藍芽檔案傳輸之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通道的建立和連線。
其中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層之上的解析就全部結束了。
附:藍芽檔案傳輸之UI分析--http://blog.csdn.net/u011960402/article/details/19538593