蓝牙opp文件发送过程剖析
蓝牙文件传输之obex层之上的分析【Android源码解析】
在上节中我们仔细分析了蓝牙文件传输过程中涉及到的UI界面,最终定格在蓝牙设备扫描的界面,我们只要选择自己想要传输的蓝牙设备就可以进行蓝牙文件的传输了。那就是这样一个简单的设备选择的点击会引发哪些连锁的操作呢?本节就来详细进行分析。
1.1.设备点击的action和响应
我们先来回顾一下,最后蓝牙设备的扫描界面是在DevicePickerFragment.java这个文件中实现的,目录是/packages/apps/Settings/src/com/android/settings/bluetooth/。毫无疑问,对这个界面中设备的点击所做的处理显然也是在该文件中实现的。我们来看一下具体的代码:
void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { …… //停止扫描,所以我们会看到点击设备后扫描就不再继续了 mLocalAdapter.stopScanning(); //保存选择的设备和时间 LocalBluetoothPreferences.persistSelectedDeviceInPicker( getActivity(), mSelectedDevice.getAddress()); //因为mNeedAuth是false,所以这个if是肯定进了,换句会说蓝牙文件的传输并不需要先进行设备的配对 if ((btPreference.getCachedDevice().getBondState() == BluetoothDevice.BOND_BONDED) || !mNeedAuth) { //发送BluetoothDevicePicker.ACTION_DEVICE_SELECTED的broadcast sendDevicePickedIntent(mSelectedDevice); finish(); } else { super.onDevicePreferenceClick(btPreference); } }
所以,简单地来说,选择设备之后就是发送了一个BluetoothDevicePicker.ACTION_DEVICE_SELECTED的broadcast来通知我们选择了某个设备进行文件的传输。对这个broadcast的监听就只有一个,还是和STATE_ON监听的那个是一样的,在文件:/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppReceiver.java中。具体代码如下:
} else if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) { //得到选中的device BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); //就是真正的传输 mOppManager.startTransfer(remoteDevice); //显示一个正在向**发送文件的toast。 String deviceName = mOppManager.getDeviceName(remoteDevice); String toastMsg; int batchSize = mOppManager.getBatchSize(); if (mOppManager.mMultipleFlag) { toastMsg = context.getString(R.string.bt_toast_5, Integer.toString(batchSize), deviceName); } else { toastMsg = context.getString(R.string.bt_toast_4, deviceName); } Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show(); }
所以,这里在对action的处理中,核心的部分就是这个函数:mOppManager.startTransfer(remoteDevice);该函数位于packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppManager.java这个文件中:
public void startTransfer(BluetoothDevice device) { …… //insert的thread不能大于3,否则就启动BluetoothOppBtErrorActivity的activity。 if (mInsertShareThreadNum > ALLOWED_INSERT_SHARE_THREAD_NUMBER) { } //启动insert thread。 insertThread = new InsertShareInfoThread(device, mMultipleFlag, mMimeTypeOfSendingFile, mUriOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles); insertThread.start(); }
这里的核心函数就是启动insert thread,这个insert thread在我们想来应该是很简单的,就是把相应的info insert到BluetoothShare.CONTENT_URI中即可。事实上是否真的如此呢?我们下节来详细分析。
至此,我们先来总结一下到目前为止设备点击之后做的工作,在点击设备之后,会发送一个ACTION_DEVICE_SELECTED的broadcast,BluetoothOppReceiver收到这个broadcast后,会启动insertThread来把需要分享的文件内容加入到对应的数据库中并引起后续的发送操作,同时在ui上显示“正在向**发送文件”的toast。
1.2.insertThread的详细分析
insertThread其实并不是简单的把数据加入到dataBase就结束,或者可以理解为这个insert的操作引起了一系列的波澜。
values.put(BluetoothShare.URI, mUri); values.put(BluetoothShare.MIMETYPE, mTypeOfSingleFile); values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress()); final Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, values);
这里的getContentResolver是什么呢,从AndroidManifest.xml中我们可以看到,对应的provider就是BluetoothOppProvider。
<provider android:name=".opp.BluetoothOppProvider" android:authorities="com.android.bluetooth.opp" android:process="@string/process"> <path-permission android:path="/btopp" android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" /> </provider>
所以,调用的就是BluetoothOppProvider的insert函数了,
public Uri insert(Uri uri, ContentValues values) { //创建或打开一个可读/可写的database SQLiteDatabase db = mOpenHelper.getWritableDatabase(); //得到对应values中的info信息 //启动BluetoothOppService这个service context.startService(new Intent(context, BluetoothOppService.class)); //把values信息insert到uri对应的database long rowID = db.insert(DB_TABLE, null, filteredValues); //再次启动BluetoothOppService的service(调用onStartCommand) context.startService(new Intent(context, BluetoothOppService.class)); //notifyChange通知database的改变 context.getContentResolver().notifyChange(uri, null); }
因此,对insert的处理有以下几个比较重要的操作:
1)创建一个可读/可写的database
2)启动了BluetoothOppService
3)通知database的改变,调用对应的onChange函数
1.2.1. Database的创建。
Database的创建是整个蓝牙文件传输过程中很重要的一环。其实他的创建语句也很简单,主要就是执行以下语句:
private void createTable(SQLiteDatabase db) { try { db.execSQL("CREATE TABLE " + DB_TABLE + "(" + BluetoothShare._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + BluetoothShare.URI + " TEXT, " + BluetoothShare.FILENAME_HINT + " TEXT, " + BluetoothShare._DATA + " TEXT, " + BluetoothShare.MIMETYPE + " TEXT, " + BluetoothShare.DIRECTION + " INTEGER, " + BluetoothShare.DESTINATION + " TEXT, " + BluetoothShare.VISIBILITY + " INTEGER, " + BluetoothShare.USER_CONFIRMATION + " INTEGER, " + BluetoothShare.STATUS + " INTEGER, " + BluetoothShare.TOTAL_BYTES + " INTEGER, " + BluetoothShare.CURRENT_BYTES + " INTEGER, " + BluetoothShare.TIMESTAMP + " INTEGER," + Constants.MEDIA_SCANNED + " INTEGER); "); } catch (SQLException ex) { Log.e(TAG, "couldn't create table in downloads database"); throw ex; } }
熟悉SQLite的同学应该很快就知道这个语句的意义,他其实就是创建一个表格:
表格名:DB_TABLE:bt_opp
_id |
uri |
hint |
_data |
mimetype |
direction |
destination |
visibility |
confirm |
|
|
|
|
|
|
|
|
|
status |
total_bytes |
current_bytes |
timestamp |
scanned |
|
|
|
|
|
这个表格共有14列,后面的所有操作都是围绕着这14列进行的。每一列的含义如下所示:
_id: 这个就是id,可以用来表示行号,它是一个自动增加的值。
-
uri: 表示发送和接收文件的uri。
-
hint:接收文件的推荐名字
-
_data:分享文件真实保存的名字
-
mimetype:文件的类型
-
direction:文件的方向,就是用来表示是发送还是接收文件。
-
destination:进行交互(文件传输)的另外一端的bt address。
-
visibility:表示传输是否显示在UI上
-
confirm:当前传输的确认状态
-
status:当前传输的状态
-
total_bytes:正在传输文件的总的大小
-
current_bytes:到目前为止已经传输的大小
-
timestamp:这个传输初始化的时间点
-
scanned:是否执行过media scanner
有了以上表格的分析,我们可以大胆猜测了,insert函数要做的一件事情就是把上面values包含的uri,mimetype和destination这三个列值填充,那是否还有别的列需要填充呢?其实在insert的细节处理中,以下几个值若是没有定义的话,则会有一些初始化的的值:
visibility:初始化为0,就是在传输过程中ui上是可见的。
-
direction:初始化为outbound,也就是默认为发送文件。
-
confirm:若是在direction为发送的情况下,confirm就是autoconfirm,无需用户确认,若是接收,则设为confirm_pending,也就是需要用户确认了。
-
status:初始化的status值为pending,表示发送还没有开始。
-
timestamp:初始化为当前系统的时间。
所以,在开始传输之前,这里有8个列是会先insert到对应的database的表格中去的。其返回的就是row_ID,也就是对应的行号了。
1.2.2. BluetoothOppService的启动
BluetoothOppService的启动是在上面真正的insert之前调用的,之所以要强调这一点是因为它注册了一个对database改变的监听,这样insert之后才能引起后续的一系列的反应。这个service的启动非常关键,所以,我们来看看这个service究竟都干了些什么工作:
public void onCreate() { //新建BluetoothOppRfcommListener类 mSocketListener = new BluetoothOppRfcommListener(mAdapter); //这里注册对CONTENT_URI的监听,就是上面提到的database改变,所对应的onChange函数就是这里注册的。 mObserver = new BluetoothShareContentObserver(); getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver); //刷新notification,这个就是用来进行通知栏的更新了 mNotifier = new BluetoothOppNotification(this); mNotifier.mNotificationMgr.cancelAll(); mNotifier.updateNotification(); //启动一个trimdatabase的thread,对数据库进行清理 new Thread("trimDatabase") { public void run() { trimDatabase(contentResolver); } }.start(); //注册ACTION_STATE_CHANGED的receiver,用于处理在传输过程中的蓝牙开关操作 //开始rfcomm的监听 startListener(); //启动update thread updateFromProvider(); }
整个流程就比较清晰了:
1)注册了database的改变监控。
2)通知栏信息的刷新
3)清理数据库
4)注册ACTION_STATE_CHANGED的receiver
5)开始rfcomm的socket的监听
6)启动update的thread,在传输过程中根据传输的情况进行不断地刷新。
1.2.2.1. 通知栏信息的刷新
这个信息的刷新,说到底还是很简单的,就是发送NOTIFY的msg,那这个msg会做些什么呢,还是要从源码的角度来简单看看。
case NOTIFY: synchronized (BluetoothOppNotification.this) { if (mPendingUpdate > 0 && mUpdateNotificationThread == null) { if (V) Log.v(TAG, "new notify threadi!"); mUpdateNotificationThread = new NotificationUpdateThread(); //启动NotificationUpdateThread mUpdateNotificationThread.start(); if (V) Log.v(TAG, "send delay message"); mHandler.sendMessageDelayed(mHandler.obtainMessage(NOTIFY), 1000); } else if (mPendingUpdate > 0) { if (V) Log.v(TAG, "previous thread is not finished yet"); mHandler.sendMessageDelayed(mHandler.obtainMessage(NOTIFY), 1000); } break; }
从这段代码中我们可以看到,第一次,就是启动NotificationUpdateThread这个thread,然后会在1s之后再次发送NOTIFY的msg,这里有一个关键的参数mPendingUpdate。他在updateNotification的时候会++,在mUpdateNotificationThread会被清除。所以,1s之后我们会再次检查是否有pending的update,若有就会继续等待1s发送,直至上次mUpdateNotificationThread结束,会再次把pending的update刷新一下。
那updateNotification究竟都做了些什么,我们来看一下:
public void run() { //priority不是很高,想想也是,只是刷新而已,慢一点无所谓 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); synchronized (BluetoothOppNotification.this) { //pendingupdate清除 mPendingUpdate = 0; } //刷新正在进行的传输 updateActiveNotification(); //刷新已经完成的传输 updateCompletedNotification(); //刷新接收的确认通知 updateIncomingFileConfirmNotification(); synchronized (BluetoothOppNotification.this) { //thread也会清null mUpdateNotificationThread = null; }
其实在最开始的传输中,这三个(active,complete,incomingfile)都是没有的,所以我们暂时不管。
1.2.2.2. 数据库的清理
在insert对应信息之前,我们会对原有的数据库做一些清理的操作。这其实也是对数据库访问效率的一种提升方法。
private static void trimDatabase(ContentResolver contentResolver) { //清除发送时出问题的一些数据(比如对端拒绝接收等原因造成的遗留数据) int delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_COMPLETE_OUTBOUND, null); //清楚接收时出问题的数据 delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_COMPLETE_INBOUND_FAILED, null); //只保留最近的1000个接收的文件在数据库中,其余的会删除。根据id的先后来删除的 Cursor cursor = contentResolver.query(BluetoothShare.CONTENT_URI, new String[] { BluetoothShare._ID }, WHERE_INBOUND_SUCCESS, null, BluetoothShare._ID); // sort by id …… delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, BluetoothShare._ID + " < " + id, null); }
总得来说,就是把一些无用的历史记录清除掉。这是一个很好的习惯啊~~
1.2.2.3. startListener
Rfcomm通道的监听就是通过这个函数来实现的
for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) { try { mBtServerSocket = mAdapter .listenUsingInsecureRfcommOn(mBtOppRfcommChannel); } } catch (IOException e1) { Log.e(TAG, "Error create RfcommServerSocket " + e1); serverOK = false; } if (!serverOK) { synchronized (this) { try { if (V) Log.v(TAG, "wait 3 seconds"); Thread.sleep(3000);} //在监听成功后,会去accept该通道。若是有数据,则会发送MSG_INCOMING_BTOPP_CONNECTION的msg出来。 clientSocket = mBtServerSocket.accept(); Log.i(TAG, "Accepted connectoin from " + clientSocket.getRemoteDevice()); BluetoothOppRfcommTransport transport = new BluetoothOppRfcommTransport( clientSocket); Message msg = Message.obtain(); msg.setTarget(mCallback); msg.what = MSG_INCOMING_BTOPP_CONNECTION; msg.obj = transport; msg.sendToTarget();
可以看到这段代码,大概的意思就是去监听对应的rfcomm通道。若是失败,就等3s之后去再次监听,一共尝试10次,也就是共30s。
1.2.2.4. 启动update的thread
整个这个thread是一个很重要的刷新thread。我们来看一下他究竟做了些什么:
//按照ID的升序,来开始遍历bt_opp表格了。 Cursor cursor = getContentResolver().query(BluetoothShare.CONTENT_URI, null, null, null, BluetoothShare._ID); //光标移到第一个,其实我们若是只有一个文件,那这个就是指向那一行了。 cursor.moveToFirst(); int arrayPos = 0; keepService = false; //这个是用来判断是否是最后一个了 boolean isAfterLast = cursor.isAfterLast(); while (!isAfterLast || arrayPos < mShares.size()) { if (isAfterLast) { //已经到最后了,但是arrayPos还没有知道最后,那这最后的东西就直接丢弃了 //这个是对一些类型文件的处理,我们不关注,后面不再列出 if (shouldScanFile(arrayPos)) { scanFile(null, arrayPos); } deleteShare(arrayPos); // this advances in the array } else { //这整个就是一个cursor和array的同步过程,他的关键就是id int id = cursor.getInt(idColumn); //说明之前的都已经结束了,这是一个新的开始 if (arrayPos == mShares.size()) { //就是把光标所指向的内容加入到array的arrayPos的位置处 insertShare(cursor, arrayPos); //其余的一些文件和通知处理 //正常的++和move next以及判断是否是最后 ++arrayPos; cursor.moveToNext(); isAfterLast = cursor.isAfterLast(); } else { int arrayId = mShares.get(arrayPos).mId; //若是arrayPos不为size的大小,又因为id是按照大小排序的,所以,若是arrayId小于id,则意味着array中的这条消息再cursor中是没有的,则需要删除 if (arrayId < id) { deleteShare(arrayPos); //两者相等,说明需要更新 } else if (arrayId == id) { updateShare(cursor, arrayPos, userAccepted); …… ++arrayPos; cursor.moveToNext(); isAfterLast = cursor.isAfterLast(); } else { //否认,就意味着cursor中有的array中没有,直接insert好了 insertShare(cursor, arrayPos); …… ++arrayPos; cursor.moveToNext(); isAfterLast = cursor.isAfterLast(); } …… //通知栏更新和cursor关闭。 mNotifier.updateNotification(); cursor.close(); }
静下心来看这段篇幅较长的代码,我们不难发现其实这个update的thread主要工作内容就做了一件事,就是cursor和mShares的同步。而这其中显然是以cursor为主的,可以认为mShares中更新cursor的内容。他们同步的关键就是固定的按照一定顺序排列的id,他们一起从0开始增加,若是mshares中的id小于cursor中的id,则意味着mShares中有cursor中没有的内容,那就使用deleteShare来删除对应的内容,若是两者id相等,则使用updateShare来更新cursor中内容,若是mShares的id大于cursor中的id,则意味着cursor中有内容没有增加到mShares中,则需要使用insertShare来添加。在这些的最后就是updateNotification。
所以,这个函数涉及到的三个最主要的方面就是insertShare,updateShare以及deleteShare,当然最后还有一个updateNotification。下面我们将一一进行分析这几个函数的内容。
1)insertShare的分析
insertShare就是把cursor加入到对应mShares中。
private void insertShare(Cursor cursor, int arrayPos) { //得到对应的cursor所对应的info,也就是表格中各个列的值。 //把info和arrayPos相关联,并加入到mShares中去 mShares.add(arrayPos, info); //可以开始 if (info.isReadyToStart()) { if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { //若是发送文件,则先通过open来确定需要发送的文件是否存在。 } if (mBatchs.size() == 0) { //第一次则会新建oppBatch BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info); newBatch.mId = mBatchId; mBatchId++; //加入到mBatchs中 mBatchs.add(newBatch); if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) { //若是发送,新建发送的transfer mTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch); } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) { //若是接收,新建接收的transfer,差别就在于接收没有Batch参数 mServerTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch, mServerSession); } //开始transfer,接收和发送是类似的。 mTransfer.start(); } else { int i = findBatchWithTimeStamp(info.mTimestamp); //正在传输该info对应的batch,就直接add即可。 mBatchs.get(i).addShare(info); } else { //正在传输别的info就直接加入到batchs中即可 BluetoothOppBatch newBatch = new BluetoothOppBatch(this, info); newBatch.mId = mBatchId; mBatchId++; mBatchs.add(newBatch); }
到此时我们会发现insertShare这个函数,加入info只是其职责之一,其更重要的是启动对应的transfer,当然若是有正在工作的transfer,他就根据情况只做insert的操作即可。关于BluetoothOppTransfer的start是一个最重要的函数,我们将会在后面详细介绍。
2)updateShare
该函数的作用就是根据cursor中的内容刷新mShares。
private void updateShare(Cursor cursor, int arrayPos, boolean userAccepted) { //根据arryPos得到原来的info,修改成cursor对应的info值 BluetoothOppShareInfo info = mShares.get(arrayPos); …… //根据新的cursor中的timestamp来看是否有对应的batch int i = findBatchWithTimeStamp(info.mTimestamp); //看对应的batch是否已经finished或者failed if (batch.mStatus == Constants.BATCH_STATUS_FINISHED || batch.mStatus == Constants.BATCH_STATUS_FAILED) { //若是这样把对应的transfer stop mTransfer.stop(); //同时把该batch remove removeBatch(batch); }
刷新share总得来说就是根据cursor内容更改mShares中的内容,当然还会有一些细节的处理,这里不再详细描述,大家有兴趣可以去读读源码。
3)deleteShare
deleteShare就是根据对应的arrayPos得到info,然后根据info的timestamp得到对应的batch,然后进行delete的处理,需要注意的是这里会有一些细节的处理地方,本文就不详细阐述了。
至此,BluetoothOppService的启动就全部分析完成了,主要就是进行各种数据库的准备和清理操作。最重要的就是引入了BluetoothOppTransfer的start,这个是关键的传输函数。
BluetoothOppTransfer分析
这个函数就是开始Opp的传输,我们来看一下具体的实现:
public void start() { //开始一个handler的thread,用于接收各种event mSessionHandler = new EventHandler(mHandlerThread.getLooper()); //若是没有rfcomm通道的信息,则可以先sdp一下得到对应的rfcomm通道 Batch.mDestination.fetchUuidsWithSdp(); //若是有rfcomm通道信息,新建一个thread去建rfcomm并连接rfcomm socket mConnectThread = new SocketConnectThread(mBatch.mDestination, msg.arg1, false); mConnectThread.start(); //连接成功,设置batch状态为BATCH_STATUS_RUNNING mBatch.mStatus = Constants.BATCH_STATUS_RUNNING; //若是发送,则新建BluetoothOppObexClientSession,并启动发送 mSession = new BluetoothOppObexClientSession(mContext, mTransport); }
整个这个函数的实现显然不是如我上面所写的这样“清晰”,他是使用了一些event的handler来实现的。但整个流程的确就是这样的,这里面有两个关键的步骤:
1)rfcomm通道的建立和连接。
2)启动BluetoothOppObexClientSession。
其中rfcomm通道的建立和连接是文件传输之前的一个很重要的蓝牙设备交互过程,可想而知,这个操作的实现也必然是非常复杂的。因此,本章暂且不去详细介绍,后面会另开一章详细介绍这个过程。下面我们就来看BluetoothOppObexClientSession都做了些什么。
1.3.真正的传输开始分析
上面我们可以认为都是一些文件传输的准备工作,下面才是真正的文件传输的开始。
1.3.1. BluetoothOppObexClientSession的分析
BluetoothOppObexClientSession会启动一个ClientThread的thread来进行文件发送操作的一系列动作。
public void run() { //这是一个比较重要的操作,先抓wakelock锁,这样在传输文件的过程中,系统就进入不了休眠了,可以保证灭屏的状况下文件传输的完成。 wakeLock.acquire(); //obex层的connect connect(); while (!mInterrupted) { //真正的发送文件 doSend(); } //obex层的disconnect disconnect(); //释放wakelock锁 wakeLock.release(); //发送MSG_SESSION_COMPLETE的msg msg.what = BluetoothOppObexSession.MSG_SESSION_COMPLETE; msg.sendToTarget(); }
整个这个过程一共有以下几个关键的步骤:
1)wakelock锁的抓住和释放
2)obex层的连接和断开
3)真正的send函数,该函数最终会执行sendFile函数,并在最后发送MSG_SHARE_COMPLETE的msg。
这其中wakelock锁想必大家都已经很熟悉了,就不多说了,obex层的连接和断开是蓝牙中很关键的部分,将会另开一章来详细讲解。这里我们主要看sendFile函数的工作。
1.3.1.1. sendFile的分析
sendFile的主要工作如下:
private int sendFile(BluetoothOppSendFileInfo fileInfo) { //更新表格中的status状态为running Constants.updateShareStatus(mContext1, mInfo.mId, BluetoothShare.STATUS_RUNNING); //obex层的put headset putOperation = (ClientOperation)mCs.put(request); //打开对应的input和output stream outputStream = putOperation.openOutputStream(); inputStream = putOperation.openInputStream(); //更新current_byte列的值为0,初始化 updateValues.put(BluetoothShare.CURRENT_BYTES, 0); //启动一个20s的定时器,用于第一笔包写的超时 mCallback.sendMessageDelayed(mCallback .obtainMessage(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT), BluetoothOppObexSession.SESSION_TIMEOUT); //写第一笔包,obex层调用 outputStream.write(buffer, 0, readLength); //remove超时 mCallback.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT); //根据response code来刷新current_byte updateValues.put(BluetoothShare.CURRENT_BYTES, position); //若仍有数据,继续写,再根据response code和length来继续,直到写完。需要特别注意的是这里没有超时了。。 while (!mInterrupted && okToProceed && (position != fileInfo.mLength)) { readLength = a.read(buffer, 0, outputBufferSize); outputStream.write(buffer, 0, readLength); }
整个sendFile就是通过obex层的write来进行数据的写,然后根据得到的response来决定是否继续发送。整个流程还是很清晰的。
我们前文提到在sendFile完成之后会发送MSG_SHARE_COMPLETE的msg,这个msg的主要工作对发送而言就是看是否还有需要发送的文件,若有就继续发送,否则停止,而对接收而言,他就不做任何工作了。
在doSend完成之后会发送MSG_SESSION_COMPLETE的msg,这个msg的作用就更简单了,他会把batchs的状态更新为finished。
至此,文件的发送过程中obex层之上的解析就全部结束了。