跟我一塊兒學OPP(一)

摘要概述

在藍牙opp時既然是發送文件,client爲發送方,那麼還須要明確一個接收方做爲server,待發送方和接受方肯定後就要在兩個設備之間點對點的打通一條光明大道做爲傳輸通道。android

 

固然還有你要運輸的信息,有了這四要素,你就能夠進行完美的運輸了。sql

 

在運輸結束以後須要把通道給拆了,由於每一個設備的通道是有限的。因此運輸的前提是保證server端存在,而且通道能夠正確創建。數據庫

 

那麼在創建以後開始傳輸,當信息送到server端時server是有自主選擇權的,能夠選擇是否接受。若是被server拒絕的話,那麼信息就會被丟棄了。ide

通道是否創建以及傳輸的具體狀況都須要通知給client和server,因此應運而生的就是必不可少的notification。工具

 

因此,做爲client端首先從功能上考慮總共須要處理5件事線程

 

  1. 選擇接收方:在保證藍牙開啓的狀況下,開啓選擇附近可用藍牙設備界面,供用戶選擇目標設備進行分享設計

  2. 將要發送的文件存入db:以便隨時更新狀態,隨時通知client。將須要分享的file插入到btopp.db中,由BluetoothOppProvider提供對數據庫的增刪改查。用於記錄file的狀態,協助傳輸和notification3d

  3. 創建傳輸通道:經過sdp搜索簡歷l2cap鏈接,若sdp搜索失敗或者l2cap鏈接創建失敗,則會去創建rfcomm鏈接。obex層connect操做,會在connect以後進行put發送。server

  4. 拆除通道:在發送完畢後斷開obex和l2cap或者是Rfcomm,即斷開所創建的通道。sqlite

  5. notification:對於文件的等待狀態,正在發送狀態,以及發送結束(包括髮送成功或者失敗或者被server拒絕)進行更新通notification,是直接和用戶交互的窗口,貫穿整個文件傳輸過程。

 

以下圖所示,client和server端進行opp傳輸時的具體協議棧狀況

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

源碼真算是博大精深,在實現opp時沒有多餘的廢話,每一個功能都缺一不可,在功能設計上讓人佩服。

 

至於實現過程當中源碼是否是有不恰當的地方,就須要好好研究一下了。接下來分析各個功能項的具體實現了。

 

選擇接收方

如上所示,選擇接收方也就是選擇目標方server。那麼功能是如何實現的呢?

 

在選擇藍牙分享時會發送action_send(分享單個對象)或者是

action_send_multiple(分享多個對象),負責監聽處理該廣播的爲BluetoothOppLauncherActivity。那麼接下來看看接收到send以後都作了哪些處理。

 

這裏就不貼代碼了,直接總結出他所做的事情

  1. 判斷藍牙是否可用

  2. 保存要分享的文件

  3. 開啓藍牙設備選擇界面

 

接下來就是各個擊破了

 

第一:判斷藍牙是否可用isBluetoothAllowed():注意此處判斷藍牙是否可用並不是是指判斷isEnable而是判斷canEnable。那麼藍牙是否可用和什麼相關呢?代碼以下

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

首先確認,該方法返回爲true時表示此時藍牙可用。能夠看到藍牙是否可用有三個判斷條件

 

  1. isAirplaneModeOn:是否處於飛行模式

  2. isAirplaneSensitive:從字面上看是說是否對飛行模式敏感,也就是說在a條件知足即飛行模式開啓時藍牙是否被disable

  3. isAirplaneToggleable:在a和b條件都知足的狀況下即飛行模式開啓且在開啓飛行模式時bluetooth會被disable的狀況下,是否支持用戶從新開啓reenable藍牙。

 

第二:保存要分享的對象saveSendingFileInfo:注意此時要分享的對象並非保存在db,而是會被保存至BluetoothOppManager和BluetoothOppUtility供確認目標方後開啓分享時使用。

 

saveSendingFileInfo是BluetoothOppManager的實例方法,以保存單個分享對象爲例進行說明

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

