Android 蓝牙开发(五)OPP接收文件
2017-05-03 20:51
309 查看
原址
1 创建rfcomm层sever |
BluetoothOppReceiver在AndroidManifest.xml文件中进行了注册,其中action包括”Android.bluetooth.adapter.action.STATE_CHANGED”,也就是它会监听蓝牙状态的改变。
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)){ if (BluetoothAdapter.STATE_ON == intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) { context.startService(new Intent(context, BluetoothOppService.class)); //...... } }1
2
3
4
5
6
1
2
3
4
5
6
[/code]
监听到蓝牙开启,BluetoothOppReceiver会打开服务BluetoothOppService。接着看BluetoothOppService。
mAdapter = BluetoothAdapter.getDefaultAdapter(); //创建蓝牙适配器 mSocketListener = new BluetoothOppRfcommListener(mAdapter); mObserver = new BluetoothShareContentObserver(); //监听数据库的变化 getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver); mNotifier = new BluetoothOppNotification(this); // 创建BluetoothOppNotification对象 mNotifier.mNotificationMgr.cancelAll(); //取消所有状态栏通知 mNotifier.updateNotification(); IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); registerReceiver(mBluetoothReceiver, filter); //注册广播接受者,监听蓝牙状态的改变 synchronized (BluetoothOppService.this) { if (mAdapter == null) {//检查蓝牙是否可用。 Log.w(TAG, "Local BT device is not enabled"); } else { startListener(); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[/code]
在BluetoothOppService的onCreate()函数中,创建了BluetoothOppRfcommListener对象和BluetoothOppNotification对象。
BluetoothOppRfcommListener类主要用来创建BluetoothServerSocket,接收其他设备的连接。
BluetoothOppNotification则用来弹状态栏通知,显示发送、接收文件,及发送和接收进度等。
BluetoothOppService中还注册了observer,监测数据库的变化。
注册广播接受者,监听蓝牙状态的改变,开启蓝牙时,调用startSocketListener->mSocketListener.start,创建socket并开始监听其他设备的连接。关闭蓝牙时,调用mSocketListener.stop,关闭BluetoothServerSocket。
接着看startListener();
private void startListener() { if (!mListenStarted) {//mListenStarted初始值为false if (mAdapter.isEnabled()) {//检查蓝牙是否开启 mHandler.sendMessage(mHandler.obtainMessage(START_LISTENER)); mListenStarted = true; } } }1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
[/code]
handler接收到消息START_LISTENER,则调用
//开始socket监听。 private void startSocketListener() { mSocketListener.start(mHandler); }1
2
3
4
1
2
3
4
[/code]
mSocketListener为创建的BluetoothOppRfcommListener对象,调用其start()方法,携带handler对象。在start()内将此handler存为全局变量,用于之后向BluetoothOppService发送消息。下一步创建线程mSocketAcceptThread ,并开始运行该线程。
public synchronized boolean start(Handler callback) { if (mSocketAcceptThread == null) { mCallback = callback; //创建线程mSocketAcceptThread mSocketAcceptThread = new Thread(TAG) { public void run() { if (Constants.USE_TCP_DEBUG) { //这个是实用tcp协议,可忽略。 } else { boolean serverOK = true; //可能创建失败,尝试10次 for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) { try { mBtServerSocket = mAdapter.listenUsingInsecureRfcommWithServiceRecord("OBEX Object Push", BluetoothUuid.ObexObjectPush.getUuid()); } catch (IOException e1) { serverOK = false; } if (!serverOK) { synchronized (this) { try {//等待300ms Thread.sleep(300); } catch (InterruptedException e) { mInterrupted = true; } } } else { //创建BluetoothServerSocket成功,退出for循环。 break; } } if (!serverOK) { mInterrupted = true; } BluetoothSocket clientSocket; while (!mInterrupted) { try { BluetoothServerSocket sSocket = mBtServerSocket; if (sSocket ==null) { mInterrupted = true; } else {//接收客户端的连接 clientSocket = sSocket.accept(); BluetoothOppRfcommTransport transport = new BluetoothOppRfcommTransport( clientSocket); Message msg = Message.obtain(); msg.setTarget(mCallback); msg.what = MSG_INCOMING_BTOPP_CONNECTION; msg.obj = transport; msg.sendToTarget(); } } catch (IOException e) { try { Thread.sleep(500); } catch (InterruptedException ie) {} } } } } }; mInterrupted = false; if(!Constants.USE_TCP_SIMPLE_SERVER) {//该值为false,没有使用tcp相关的。 mSocketAcceptThread.start(); //mSocketAcceptThread线程开始运行 } } return true; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
[/code]
mSocketAcceptThread中创建BluetoothServerSocket,创建BluetoothServerSocket可能会失败,这里做的保护措施是进行10次尝试。listenUsingInsecureRfcommWithServiceRecord()函数表示此rfcomm连接是不安全的,所以连接时不会进行配对。
sSocket.accept()是阻塞的,等待远程设备的连接。
当与远程设备连接成功后,发送消息MSG_INCOMING_BTOPP_CONNECTION。mCallback对应BluetoothOppService的mHandler。所以在BluetoothOppService中看是如何处理此消息到。
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case BluetoothOppRfcommListener.MSG_INCOMING_BTOPP_CONNECTION: ObexTransport transport = (ObexTransport)msg.obj; //传入连接策略: //1. 如果没有向外分享文件,也没有保持到连接则 start //2. 如果正在通过向外蓝牙分享文件,保持20秒(1秒* 20次), //3. 如果之前有保持到连接,则直接拒绝。 if (mBatchs.size() == 0 && mPendingConnection == null) { //判断如果没有正在向外分享文件,并且之前没有保留的连接,则创建obex server端。 createServerSession(transport); } else { if (mPendingConnection != null) {//如果有保留的连接,则直接拒绝,关闭rfcomm连接。 try { transport.close(); } catch (IOException e) { Log.e(TAG, "close tranport error"); } } else if (Constants.USE_TCP_DEBUG && !Constants.USE_TCP_SIMPLE_SERVER) {//忽略 } else {//正在向外分享文件 mIncomingRetries = mIncomingRetries + 1; //记录尝试次数 mPendingConnection = transport; //保留连接。 Message msg1 = Message.obtain(mHandler); msg1.what = MSG_INCOMING_CONNECTION_RETRY; mHandler.sendMessageDelayed(msg1, 1000); //1s后发送消息, } } break; case MSG_INCOMING_CONNECTION_RETRY: //尝试重连 if (mBatchs.size() == 0) { //分享文件完毕 createServerSession(mPendingConnection); mIncomingRetries = 0; //尝试次数清零 mPendingConnection = null; //保留的连接清除 } else {//分享文件尚未完成 if (mIncomingRetries == 20) { //最多进行19次重试,第二十次还是没有分享完成则关闭连接,不再接收此次远程设备分享。 try { mPendingConnection.close(); } catch (IOException e) { } mIncomingRetries = 0; mPendingConnection = null; } else {//1s后重新试 mIncomingRetries = mIncomingRetries + 1; Message msg2 = Message.obtain(mHandler); msg2.what = MSG_INCOMING_CONNECTION_RETRY; mHandler.sendMessageDelayed(msg2, 1000); } } break;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
[/code]
2 创建obex层server |
private void createServerSession(ObexTransport transport) { //创建obex server端。 mServerSession = new BluetoothOppObexServerSession(this, transport); mServerSession.preStart(); //进行准备 }1
2
3
4
5
1
2
3
4
5
[/code]
client端socket连接成功后会调用ClientSession.connect()进行obex层的连接。server端回调onConnect函数
public int onConnect(HeaderSet request, HeaderSet reply) { Long objectCount = null; try { byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET); if(uuid != null) { //如果target有内容则返回OBEX_HTTP_NOT_ACCEPTABLE return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; } //获取分享文件数量信息 objectCount = (Long) request.getHeader(HeaderSet.COUNT); } catch (IOException e) { return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; } 。。。 return ResponseCodes.OBEX_HTTP_OK; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[/code]
onConnect函数中会对请求头中的target信息进行判断,没有才返回ok。
client端connect成功后,调用put()函数发送文件详细信息。server端回调onPut。
public int onPut(Operation op) { HeaderSet request; String name, mimeType; Long length; int obexResponse = ResponseCodes.OBEX_HTTP_OK; //对于分享的多个文件,用户拒绝第一个文件,其他也拒绝。 if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED) { return ResponseCodes.OBEX_HTTP_FORBIDDEN; } try { boolean pre_reject = false; request = op.getReceivedHeader(); //获取请求信息 name = (String)request.getHeader(HeaderSet.NAME); //文件名 length = (Long)request.getHeader(HeaderSet.LENGTH); //文件长度 mimeType = (String)request.getHeader(HeaderSet.TYPE); // 文件类型 if (length == 0) { //长度为0,设置response。 pre_reject = true; obexResponse = ResponseCodes.OBEX_HTTP_LENGTH_REQUIRED; } if (name == null || name.equals("")) { //name为null或空,设置response pre_reject = true; obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST; } if (!pre_reject) { String extension, type; int dotIndex = name.lastIndexOf("."); //查看文件名中是否有‘.’, if (dotIndex < 0 && mimeType == null) { pre_reject = true; obexResponse = ResponseCodes.OBEX_HTTP_BAD_REQUEST; } else { //获取后缀名 extension = name.substring(dotIndex + 1).toLowerCase(); MimeTypeMap map = MimeTypeMap.getSingleton(); type = map.getMimeTypeFromExtension(extension); //查看后缀对应的文件类型 if (type != null) { mimeType = type; } else { if (mimeType == null) { //请求信息没有类型信息,文件名的后缀也没有对应的类型。 pre_reject = true; obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE; } } if (mimeType != null) { mimeType = mimeType.toLowerCase(); //小写 } } } if (!pre_reject && (mimeType == null || (!isWhitelisted && !Constants.mimeTypeMatches(mimeType, Constants.ACCEPTABLE_SHARE_INBOUND_TYPES)) || Constants.mimeTypeMatches(mimeType, Constants.UNACCEPTABLE_SHARE_INBOUND_TYPES))) { // mimeType为null或不可接受的列表,拒绝传输 pre_reject = true; obexResponse = ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE; } if (pre_reject && obexResponse != ResponseCodes.OBEX_HTTP_OK) { // 一些坏的实施客户端不会发送断开连接 return obexResponse; } } catch (IOException e) { return ResponseCodes.OBEX_HTTP_BAD_REQUEST; } ContentValues values = new ContentValues(); values.put(BluetoothShare.FILENAME_HINT, name); //文件名 values.put(BluetoothShare.TOTAL_BYTES, length.intValue()); //文件长度 values.put(BluetoothShare.MIMETYPE, mimeType); //文件类型 values.put(BluetoothShare.DESTINATION, destination); //设备蓝牙地址 values.put(BluetoothShare.DIRECTION, BluetoothShare.DIRECTION_INBOUND); //接收 values.put(BluetoothShare.TIMESTAMP, mTimestamp); //时间 boolean needConfirm = true; if (!mServerBlocking) { //对于多个文件,接收了第一个,所以我们自动接受后面的 values.put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED); needConfirm = false; } if (isWhitelisted) { //对于白名单的社保,则不需要用户确认。白名单哪里设置暂时没有发现。 values.put(BluetoothShare.USER_CONFIRMATION, BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED); needConfirm = false; } //将此条记录插入到数据库。用来提示用户,接收还是拒绝,这个稍后再说弹提示框。 Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values); mLocalShareInfoId = Integer.parseInt(contentUri.getPathSegments().get(1)); if (needConfirm) { //需要确认,发送广播,广播接受者中弹了一个toast。 Intent in = new Intent(BluetoothShare.INCOMING_FILE_CONFIRMATION_REQUEST_ACTION); in.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); mContext.sendBroadcast(in); } synchronized (this) { if (mWakeLock.isHeld()) { mPartialWakeLock.acquire(); mWakeLock.release(); //释放锁 } mServerBlocking = true; try { while (mServerBlocking) {//进行阻塞,等待用户确认是否接收。 wait(1000); if (mCallback != null && !mTimeoutMsgSent) {//发送超时信息,避免用户没有进行确认。 mCallback.sendMessageDelayed(mCallback .obtainMessage(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT), BluetoothOppObexSession.SESSION_TIMEOUT); mTimeoutMsgSent = true; } } } catch (InterruptedException e) { } } //........1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
[/code]
onPut中对请求的文件信息(文件名、文件长度、文件类型)进行检查。其中文件类型是否可接收在Constants中有定义(ACCEPTABLE_SHARE_INBOUND_TYPES和UNACCEPTABLE_SHARE_INBOUND_TYPES)。
符合规定则插入数据库。(插入数据库后,根据数据内容提示用户有文件接收请求。)
然后阻塞,等待用户的确认,发送延时消息(50s),避免用户没有处理该传入文件请求。
在BluetoothOppObexServerSession的onPut函数中会调用mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);将数据插入数据库。
3 创建文件 |
在mUpdateThread中会调用根据数据库进行不同的操作,包括:创建文件、显示传入文件确认通知等。在mUpdateThread线程中调用insertShare,插入共享文件到ArrayList mShares中。然后:
mServerTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch, mServerSession); mServerTransfer.start();1
2
3
1
2
3
[/code]
BluetoothOppTransfer.start() -> startObexSession() -> processCurrentShare() ->mSession.addShare(mCurrentShare);
接着看BluetoothOppObexServerSession中的addShare。
public void addShare(BluetoothOppShareInfo info) { mInfo = info; //当前传输信息 mFileInfo = processShareInfo(); //处理传输信息 } private BluetoothOppReceiveFileInfo processShareInfo() { BluetoothOppReceiveFileInfo fileInfo = BluetoothOppReceiveFileInfo.generateFileInfo( mContext, mInfo.mId); return fileInfo; }1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
[/code]
generateFileInfo()中判断是否有sd卡。判断/sdcard/blueooth是否是文件夹,及创建该文件夹是否成功。通过蓝牙接收的文件存储路径就是/sdcard/blueooth目录下。
检查存储空间是否满足。检查文件名和文件类型,生成唯一的文件名(因为可能接收到同名的文件多次则在文件名后,类型名前加“-1”,如:img-1.jpg,img-2.jpg。)
最后创建文件,返回BluetoothOppReceiveFileInfo对象。
public static BluetoothOppReceiveFileInfo generateFileInfo(Context context, int id) { ContentResolver contentResolver = context.getContentResolver(); Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id); String filename = null, hint = null, mimeType = null; long length = 0; Cursor metadataCursor = contentResolver.query(contentUri, new String[] { BluetoothShare.FILENAME_HINT, BluetoothShare.TOTAL_BYTES, BluetoothShare.MIMETYPE }, null, null, null); if (metadataCursor != null) { try { if (metadataCursor.moveToFirst()) { hint = metadataCursor.getString(0); length = metadataCursor.getInt(1); mimeType = metadataCursor.getString(2); } } finally { metadataCursor.close(); } } File base = null; StatFs stat = null; //判断是否有sd卡 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { String root = Environment.getExternalStorageDirectory().getPath(); base = new File(root + Constants.DEFAULT_STORE_SUBDIR); if (!base.isDirectory() && !base.mkdir()) { // /sdcard/bluetooth不是文件夹并且创建文件夹失败 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); } stat = new StatFs(base.getPath()); } else { //没有sd卡 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_NO_SDCARD); } //检查文件系统是否有足够的空间来保存文件 if (stat.getBlockSize() * ((long)stat.getAvailableBlocks() - 4) < length) { return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_SDCARD_FULL); } filename = choosefilename(hint); if (filename == null) { return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); } String extension = null; int dotIndex = filename.lastIndexOf("."); if (dotIndex < 0) { if (mimeType == null) { return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); } else { extension = ""; } } else { extension = filename.substring(dotIndex); filename = filename.substring(0, dotIndex); } filename = base.getPath() + File.separator + filename; // 生成唯一的文件名。 String fullfilename = chooseUniquefilename(filename, extension); if (!safeCanonicalPath(fullfilename)) { // If this second check fails, then we better reject the transfer return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); } if (fullfilename != null) { try { //创建文件,将流关闭。 new FileOutputStream(fullfilename).close(); int index = fullfilename.lastIndexOf('/') + 1; // 更新显示名称 if (index > 0) { String displayName = fullfilename.substring(index); ContentValues updateValues = new ContentValues(); updateValues.put(BluetoothShare.FILENAME_HINT, displayName); context.getContentResolver().update(contentUri, updateValues, null, null); } return new BluetoothOppReceiveFileInfo(fullfilename, length, new FileOutputStream(fullfilename), 0); } catch (IOException e) { return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); } } else { return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
[/code]
4 传入文件确认通知 |
updateNotification()会创建线程NotificationUpdateThread(),并运行该线程。该线程会进行更新notification,包括:
- updateActiveNotification()表示正在传输的notification,用来更新传输或接收进度;
- updateCompletedNotification()用来显示已完成的notification;
- updateIncomingFileConfirmNotification()用来显示传入文件的通知。
主要看一下显示传入文件确认的通知
private void updateIncomingFileConfirmNotification() { Cursor cursor = mContext.getContentResolver().query(BluetoothShare.CONTENT_URI, null, WHERE_CONFIRM_PENDING, null, BluetoothShare._ID); if (cursor == null) { return; } for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { CharSequence title = mContext.getText(R.string.incoming_file_confirm_Notification_title); CharSequence caption = mContext .getText(R.string.incoming_file_confirm_Notification_caption); int id = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)); long timeStamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)); Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id); //创建Notification,设置notification的图标、标志、标题等 Notification n = new Notification(); n.icon = R.drawable.bt_incomming_file_notification; n.flags |= Notification.FLAG_ONLY_ALERT_ONCE; n.flags |= Notification.FLAG_ONGOING_EVENT; n.defaults = Notification.DEFAULT_SOUND; n.tickerText = title; Intent intent = new Intent(Constants.ACTION_INCOMING_FILE_CONFIRM); intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); intent.setDataAndNormalize(contentUri); n.when = timeStamp; //时间 //设置点击此notification时,发送Constants.ACTION_INCOMING_FILE_CONFIRM广播。 n.setLatestEventInfo(mContext, title, caption, PendingIntent.getBroadcast(mContext, 0, intent, 0)); intent = new Intent(Constants.ACTION_HIDE); intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); intent.setDataAndNormalize(contentUri); //设置删除此notification时,发送Constants.ACTION_HIDE广播。 n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); mNotificationMgr.notify(id, n); //显示该notification。 } cursor.close(); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[/code]
一般情况我们接收文件都会去点击该notification,点击该notification会发送Constants.ACTION_INCOMING_FILE_CONFIRM广播。现在使用的手机许多都改成直接弹对话框了,而是直接弹出对话框(直接发送该广播就可以了)。
if (action.equals(Constants.ACTION_INCOMING_FILE_CONFIRM)) { Uri uri = intent.getData(); Intent in = new Intent(context, BluetoothOppIncomingFileConfirmActivity.class); in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); in.setDataAndNormalize(uri); context.startActivity(in); //打开BluetoothOppIncomingFileConfirmActivity界面。 NotificationManager notMgr = (NotificationManager)context .getSystemService(Context.NOTIFICATION_SERVICE); if (notMgr != null) { //清除该notification notMgr.cancel((int)ContentUris.parseId(intent.getData())); } }1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
[/code]
在BluetoothOppReceiver中接收到该广播Constants.ACTION_INCOMING_FILE_CONFIRM,打开BluetoothOppIncomingFileConfirmActivity界面。该界面样式是一个dialog,用来判断是否接收该文件。点击接收或取消后都更新数据库,BluetoothShare.USER_CONFIRMATION字段的值不同,分别为USER_CONFIRMATION_CONFIRMED和USER_CONFIRMATION_DENIED。
BluetoothProvider对应的update方法中调用notifyChange(uri, null)。通知监测数据库变化的observer。
BluetoothOppService检测到数据库的变化->updateFromProvider ->创建UpdateThread线程并运行 - >updateShare()。
如果用户点击了接收或取消,也就是对此请求进行了确认,调用mServerTransfer.setConfirmed()。
BluetoothOppTransfer setConfirmed()函数。在该函数中创建notifyThread 线程,使server session停止阻塞
public void setConfirmed() { /* unblock server session */ final Thread notifyThread = new Thread("Server Unblock thread") { public void run() { synchronized (mSession) { mSession.unblock(); //停止阻塞 mSession.notify(); //该函数应该其父类实现的,没有源码。 } } }; notifyThread.start(); //运行notifyThread线程。 }1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
[/code]
BluetoothOppObexServerSession的unblock()函数设置mServerBlocking为false。使onPut函数阻塞取消。
onPut阻塞时发送50s延时消息BluetoothOppObexSession.MSG_CONNECT_TIMEOUT。当用户超过时间并没有点击接收文件的notification,或者并没有点击dialog上的接收或取消,看一下是怎么处理的
// 删除传入文件确认通知 NotificationManager nm = (NotificationManager)mContext .getSystemService(Context.NOTIFICATION_SERVICE); nm.cancel(mCurrentShare.mId); //用户确认的界面如果已开启,则dialog提示改为“接受来自‘’的文件时发生超时”,2s后关闭界面。 Intent in = new Intent(BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION); mContext.sendBroadcast(in); markShareTimeout(mCurrentShare); //更新数据库。1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
[/code]
超时后用户没有处理,则会先清除传入文件确认的通知,然后ui超时处理(dialog内容关闭,2s后关闭),如果没有显示用户确认的dialog则不用管。更新数据库,流程和上面点击接收或取消流程类似,都会调到mSession.unblock(),停止onPut函数的阻塞。
5 接收文件 |
mAccepted = mInfo.mConfirm; //用户确认 int status = BluetoothShare.STATUS_SUCCESS; //确认或自动确认(对于多个文件后面的文件) if (mAccepted == BluetoothShare.USER_CONFIRMATION_CONFIRMED || mAccepted == BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED || mAccepted == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED) { if (mFileInfo.mFileName == null) { //检查文件名 status = mFileInfo.mStatus; mInfo.mStatus = mFileInfo.mStatus; Constants.updateShareStatus(mContext, mInfo.mId, status); obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; } if (mFileInfo.mFileName != null) { ContentValues updateValues = new ContentValues(); contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId); updateValues.put(BluetoothShare._DATA, mFileInfo.mFileName); updateValues.put(BluetoothShare.STATUS, BluetoothShare.STATUS_RUNNING); mContext.getContentResolver().update(contentUri, updateValues, null, null); //接收文件 status = receiveFile(mFileInfo, op); if (status != BluetoothShare.STATUS_SUCCESS) { obexResponse = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; } Constants.updateShareStatus(mContext, mInfo.mId, status); } if (status == BluetoothShare.STATUS_SUCCESS) { //接收成功 Message msg = Message.obtain(mCallback, BluetoothOppObexSession.MSG_SHARE_COMPLETE); msg.obj = mInfo; msg.sendToTarget(); } else { if (mCallback != null) { Message msg = Message.obtain(mCallback, BluetoothOppObexSession.MSG_SESSION_ERROR); mInfo.mStatus = status; msg.obj = mInfo; msg.sendToTarget(); } } } else if (mAccepted == BluetoothShare.USER_CONFIRMATION_DENIED || mAccepted == BluetoothShare.USER_CONFIRMATION_TIMEOUT) { //用户拒绝接收,或者超时未进行确认 if (mFileInfo.mFileName != null) { try { mFileInfo.mOutputStream.close(); //关闭文件输出流 } catch (IOException e) { } new File(mFileInfo.mFileName).delete(); //删除文件 } status = BluetoothShare.STATUS_CANCELED; // 设置状态本地取消 Constants.updateShareStatus(mContext, mInfo.mId, status); obexResponse = ResponseCodes.OBEX_HTTP_FORBIDDEN; //response设为被禁止 Message msg = Message.obtain(mCallback); msg.what = BluetoothOppObexSession.MSG_SHARE_INTERRUPTED; mInfo.mStatus = status; msg.obj = mInfo; msg.sendToTarget(); //发送消息。 }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
[/code]
首先判断用户是否接收文件。
接收文件:检查文件名是否有问题,没有问题,则更新数据看接收状态,开始接收文件。接收文件完成后更新notification。
拒绝接收或超时未处理:关闭文件输出流,删除空文件。更新notification。
接着看接收文件的具体代码receiveFile
private int receiveFile(BluetoothOppReceiveFileInfo fileInfo, Operation op) { int status = -1; BufferedOutputStream bos = null; InputStream is = null; boolean error = false; try { is = op.openInputStream(); //获取obex连接输入流 } catch (IOException e1) { status = BluetoothShare.STATUS_OBEX_DATA_ERROR; error = true; } Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + mInfo.mId); if (!error) { ContentValues updateValues = new ContentValues(); updateValues.put(BluetoothShare._DATA, fileInfo.mFileName); mContext.getContentResolver().update(contentUri, updateValues, null, null); } int position = 0; if (!error) { //获取输出流,输出到fileInfo文件中 bos = new BufferedOutputStream(fileInfo.mOutputStream, 0x10000); } if (!error) { int outputBufferSize = op.getMaxPacketSize(); //获取最大的包大小 byte[] b = new byte[outputBufferSize]; int readLength = 0; long timestamp = 0; try { while ((!mInterrupted) && (position != fileInfo.mLength)) { readLength = is.read(b); //获取蓝牙分享的内容 if (readLength == -1) { //表示接收结束。 break; } bos.write(b, 0, readLength); //将读到的内容写入到本地文件中。 position += readLength; //接收到数据大小 //更新数据库下载大小。BluetoothOppProvider.update->BluetoothOppService->BluetoothOppNotification。更新接收进度条。 ContentValues updateValues = new ContentValues(); updateValues.put(BluetoothShare.CURRENT_BYTES, position); mContext.getContentResolver().update(contentUri, updateValues, null, null); } } catch (IOException e1) { /* OBEX Abort packet received from remote device */ if ("Abort Received".equals(e1.getMessage())) { status = BluetoothShare.STATUS_CANCELED; } else { status = BluetoothShare.STATUS_OBEX_DATA_ERROR; } error = true; } } if (mInterrupted) { //接收被用户打断 status = BluetoothShare.STATUS_CANCELED; } else { if (position == fileInfo.mLength) { //判断接收到的和文件长度相同,则接收完成 status = BluetoothShare.STATUS_SUCCESS; } else {//接收失败 if (status == -1) { status = BluetoothShare.STATUS_UNKNOWN_ERROR; } } } if (bos != null) { try { bos.close(); //关闭输出流。 } catch (IOException e) { } } return status; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
[/code]
receiveFile函数通过流获取到蓝牙数据,并将数据写入本地文件中。更新接收到进度。到此接收文件的具体过程就分析完了。
相关文章推荐
- Android 蓝牙开发【五】OPP接收文件
- Android 蓝牙开发(五)OPP接收文件
- Android 蓝牙开发(四)OPP传输文件
- android -- 蓝牙 bluetooth (四)OPP文件传输
- Android官方开发文档Training系列课程中文版:通过NFC共享文件之从其它设备接收文件
- android4.2 蓝牙 bluetooth第4节:OPP文件传输
- Android蓝牙开发之数据窜位和数据接收错误以及重组字节数据
- [FAQ12964][OPP]无法通过蓝牙分享及接收apk应用文件
- android 通过蓝牙接收文件,从历史传输记录打开,无法自动选择合适的应用程序
- android -- 蓝牙 bluetooth (四)OPP文件传输
- android -- 蓝牙 bluetooth (四)OPP文件传输
- 处理android 经典蓝牙发送文件时接收包的问题
- android -- 蓝牙 bluetooth (四)OPP文件传输
- ZT android -- 蓝牙 bluetooth (四)OPP文件传输
- android通过蓝牙接收文件打开时无法自动选择合适的应用程序
- Android BluetoothProfile之OPP(蓝牙文件分享流程)
- Android开发蓝牙篇之蓝牙配对、连接与接收数据
- eclipse中开发android程序时,打开layout配置文件eclipse关闭
- Android开发之旅:深入分析布局文件
- Android-NDK开发之基础--Android.mk文件语法规范(转JavaEye)