在保存時有這麼幾個參數

  1. mMultipleFlag:表示要分享的是不是多個文件,該參數值由所監聽到的send的action決定,若是是send_multiple則爲true

  2. mMimeTypeOfSendingFile:表示要分享的類型,好比image/png,從監聽到action的intent中獲取

  3. mUriOfSendingFile:要分享的文件的uri,好比file:///storage/emulated/0/DCIM/Camera/IMG_20180607_142700R.jpg@3348f35,該參數也是在監聽到send的action時從intent中獲取的。

  4. mIsHandoverInitiated:這次文件傳輸是不是由NFC發起的,普及一下,nfc傳輸文件實際上走的也是藍牙opp傳輸

 

而後就是具體的保存了,保存分爲兩大部分:

一部分是storeAppicationData(),將file經過sharedPreference方法保存到文件OPPMGR.xml中,文件所保存的信息以下所示:

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

其中sendingflag表示程序是否在開啓藍牙的過程當中爲。其餘參數解釋見上文。

 

而關於保存的另外一部分操做就是在BluetoothOppUtility的putSendFileInfo方法了,在putSendFileInfo方法中其實就是保存早BluetoothOppUtility私有的map集合sSendFileMap,key值爲uri,value爲generateFileInfo所生成的BluetoothOppSendFileInfo對象。

 

每一個BluetoothOppSendFileInfo對象包含如下6個字段

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

其中每一個字段的取值示例以下所示

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

該對象就是藍牙opp傳輸進行時所操做的對象了。也就是說在傳輸過程當中對於文件傳輸的狀態、進度、輸入流都跟該對象相關。

 

至此,準備工做作完了,接下來就是去打開藍牙設備選擇界面選擇設備了

 

第三:啓動藍牙設備選擇界面。經過在BluetoothOppLauncherActivity中調用launchDevicePicker方法來啓動藍牙選擇界面加載可用設備。

 

固然在加載該界面以前須要保證藍牙處於開啓狀態,因此在這裏作了個藍牙isEnable的判斷,若是藍牙未開啓,則會去先開啓藍牙而後再去啓動藍牙選擇界面。

 

對於藍牙選擇界面是依靠com.android.example.notificationlistener.LAUNCH來啓動DevicePickerActivity的。

 

關於界面的UI加載不在贅述,當用戶選擇設備後會發送android.bluetooth.devicepicker.action.DEVICE_SELECTED廣播,交由BluetoothOppReceiver處理選擇設備以後的事情。

 

好了,目前爲止第一部分也是最簡單的部分選擇目標設備的分析完成了,那麼整個傳輸過程徹底就能夠到此爲止了。

 

按照以前的總結再加上對代碼的分析能夠看出選擇目標方這一過程其實作了四件事

 

第一:檢測藍牙的可用性即canEnable,這是必要的,若是目前處於飛行模式而且飛行模式下會關閉藍牙且不支持用戶從新enable藍牙。

 

第二:將一些file相關的type和length以及uri的信息保存到BluetoothOppManager中,而這個信息就是在接下來保存到db時作鋪墊,須要將manager中的信息存入db

 

第三:固然每一個file都會對應一個BluetoothSendFileInfo對象,該對象會在文件開始傳輸時發揮做用,記錄file的狀態並提供輸入流。

 

第四:啓動藍牙選擇界面,供用戶選擇目標設備。

若是對某部分有疑問請往上翻...

 

存入btopp.db

在接收到設備選擇後的廣播以後調用BluetoothOppManager的實例方法startTransfer開始傳輸,本文會將傳輸分紅兩大部分解釋,一是將文件存入DB,另外一個就是開啓傳輸。

 

本部分講述將文件存入DB,其實存入db本來是沒什麼好說的也不是文章的重點,可是在藍牙OPP過程當中存入DB之時作了一件決定性的操做,因此就看一下db的存入來避免錯太重點。

 

好了接下來看一下具體代碼,以下

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

能夠看到,在該方法幾乎算是隻作了一件事,那就是開啓了插入db的線程InsertShareInfoThread。

 

直接奔到線程的run方法中,代碼邏輯很簡單,就再也不貼出,run方法中處理以下:若是是插入單個文件則調用insertSingleShared,若是是插入多個文件則調用insertMultipleShare。

 

本次分析以插入單個文件爲例。因此直接看插入單個文件的代碼。

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

根據這幾行代碼,你要確認3個信息

 

  1. 所出入的對象values所包含的信息有:能夠看到會包含file的uri、file的文件類型、以及目標設備。固然若是是經過nfc啓動的文件傳輸須要傳入一個確認方式即爲nfc確認。是否是以爲少?還記得文章第一部分說的文件保存嗎?保存方式是以map的形式,key爲uri,value爲BluetoothOppSendFileInfo對象,因此一旦有了uri,那麼對應的value也就不難獲取了

  2. 所插入的數據庫爲:這個能夠從contentUri中獲得,爲btopp.db

  3. 執行插入操做的contentprovider爲:根據contenturi一樣也能夠得出執行insert操做的爲BluetoothOppProvider(根據contenturi推出provider相信你們都很明白,再也不贅述)

 

看到這兒有沒有一臉懵的感受...說好的文件傳輸呢?怎麼insert到DB就完事兒了?

 

難道你覺得insert就是單純的將數據寫入DB?那你就太單純了...目前到這兒只有insert的實現不明,因此直接奔到BluetoothOppProvider的insert方法中看看究竟是怎麼作的...

 

insert方法基本上是分爲兩部分,一部分實現insert一條數據到db,而另外一部分就是開啓BluetoothOppService。

 

第一,insert數據

 

insert數據其實流程都同樣:首先構造values對象,而後insert到db,代碼就不貼了,想看的能夠經過androidxref看谷歌源碼。

 

這裏貼一下btopp.db數據庫示例,該數據庫通常位於data/data/com.android.bluetooth/databases/目錄下,root以後能夠導出來經過sqlite工具查看,先來看一下數據庫都有哪些字段

 

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

共包含14個字段,先來看一個db數據示例

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

其中id屬於插入db的序列號,uri是文章第一部分所保存的uri,hint文件名,direction表示是發送文件仍是接受文件,destination表示目標方設備地址,comfirm表示文件傳輸時的用戶確認方式,status表示文件的狀態等等。

 

stauts是個很重要的字段,在文件傳輸過程當中就是經過更新status狀態來讓其餘人能夠經過查詢db來查看當前的文件狀態:是否正在等待發送,是否正在發送,是否發送結束等等,也是更新notification的依據。

 

status的更新貫穿整個opp過程,會把status和notification放在一塊兒進行分析。current_bytes和total_bytes用於更新文件傳輸過程當中的進度條。

 

timeStamp字段在這裏不起眼很容易被忽略掉,在後續的開啓transfer時須要建立batch,而至因而新建batch仍是插入已有的batch就是由該timestamp決定的。

 

若是分享單個文件那麼毫無疑問timestamp是在BluetoothOppProvider的insert賦值的,但若是是分享多個文件那麼timestamp實在BluetoothOppManager的插入db以前完成的。

 

也就是說一次分享多個文件時這些文件擁有同一個timestamp值那麼在後續的transfer中也就會擁有同一個batch

 

第二,開啓BluetoothOppService

insert裏經過startService來開啓服務。在開啓藍牙時就會去檢測是否支持Opp並開啓服務,那爲何這裏又要開啓呢?並且仍是連續兩次?,以下

 

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

 

明明在藍牙開啓以後service就會啓動了,那爲何在真正insert到db以前會啓動一次service,insert以後再次start一次,什麼目的?

 

首先明確一點調用startService啓動服務時若是service未啓動則走create-->start流程,若是service已經存在則會去觸發service的start,而在opp的service的start方法會根據db數據庫的更新來開啓傳輸。

 

因此此處猜想之因此啓動兩次一是避免service已經被殺死,二是及時根據db的更新來更新發送。

 

基本上往db存數據就到這兒了,主要就是存入db,順便重啓service保證oppservice不死。接下來就是看看當把數據插入到db以後是如何影響service來開啓transfer的

https://mp.weixin.qq.com/s/P0BgNKxQxlCrP8Y1Zm3AIw

相關文章
相關標籤/搜索