Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android藍牙完全學習手冊

Android藍牙完全學習手冊

編輯:關於Android編程

1.前言

市面上關於Android的技術書籍很多,幾乎每本書也都會涉及到藍牙開發,但均是上層應用級別的,而且篇幅也普遍短小。對於手機行業的開發者,要進行藍牙模塊的維護,就必須從Android系統底層,至少框架層開始,了解藍牙的結構和代碼實現原理。這方面的文檔、網上的各個論壇的相關資料卻少之又少。分析原因,大概因為雖然藍牙協議是完整的,但是並沒有具體的實現。藍牙芯片公司只負責提供最底層的API,與上層的適配和其他元件的兼容,需要各個廠家自己去實現,因此並未出現適用非常廣泛的標准API供各個領域的公司使用。而實現了自己適配的公司,出於技術的保護又很少公開相關技術代碼或者資料。

作為Android手機系統應用維護工程師,初學藍牙模塊也深感資料匮乏。閱讀MTK的PPT,總是過分簡略不夠深入。閱讀代碼當然是好辦法,但是沒有指導,容易因理解不到位而出錯和繞彎路,難免費時費力。基於這種現狀,我將自己的藍牙學習、代碼分析總結出來,形成此文,一來梳理自身的藍牙技術知識,而來貢獻力量將本Team的知識積累建設得更加到位。希望後來者有文檔可依,學習上手能更加便捷。由於作者水平有限,文字和理解的勘誤難免,如果能互相指教提高,便是最大的榮幸了!

閱讀本文後面詳細分析,推薦的方法是打開一套工程源碼,一邊利用本文粘貼出來的代碼和對應的說明文字,一邊利用工程源碼,對照閱讀。這樣遇到跳轉的時候可以直接操作,不至於跟丟致使茫然無從。文字總無法一一俱到,遇到部分沒有講到的但或許對於特定讀者卻有疑惑的地方,請使用手邊的源碼認真分析。這樣在作者看來學習提高是比較快的。

2.技術起源和名稱來歷

藍牙(Bluetooth)是一種短距離的無線通信技術標准。它最初由瑞典的愛立信公司創制,技術始於公司的1994方案,後來由藍牙技術聯盟訂定技術標准,1999年7月26日正式公布1.0版。藍牙名字來源於10世紀丹麥國王Harald Blatand,英文名字是Harold Bluetooth。無線行業協會組織人員進行討論,有人認為用Blatand國王的名字命名這種無線技術再好不過了,因為Blatand國王將挪威、瑞典和丹麥統一起來,就如同這項技術將統一無線通信領域一樣。因此,藍牙的名字就這樣定了下來。它的標志由H和B兩個字母合成,如下圖:
藍牙標志示意圖
圖1 藍牙標志示意圖<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMSBpZD0="3技術規格">3.技術規格

藍牙采用了分散式網絡結構以及快跳頻和短包技術,支持點對點及點對多點的通信。

3.1射頻與基帶部分

Bluetooth設備工作在全球通用的2.4GHz的ISM(Industrial,Science and Medicine)頻段,在北美和歐洲為2400~2483.5MHz,使用79個頻道,載頻為2402+kMHz(k=0,1…,22)。無論是79個頻道還是23個頻道,頻道間隔均為1MHz,采用時分雙工(TDD,TimeDivision Duplex)方式。調制方式為BT=0.5的GFSK,調制指數為0.28~0.35,最大發射功率分為三個等級,分別是:100mW(20dBm),2.5mW(4dBm)和1mW(0dBm),在4~20dBm范圍內要求采用功率控制,因此,Bluetooth設備間的有效通信距離大約為10~100米。

Bluetooth的基帶符號速率為1Mb/s,采用數據包的形式按時隙傳送,每時隙長0.625ūs,不排除將來采用更高的符號速率。Bluetooth系統支持實時的同步面向連接傳輸和非實時的異步面向非連接傳輸,分別成為SCO鏈路(Synchronous Ccnnection-Oriented Link)和ACL鏈路(Asynchronous Connection-Less Link),前者只要傳送語音等實時性強的信息,在規定的時隙傳輸,後者則以數據為主,可在任意時隙傳輸。但當ACL傳輸占用SCO的預留時隙,一旦系統需要SCO傳輸,ACL則自動讓出這些時隙以保證SCO的實時性。數據包被分成3大類:鏈路控制包、SCO包和ACL包。已定義了4鐘鏈路控制數據包,後兩者最多可分別定義12種,目前已定義了4種和7種,即共定義了15種。大多數數據包只占用1個時隙,但有些包占用3個或5個時隙。

Bluetooth支持64kb/s的實時語音傳輸和各種速率的數據傳輸,語音編碼采用對數PCM或連續可變斜率增量調制(CVSD,Continuous Variable Slope Delta Modulation)。語音和數據可單獨或者同時傳輸。當僅傳輸語音時,Bluetooth設備最多可同時支持3路全雙工的語音通信;當語音和數據同時傳輸或僅傳輸數據時,Bluetooth設備支持433.9kb/s的對稱全雙工通信或723.2/57.6kb/s的非對稱雙工通信,另外,采用CRC(Cyclic Redundancy Check)、FEC(Forward Error Correction)及ARQ(Automatic Repeat Request),保證了通信的可靠性。

跳頻是Bluetooth使用的關鍵技術之一,對應於單時隙包,Bluetooth的跳頻速率為1600跳每秒;對應於多時隙包,跳頻速率有所降低;但在建鏈時(包括尋呼和查詢)則提高為3200跳每秒。使用這樣高的跳頻速率,Bluetooth系統具有足夠高的抗干擾能力。

跳頻序列受控於Bluetooth 48-bit設備地址碼(BD——ADDR)中的28-bit和28-bit的時鐘,采用以多級碟形運算為核心的映射方案。該跳頻方案和其他方案相比,具有硬件設備簡單、性能優越、便於79/23頻段兩種系統的兼容以及各種狀態的跳頻序列使用統一的電路來實現等特點。

3.2組網技術

Bluetooth根據網絡的概念提供點對點和點對多點的無線鏈接,在任意一個有效通信范圍內,所有設備的地位都是平等的。首先提出通信要求的設備稱為主設備(Master),被動進行通信的設備稱為從設備(Slave)。利用TDMA,一個Master最多可同時與7個Slave進行通信並和多個Slave(最多可超過200個)保持同步但不通信。一個Master和一個以上的Slave構成的網絡稱為Bluetooth的主從網絡(Piconet)。若兩個以上的Piconet之間存在著設備間的通信,則構成了Bluetooth的分散網絡(Scatternet)。基於TDMA原理和Bluetooth設備的平等性,任意Bluetooth設備在Piconet和Scatternet中,既可作Master,又可作Slave,還可同時既是Master又是Slave。因此,在Bluetooth中沒有基站的概念。另外,所有設備都是可移動的。

Bluetooth的基本出發點是可使其設備能夠在全球范圍內應用於任意的小范圍通信。任一Bluetooth設備,都可根據IEEE 802標准得到一個唯一的48-bit的BD_ADDR,它是一個公開的地址碼,可以通過人工或自動進行查詢。在BD_ADDR基礎上,使用一些性能良好的算法可獲得各種保密和安全碼,從而保證了設備識別碼(ID,Identification)在全球的唯一性,以及通信過程中設備的鑒權和通信的安全保密。

4.藍牙規范

藍牙規范(Specification of the Bluetooth System)就是藍牙無線通信協議標准,它規定了藍牙應用產品應遵循的標准和需要達到的要求,由SIG頒布。

藍牙規范包括核心協議(Core)與應用框架(Profiles)兩個文件。協議規范部分定義了藍牙的各層通信協議,應用框架指出了如何采用這些協議實現具體的應用產品。藍牙協議規范遵循開放系統互連參考模型(Open System Interconnetion/Referenced Model, OSI/RM),從低到高地定義了藍牙協議堆棧的各個層次。

5.協議堆棧

5.1概述

藍牙棧的目的是什麼呢?棧是控制藍牙設備的軟件(和固件)。藍牙協議堆棧低層為各類應用所通用,高層則視具體應用而有所不同,大體上分為計算機背景和非計算機背景兩種方式,前者通過主機控制接口(HCI,Host Controller Interface)實現高、低層的聯接,後者則不需用HCI。層次結構使其設備具有最大可能的通用性和靈活性。根據通信協議,各種Bluetooth設備無論在任何地方,都可以通過人工或自動查詢來發現其他Bluetooth設備,從而構成Piconet或Scatternet,實現系統提供的各種功能,使用起來十分方便。

5.2協議體系結構

藍牙協議堆棧按照功能分為4層:

核心協議層(BaseBand、LMP、L2AP、SDP)
線纜替換協議層(RFCOMM)
電話控制協議層(TCS-BIN、AT命令集)
選用協議層(PPP、TCP、IP、UDP、OBEX、IrMC、WAP、WAE)

這裡寫圖片描述
除上述協議層外,規范還定義了主機控制器接口(HCI),它為基帶控制器、連接管理器、硬件狀態和控制寄存器提供命令接口。在圖1中,HCI位於L2CAP的下層,但HCI也可位於L2CAP上層。

藍牙核心協議由SIG制定的藍牙專用協議組成。絕大部分藍牙設備都需要核心協議(加上無線部分),而其他協議則根據應用的需要而定。總之,電纜替代協議、電話控制協議和被采用的協議在核心協議基礎上構成了面向應用的協議。

5.2.1核心協議

核心協議包括基帶、鏈路管理、邏輯鏈路控制和適應協議四部分。

(1)基帶(BaseBand)協議

描述了完成底層鏈路建立維護和執行基帶協議的鏈路控制器的規范;基帶和鏈路控制層確保微微網內各藍牙設備單元之間由射頻構成的物理連接。藍牙的射頻系統是一個跳頻系統,其任一分組在指定時隙、指定頻率上發送。它使用查詢和分頁進程同步不同設備間的發送頻率和時鐘,為基帶數據分組提供了兩種物理連接方式,即面向連接(SCO)和無連接(ACL),而且,在同一射頻上可實現多路數據傳送。ACL適用於數據分組,SCO適用於話音以及話音與數據的組合,所有的話音和數據分組都附有不同級別的前向糾錯(FEC)或循環冗余校驗(CRC),而且可進行加密。此外,對於不同數據類型(包括連接管理信息和控制信息)都分配一個特殊通道。

可使用各種用戶模式在藍牙設備間傳送話音,面向連接的話音分組只需經過基帶傳輸,而不到達L2CAP。話音模式在藍牙系統內相對簡單,只需開通話音連接就可傳送話音。

(2)鏈路管理協議(Link Manager Protocol)

負責藍牙組件間連接的建立,定義了鏈路的建立與控制的規范,在接收層信號由解釋及過濾;該協議負責各藍牙設備間連接的建立。它通過連接的發起、交換、核實,進行身份認證和加密,通過協商確定基帶數據分組大小。它還控制無線設備的電源模式和工作周期,以及微微網內設備單元的連接狀態。

(3)邏輯鏈路控制與適配協議(Logical Link Control and Adaptation Protocol)

位於基帶(BaseBand)協議層上,屬於數據鏈路層,是一個為高層傳輸和應用層協議屏蔽基帶協議的適配協議,支持高層協議復用、數據包分段重組、QoS信息服務並獲得相應的信息;該協議是基帶的上層協議,可以認為它與LMP並行工作,它們的區別在於,當業務數據不經過LMP時,L2CAP為上層提供服務。L2CAP向上層提供面向連接的和無連接的數據服務,它采用了多路技術、分割和重組技術、群提取技術。L2CAP允許高層協議以64k字節長度收發數據分組。雖然基帶協議提供了SCO和ACL兩種連接類型,但L2CAP只支持ACL。

(4) 服務發現協議(SDP)

在藍牙技術框架中起著至關緊要的作用,它是所有用戶模式的基礎。使用SDP可以查詢到設備信息和服務類型,從而在藍牙設備間建立相應的連接。

5.2.2電纜替代協議

電纜替代協議(RFCOMM)是ETSITS07.10的子集,提供L2CAP之上的串口防真。它在藍牙基帶協議上仿真RS-232控制和數據信號,為使用串行線傳送機制的上層協議(如OBEX)提供服務。

5.2.3電話控制協議層

(1) 二元電話控制協議(TCS-Binary或TCSBIN)是面向比特的協議,它定義了藍牙設備間建立語音和數據呼叫的控制信令,定義了處理藍牙TCS設備群的移動管理進程。基於ITU TQ.931建立的TCSBinary被指定為藍牙的二元電話控制協議規范。

(2)AT命令集電話控制協議,SIG定義了控制多用戶模式下移動電話和調制解調器的AT命令集,該AT命令集基於ITU TV.250建議和GSM07.07,它還可以用於傳真業務。

5.2.4選用協議

選用協議都是已有的其他組織的協議。

6.應用模型

Profiles部分規定不同藍牙應用所需的協議,整個Profiles部分涉及了從耳機到局域網接入點等多種應用。對於每一個應用,應用模型給出其協議棧結構,並針對每一層具體規定一些必須實現的內容,諸如消息序列、功能集以及空中接口。依據應用模型實現應用,有利於不同廠家設備之間的互通性。

例如應用模型的第一個是一般接入應用模型,這個模型定義了用於發現藍牙設備的過程、用於鏈接管理的過程、使用不同安全級別的過程,並描述了用戶界面層次上參數的表示格式。模型首先給出了協議棧結構,而後分別描述了藍牙地址、藍牙設備類型、藍牙PIN碼在用戶層面的表示格式,同時就認證等安全方面的內容給出流程圖,最後描述設備發現及鏈路維護的消息序列,依照這個模型實現的設備互相可以發現對方並根據用戶需求建立鏈路。

Bluetooth的四種應用模式

(1)通用訪問應用(GAP)模式:定義了兩個藍牙單元如何互發現和建立連接,它是用來處理連接設備之間的相互發現和建立連接的。它保證兩個藍牙設備,不管是哪一家廠商的產品,都能夠發現設備支持何種應用,並能夠交換信息。

(2)服務發現應用(SDAP)模式:定義了發現注冊在其他藍牙設備中的服務的過程,並且可以獲得與這些服務相關的信息。

(3)串口應用(SPP)模式:定義了在兩個藍牙設備間基於RFCOMM建立虛擬的串口連接的過程和要求。

(4)通用對象交換應用(GOEP)模式:定義了處理對象交換的協議和步驟,文件傳輸應用和同步應用都是基於這一應用的,筆記本電腦、PDA、移動電話是這一應用模式的典型應用。

7.藍牙版本

根據不同的藍牙版本,傳輸速度會差很多。例如藍牙3.0的傳輸速度為3Mb/s,而藍牙4.0技術從理論上可以達到60Mb/s。到目前為止,藍牙最新版本為4.0,它的版本歷史如下表所示:

版本 規范發布日期 增強功能 0.7 1998年10月19日 Baseband、LMP 0.8 1999年1月21日 HCI、L2CAP、RFCOMM 0.9 1999年4月30日 OBEX與IrDA的互通性 1.0Draft 1999年7月5日 SDP、TCS 1.0A 1999年7月26日 / 1.0B 2000年10月1日 WAP應用上更具互通性 1.1 2001年2月22日 IEEE 802.15.1 1.2 2003年11月5日 列入IEEE 802.15.1a 2.0+EDR 2004年11月9日 EDR傳輸率提升至2-3Mbps 2.1+EDR 2007年7月26日 簡易安全配對、暫停與繼續加密、Sniff省電 3.0+HS 2009年4月21日 交替射頻技術、取消了UMB的應用 4.0+HS 2010年6月30日 傳統藍牙技術、高速藍牙和新的藍牙低功耗技術

表X 藍牙規范版本歷史

8.Android對藍牙的支持狀況

8.1支持版本

藍牙技術作為目前比較常用的無線通信技術,早已成為手機的標配之一,可以實現藍牙手機互連、傳輸文件、藍牙耳機、藍牙鍵盤等等功能。Android1.5對藍牙的支持非常不完善,只支持像藍牙耳機一樣的設備,並不支持藍牙數據傳輸等高級特性。在Android2.0終於加入了完善的藍牙支持。

8.2Android藍牙應用開發

8.2.1應用開發基本功能

在Android開發者網站http://developer.android.com/guide/topics/connectivity/bluetooth.html,介紹Android平台包括支持藍牙的協議棧,允許設備之間通過無線交換數據。應用框架層提供了實現藍牙功能的API,這些API使得應用可以無線連接到其他藍牙設備,提供點對點和多點無線互連功能。通過藍牙API,Android應用能夠實現下面的功能:

? 掃描其他藍牙設備
? 檢索配對的藍牙設備
? 建立RFCOMM連接
? 與其他設備傳遞數據
? 管理多路連接

8.2.2 應用開發API

使用Android API進行藍牙通信,需要完成四個主要工作:建立藍牙,尋找附近配對的或者可以使用的藍牙設備,連接設備,在設備間傳遞數據。
所有的API都在android.bluetooth包中,在建立藍牙連接中所需要使用的類和接口如下:

BluetoothAdapter:代表本地藍牙適配器(Bluetooth Radio)。該BluetoothAdapter是入口點的所有藍牙互動。利用這一點,你會發現其他藍牙設備,查詢保稅(配對)的設備列表,使用已知的MAC地址實例化一個BluetoothDevice類,並創建一個BluetoothServerSocket來監聽來自其他設備的通信。

BluetoothDevice:代表一個遠程藍牙設備。使用它可以請求與遠程設備的連接,通過一個BluetoothSocket或者關於設備的名稱,地址,類別和鍵合狀態的查詢信息。

BluetoothSocket:代表了一個藍牙套接字(類似於TCP套接字)的接口。這是一個連接點,允許應用程序通過InputStream和OutputStream與其他藍牙設備交換數據。

BluetoothServerSocket:代表一個偵聽傳入的請求的開放服務器套接字(類似於TCP的ServerSocket)。為了連接兩個Android設備,一台設備必須打開這個類服務器套接字。一個遠程藍牙設備發送連接請求到該設備,當連接被接受後BluetoothServerSocket會返回一個連接的BluetoothSocket。

BluetoothClass:描述藍牙設備的一般特點和能力。這是一組只讀的定義了設備主要和次要的類以及服務的屬性。然而,這並不能可靠地描述所有的藍牙協議和設備支持的服務,但作為一個提示的設備類型是有用的。

BluetoothProfile:一個表示藍牙協議的接口。藍牙協議是一個設備之間基於藍牙通信的無線接口規范。比如免提協議(Hands-Free)。
BluetoothHandset:對藍牙耳機可被手機使用提供了支持。包括藍牙耳機(Bluetooth Headset)協議和免提(Hands-Free V1.5)協議。

BluetoothA2dp:定義了高品質的音頻怎樣通過藍牙連接以流的方式從一個設備傳輸到另一個設備。“A2DP”代表高級音頻傳輸模式(Advanced Audio Distribution Profile)。

BluetoothHealth:代表控制藍牙服務的健康設備協議代理。

BluetoothHealthCallback:用來實現BluetoothHealth回調的抽象類。你必須繼承這個類並實現回調方法來接收有關更改應用程序的注冊狀態和藍牙信道狀態的更新。

BluetoothHealthAppConfiguration:表示藍牙健康第三方應用程序注冊與遠程藍牙健康設備進行通信的應用程序配置。

BluetoothProfile. ServiceListener:當BluetoothProfile IPC客戶端已經連接到或從服務(一個運行特定協議的內部服務)斷開連接時,通知該客戶端的接口。

至於具體如何實現各種基本功能,本文暫不贅述。

8.3Android藍牙源碼開發

8.3.1Android藍牙架構

在Android源碼開發網站https://source.android.com/devices/bluetooth.html,介紹藍牙如下:

Android提供了一個默認的藍牙協議棧:BlueDroid,它被分為兩層:藍牙嵌入式系統(BTE),它實現了核心的藍牙功能;和藍牙應用層(BTA),它與藍牙的應用框架進行通信。一個藍牙系統服務通過JNI與藍牙協議棧進行通信,通過Binder IPC與應用程序進行通信。藍牙系統服務向開發者提供了多種訪問藍牙協議的途徑。下圖顯示了藍牙協議棧的整體結構:
這裡寫圖片描述
Application framework
在應用程序框架層是應用程序的代碼,它利用android.bluetooth API與藍牙硬件交互。在內部,這個代碼通過Binder IPC機制調用藍牙進程。

Bluetooth system service
藍牙系統服務,位於packages/apps/Bluetooth,被打包為一個Android應用程序,並在Android框架層實現了藍牙服務和協議。這個應用程序通過JNI調用到HAL層。

JNI
與android.bluetooth相關的JNI代碼位於packages/apps/Bluetooth/jni。JNI代碼調用到HAL層,並且當某些藍牙操作發生,比如當設備被發現的時候,接收來自HAL層的回調。

HAL
硬件抽象層定義了android.bluetooth API和藍牙進程需要調用到,而且必須實現以確保藍牙硬件能正確運行的標准接口。對於藍牙HAL的頭文件位於hardware/libhardware/include/hardware/bluetooth.h和hardware/libhardware/include/hardware/bt_*.h文件中。

Bluetooth stack
默認的藍牙協議棧是提供給開發者的,位於external/bluetooth/bluedroid。該協議棧實現了通用的藍牙HAL,當然,通過擴展和更改配置也可以自定義它。

Vendor extensions
用來添加用於跟蹤的自定義擴展和HCI層,你可以創建一個libbt-vendor模塊,並指定這些組件。

8.3.2實現HAL

藍牙HAL位於hardware/libhardware/include/hardware/,它包含以下頭文件:

bluetooth.h 包含設備上的藍牙硬件HAL
bt_av.h 包含高級音頻協議HAL
bt_hf.h 包含免提協議HAL
bt_hh.h 包含HID主機協議HAL
bt_hl.h 包含健康協議HAL
bt_pan.h 包含pan協議HAL
bt_sock.h 包含套接字協議HAL

請記住,你的藍牙實現並不限制在HAL暴露的功能和協議。你可以找到位於external/bluetooth/bluedroid目錄的默認實現,它實現了默認的HAL,也實現了額外的和自定義的功能。

8.3.3定制BlueDroid協議棧

如果你在使用默認的BlueDroid協議棧,但是想做一小部分定制,那麼可以通過以下方式:

(1)定制藍牙協議 - 如果你想補充的是, Android的HAL接口所沒有提供的藍牙協議,你必須提供一個SDK插件下載,使協議可以被程序開發人員使用,使藍牙系統進程應用程序可以使用該API(packages/apps/Bluetooth),並把它們添加到BlueDroid堆棧(external/bluetooth/bluedroid)。

(2) 定制廠商擴展和配置更改 - 你可以通過創建一個libbt-vendor模塊來添加東西,例如額外的AT命令或特定於設備的配置更改。可以去vendor/broadcom/libbt-vendor目錄查看示例。

(3) 主機控制器接口(HCI) - 你可以通過創建一個libbt-HCI模塊來提供自己的人機交互,它主要用於調試跟蹤。可以去external/bluetooth/hci目錄查看示例。

9.Android手機藍牙代碼剖析

下面,將以MTK6572平台的手機進行說明。

9.1手機藍牙的外在功能點

(可畫一幅功能圖)

藍牙開啟與關閉
Rename phone
Visibility timeout
show received files
藍牙配對
藍牙傳輸文件

9.2藍牙源代碼位置

藍牙核心功能的代碼主要位於以下路徑:

alps/packages/apps/Settings/src/com.android.settings.bluetoothangel
alps/frameworks/base/core/java/android/bluetooth
alps/mediatek/packages/apps/Bluetooth
alps/mediatek/frameworks-ext/base/core/java/android/bluetooth
alps/mediatek/frameworks-ext/base/core/java/android/server
alps/mediatek/frameworks-ext/base/core/jni/

核心代碼結構如下表所示:

Module Sourcefile   Settings alps/packages/apps/Settings/src/com/android/settings/bluetoothangel/ All files Phone alps/packages/apps/Phone/src/com/mediatek/blueangel/ All files Media Play alps/packages/apps/Music/src/com/mediatek/bluetooth/avrcp/ All files Other profile(opp、avrcp) alps/mediatek/packages/apps/Bluetooth/profiles/ All files

藍牙核心代碼結構表
表X 藍牙核心代碼結構表

9.3藍牙開啟與關閉

藍牙開關
藍牙開關
藍牙開關
藍牙開關
圖X 藍牙開關

手機藍牙默認是關閉的,要想打開藍牙,可以通過上圖所示的四個地方:

(1)手機設置裡的藍牙選項,後面有個開關,點擊可以進行打開/關閉操作;
(2)點進去藍牙設置界面,右上角也有個開關,點擊可以進行打開/關閉操作;
(3)在手機下拉快捷設置界面,長按【BLUETOOTH】按鈕,可以進入藍牙設置界面,後同第(2)條;
(4)在進行文件的分享操作時,可以選擇通過藍牙進行傳送,則會彈出是否打開藍牙的對話框。
藍牙打開/關閉時序圖
圖X 藍牙打開/關閉時序圖

在Settings.java中,如果檢測到Bluetooth service不可用,則不會顯示藍牙設置選項。具體代碼如下:

      } else if (id == R.id.bluetooth_settings) {
                // Remove Bluetooth Settings if Bluetooth service is not available.
                if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
                    target.remove(i);
                }

簡單講一下Settings界面的實現原理,它繼承於PreferenceActivity,每一個設置項被定義為一個Header。但是由於有特殊的項,比如Wi-Fi、Bluetooth右側有一個開關,Quick start右側有一個勾選框,以及若干項歸為一類,不同分類之間有間隔,因此,這些項用如下四種標簽來區分:

        static final int HEADER_TYPE_CATEGORY = 0;
        static final int HEADER_TYPE_NORMAL = 1;
        static final int HEADER_TYPE_SWITCH = 2;
        static final int HEADER_TYPE_CHECK = 3;

Wi-Fi和Bluetooth右側開關為Switch組件,實際上是一個CompoundButton,分別使用WifiEnabler和BluetoothEnabler管理;Quick start右側的勾選框為一個CheckBox。Header的視圖,則由Settings.java的內部類HeaderAdapter.java來管理,具體每一個Header的各個組件,在HeaderAdapter.java的內部類HeaderViewHolder.class中進行定義,代碼如下:

        private static class HeaderViewHolder {
            ImageView icon;
            TextView title;
            TextView summary;
            Switch switch_;
            //FR441047-qixing.chen-001 begin
            CheckBox check;
            //FR441047-qixing.chen-001 end
        }

這樣,在getView()方法中,通過getHeaderType()方法獲得Header類型,然後使用LayoutInflater裝載不同Header視圖,即形成我們最終看到的Settings主界面。
在BluetoothEnabler.java中,實現了setSwitch()方法,該方法會讀取當前藍牙的當前開關狀態bluetoothState,並根據這個狀態來設置Switch組件的選中狀態(setChecked)和是否可選(setEnabled)狀態。當Switch的選中狀態有改變的時候,將調用onCheckedChanged()方法,然後通過setBluetoothEnabled()繼續向下調用。代碼如下:

       /// M: if receive bt status changed broadcast, do not need enable/disable bt.
        if (mLocalAdapter != null && !mUpdateStatusOnly) {
            mLocalAdapter.setBluetoothEnabled(isChecked);
        }

這裡有一個mUpdateStatusOnly的變量十分重要,它是BluetoothEnabler.java的全局變量,用來表示是否只更新Switch的狀態。設置它的目的是我們可能通過其他途徑(藍牙開關的第四幅圖)打開或者關閉藍牙,而這種情況下必須同步更改Switch的狀態,但此時是不需要再次打開/關閉藍牙的操作的。

LoacalBluetoothAdapter.java中實現setBluetoothEnabled()方法。這個方法重要之處在於,它清晰地提供了打開/關閉代碼邏輯上的分支,看下面代碼很容易理解:

    public void setBluetoothEnabled(boolean enabled) {
        boolean success = enabled
                ? mAdapter.enable()
                : mAdapter.disable();

        if (success) {
            setBluetoothStateInt(enabled
                ? BluetoothAdapter.STATE_TURNING_ON
                : BluetoothAdapter.STATE_TURNING_OFF);
        } else {
            if (Utils.V) {
                Log.v(TAG, "setBluetoothEnabled call, manager didn't return " +
                        "success for enabled: " + enabled);
            }

            syncBluetoothState();
        }
    }

如果傳遞過來的參數enabled是true,則表示打開藍牙,就調用mAdapter.enable();如果enabled是false,則表示關閉藍牙,就調用mAdapter.disable()。流程圖只畫出了打開藍牙的流程,而關閉藍牙的流程從這之後,只要將enable環衛disable就基本可以了。

接下來進入BluetoothAdapter.java,其中分別實現了enable()和disable()方法,兩個方法最終分別調用了BluetoothManagerService.java中的enable()和disable()方法。

在BluetoothManagerService.java中,enable()和disable()方法分別調用sendEnableMsg(false)和sendDisableMsg(),而這兩個本地方法有異曲同工之妙,就是最終都發送了消息,由BluetoothHandler來處理。代碼如下:

    private void sendDisableMsg() {
        mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISABLE));
    }

    private void sendEnableMsg(boolean quietMode) {
        mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE,
                             quietMode ? 1 : 0, 0));
    }

BluetoothHandler是BluetoothManagerService.java的內部類,handleMessage()方法中即實現了對藍牙各個不同狀態的消息處理。找到對應的分支,接下來調用本地方法handleEnable()和handleDisable()兩個方法。

在handleEnable()中,通過mBluetooth.enable()或mBluetooth.enableNoAutoConnect()繼續向下調用;在handleDisable()中,通過mBluetooth.disable(false)繼續向下調用。

上面三個方法都調用自BluetoothService.java,分別實現了enable()和disable()的帶參數重載。並在其中分別發送了消息進行打開關閉的操作,代碼如下:
enable:

        mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_ON, saveSetting);

disable:

        mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_OFF, persistSetting);

mBluetoothState為BluetoothAdapterStateMachine.java的實例,在它的內部類PowerOff中,processMessage()方法對消息進行處理。在消息處理的對應代碼分支,將調用prepareBluetooth()本地方法。在該方法中,通過mBluetoothService調用enableNative()和disableNative()方法。mbluetoothService為BluetoothService的實例,而enableNative()和disableNative()則是JNI層的方法。

在藍牙開關圖第二幅中,由於都使用的是同一個BluetoothEnabler作為入口,所以所有流程都一樣。

在藍牙開關圖第三幅中,只需要搞懂手機下拉快捷設置中關於藍牙的界面部分。

它的代碼在/mtk6572-v1.0-dint/frameworks/base/packages/SystemUI/src下面。

包 類 作用 packages.SystemUI.src.com.android.systemui.statusbar.phone QuickSettings.java   packages.SystemUI.src.com.android.systemui.statusbar.policy BluetoothController.java   packages.SystemUI.src.com.android.systemui.statusbar.toolbar QuickSettingsConnectionModel.java  

表X 藍牙快捷設置代碼結構表

這部分涉及SystemUI模塊,由於時間因素暫時先不細講。

在藍牙開關圖第四幅中,也待補充。

9.4藍牙設置主界面

藍牙各種功能的配置,主要集中在藍牙設置主界面。它包括了打開/關閉,重命名,可見時間設置,共享歷史查詢,搜索周圍設備,配對周圍設備,配對後進行文件傳輸等功能。也就是說,以上功能都能在這裡找到入口。
藍牙設置主界面(關閉狀態)
圖X 藍牙設置主界面(關閉狀態)

藍牙設置,對應的代碼位置在Settings模塊下的com.android.settings.bluetoothangel包內。上圖的藍牙設置界面,主要由BluetoothSettings.java負責。接下來的很多功能,都將從這個類開始。

當打開藍牙後,菜單欄的【Rename phone】和【Visibility timeout】就會變為可選狀態。在最頂端會顯示本機藍牙設備名稱和可見狀態。默認對所有其他藍牙設備不可見。
藍牙設置主界面(打開狀態)
圖X 藍牙設置主界面(打開狀態)

9.5藍牙設備重命名

藍牙設備都會擁有自己的名字,便於用戶識別。對於手機這類可進行輸入的藍牙設備,還可以更改它的名字。當周圍設備搜索到該設備後,所看到的名字即這個修改後的名字。

點擊【Rename phone】,將出現一個對話框,如下圖所示:
藍牙rename功能圖
圖X 藍牙rename功能圖

由BluetoothNameDialogFragment.java負責rename界面,是一個Dialog。

其中,需要特別注意兩點:

(1)修改確認的【Rename】按鈕,在沒有進行修改的時候不可按;在有了修改操作後變為可按。為了區分有沒有修改,使用 mDeviceNameEdited作為區分標志。還有一個標志為mDeviceNameUpdated,則是用來標記是否有過確認操作。

(2)在更改名字的這個界面,有可能發生轉屏,轉屏是需要銷毀Activity再重新建立Activity的。因此,必須處理這種特殊情況下的名字保存問題,使用了KEY_NAME和KEY_NAME_EDITED兩個靜態關鍵字,來保存修改中的名字和修改的狀態,從而在發生轉屏的時候能夠恢復該名字信息。

更改的名字,最終保存在手機data/@mtBluetooth路徑下面。
藍牙rename時序圖
圖X 藍牙rename時序圖

9.6藍牙可見時間設置

藍牙在開啟後,默認是不可見狀態,也就是說就算開啟藍牙,周圍的其他設備也無法搜索到你的設備。要想能夠被其他設備搜索得到,必須設置“可見時間”。一般可以設置可見2分鐘到數分鐘不等,在這種情況下,設備在設置的時間范圍內對外可見,超過時間後自動變為不可見;當然,也可以設置為一直可見,這樣就不會有時間限制;但是,每次重新關閉又打開藍牙後,都必須重新手動點擊“可見時間”選項,才能重新生效(這種行為方式作者認為可以被定制)。

這部分內容,將功能截圖與代碼分析放在一起,便於對照說明。整個流程的時序圖如下:
藍牙可見時間設置時序圖
圖X 藍牙可見時間設置時序圖

點擊【Visibility timeout】後,顯示如下界面:
藍牙Visibility timeout設置界面
圖X 藍牙Visibility timeout設置界面

(1) 該界面由BluetoothVisibilityTimeoutFragment.java負責。這是一個填充了列表的dialog,幾個數據選項來自資源com.android.settings.R.array.bluetooth_visibility_timeout_entries。核心代碼如下:

    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
                .setTitle(R.string.bluetooth_visibility_timeout)
                .setSingleChoiceItems(R.array.bluetooth_visibility_timeout_entries,
                        mDiscoverableEnabler.getDiscoverableTimeoutIndex(), this)
                .setNegativeButton(android.R.string.cancel, null)
                .create();
    }

(2)array默認選項為2minutes。在初始化該dialog時的mDiscoverableEnabler.getDiscoverableTimeoutIndex()方法中進行控制,該方法在BluetoothDiscoverableEnabler.java中實現,具體代碼如下:

    int getDiscoverableTimeoutIndex() {
        int timeout = getDiscoverableTimeout();
        switch (timeout) {
            case DISCOVERABLE_TIMEOUT_TWO_MINUTES:
            default:
                return 0;

            case DISCOVERABLE_TIMEOUT_FIVE_MINUTES:
                return 1;

            case DISCOVERABLE_TIMEOUT_ONE_HOUR:
                return 2;

            case DISCOVERABLE_TIMEOUT_NEVER:
                return 3;
        }
    }

(3)具體設置可見時間的方法,則是在item被點擊後進行,代碼如下:

    public void onClick(DialogInterface dialog, int which) {
        mDiscoverableEnabler.setDiscoverableTimeout(which);
        dismiss();
    }
}

該方法在BluetoothDiscoverableEnabler.java中實現。在setDiscoverableTimeout()方法中,通過對應的list item ID來確定具體可見的時間,然後寫入SharedPreference中,並通過setEnable(true)開啟可見。核心代碼如下:

        mSharedPreferences.edit().putString(KEY_DISCOVERABLE_TIMEOUT, timeoutValue).apply();
        setEnabled(true);   // enable discovery and reset timer

(4)不可見的信息,由setSummary方法提供。具體代碼如下:

    private void setSummaryNotDiscoverable() {
        if (mNumberOfPairedDevices != 0) {
            mDiscoveryPreference.setSummary(R.string.bluetooth_only_visible_to_paired_devices);
        } else {
            mDiscoveryPreference.setSummary(R.string.bluetooth_not_visible_to_other_devices);
        }
    }

而當設置了可見時間後,如何動態地更新時間信息?由updateCountdownSummary()提供減數計算,算出剩余時間timeLeft,然後傳遞給updateTimerDisplay()進行界面的更新。而動態性,則是另起一個線程,由mUpdateCountdownSummaryRunnable來完成,它的run方法中調用updateCountdownSummary()。

(5)以上4點將外在的功能實現,在可見時間期間,藍牙的工作是怎樣開啟?從第3點的setEnable(ture)調用開始。代碼如下:

    private void setEnabled(boolean enable) {
        if (enable) {
            int timeout = getDiscoverableTimeout();
            long endTimestamp = System.currentTimeMillis() + timeout * 1000L;
            LocalBluetoothPreferences.persistDiscoverableEndTimestamp(mContext, endTimestamp);

            mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout);
            updateCountdownSummary();

            Log.d(TAG, "setEnabled(): enabled = " + enable + "timeout = " + timeout);

            if (timeout > 0) {
                BluetoothDiscoverableTimeoutReceiver.setDiscoverableAlarm(mContext, endTimestamp);
            } else if(timeout == 0) {
                //M: ALPS00580026 when set the discoverable timeout is 0, cacel the previous alarm
                BluetoothDiscoverableTimeoutReceiver.cancelDiscoverableAlarm(mContext);
            }
        } else {
            mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
            BluetoothDiscoverableTimeoutReceiver.cancelDiscoverableAlarm(mContext);
        }
    }

首先通過getDiscoverableTimeout()方法得到所設置的可見時間timeout。

然後,通過System.currentTimeMillis()獲得當前系統時間,與timeout相加,得到可見的結束時間endTimestamp。

然後,調用LocalBluetoothPreferences.java類的persistDiscoverableEndTimestamp()方法,將endTimestamp寫入SharedPreference中。這個時間最終在第4點updateCountdownSummary()方法中調用,用以更新Summary。
然後,用setScanMode()方法,將可見的搜尋模式DiscoveryMode和可見時間timeout通過BluetoothLocalApapter.java、BluetoothAdapter.java、BluetoothService.java一層層傳入。最終,BluetoothService.java中實現了該方法,通過setPropertyInteger()方法將時間傳入JNI層,通過setPropertyBoolean()將控制搜尋模式的兩個boolean值discoverable和pairable傳入JNI層。這兩個布爾值分別控制是否可以被其他藍牙設備搜尋到和是否可以配對,兩者組合在一起即是幾種搜尋模式。

(6)在上面的代碼中,最後還調用了BluetoothDiscoverableTimeoutReceiver.java的兩個方法setDiscoverableAlarm()和cancelDiscoverableAlarm()用來顯示當手機掛起後的notification。這個類沒有在最前面的流程圖裡畫出,這點請知悉。
時間設置成功效果
圖X 時間設置成功效果

9.7藍牙文件共享歷史

當點擊【Show received files】後,會進入下圖界面。只要是通過藍牙上傳(傳送給其他藍牙設備)或者下載(從其他藍牙設備接收)的文件,不論成功與否,沒打開之前均會在這裡有顯示。
藍牙文件傳輸歷史主界面
圖X 藍牙文件傳輸歷史主界面

(1)當點擊show received files選項後,通過發送一個廣播android.btopp.intent.action.OPEN_RECEIVED_FILES跳轉到MTK實現的廣播接收器BluetoothShareGatewayReceiver.java中,該類在/mediatek/packages/apps/Bluetooth/common/bt40/src/com/mediatek/bluetooth/下面。通過該廣播接收器,跳轉到BluetoothShareMgmtActivity.java,也就是負責上圖所示的Activity。
它繼承自TabActivity,從圖中可以看到由兩個頁面組成,分別使用inComingTabActivity和outGoingTabActivity進行管理。

(2)右上角有個菜單按鈕,提供Clear all操作。當沒有內容時,為不可點擊狀態;當有內容時,變為可點擊狀態。如下圖所示:
藍牙文件傳輸歷史功能示意圖
藍牙文件傳輸歷史功能示意圖
藍牙文件傳輸歷史功能示意圖
藍牙文件傳輸歷史功能示意圖
圖X 藍牙文件傳輸歷史功能示意圖

代碼如下:

if( CurrentTab.equals("Outgoing") ){
            menu.findItem(R.id.bt_share_mgmt_tab_menu_clear).setEnabled((outGoingTabActivity.getCursor().getCount() > 0));
        }
        else if( CurrentTab.equals("Incoming") ){
            menu.findItem(R.id.bt_share_mgmt_tab_menu_clear).setEnabled((inComingTabActivity.getCursor().getCount() > 0));
        }

而清除內容的工作,則是通過調用BluetoothShareTabActivity.java的clearAllTasks()方法實現。

(3)Download和Upload兩個Tab界面,均通過getIntent()方法跳轉至BluetoothShareTabActivity.java實現。代碼如下:

BluetoothShareTabActivity.getIntent(this, false));
BluetoothShareTabActivity.getIntent(this, true));

該方法的第二個參數是布爾型的isOutgoing。如果是false,表示不是Outgoing,則處理Download界面;如果是ture,表示是Outgoing,則處理Upload界面。

(4) BluetoothShareTabActivity.java的onCreate()方法負責繪制界面,界面是一個ListView,該ListView的數據從BluetoothShareTaskMetaData.java中query()得到後裝入Cursor,然後交由BluetoothShareTabAdapter負責顯示和管理。核心代碼如下:

       // query all tasks
        this.mCursor = getContentResolver().query(BluetoothShareTaskMetaData.CONTENT_URI, null, 
                isOutgoing ? OUTGOING_SELECTION : INCOMING_SELECTION, null, BluetoothShareTaskMetaData._ID + " DESC");

        // create list adapter
        if (this.mCursor != null) {

            BluetoothShareTabAdapter listAdapter = new BluetoothShareTabAdapter(this, R.layout.bt_share_mgmt_item,
                    this.mCursor);
            listView.setAdapter(listAdapter);

最後,使用registerTabActivity()方法回調至BluetoothShareMgmtActivity.java,這樣就能保持ListView的內容與菜單一致了,界面顯示Download的時候菜單操作的即是清除Dowload列表,界面顯示Upload的時候菜單操作的即是清除Upload列表。代碼如下:

BluetoothShareMgmtActivity.registerTabActivity(isOutgoing, this);

(5)第2點已經提到,刪除所有內容的方法是clearAllTasks()。代碼如下:

    public void clearAllTasks() {

        int columnIndex = this.mCursor.getColumnIndexOrThrow(BluetoothShareTaskMetaData._ID);
        ArrayList uris = new ArrayList();
        for (this.mCursor.moveToFirst(); !this.mCursor.isAfterLast(); this.mCursor.moveToNext()) {
            // compose Uri for the task and clear it
            int id = this.mCursor.getInt(columnIndex);
            Uri uri = Uri.withAppendedPath(BluetoothShareTaskMetaData.CONTENT_URI, Integer.toString(id));
            uris.add(uri);
        }

        Object[] param = new Object[] {
                this.mCursor, uris
        };
        sHandler.sendMessage(sHandler.obtainMessage(CLEAR_ALL_TASK, param));
    }

首先獲得存儲ListView數據的mCursor中每一項的id,然後使用得到的id組合成每一項的uri,全部裝入數組元素為uri的數組列表uris中,然後將該數組列表uris和代表清除所有數據的標志作為參數包裝為消息交給Handler進行處理。

其實,刪除單個內容的方法也是交給Hanlder進行處理的,這個後面遇到再講。

(6)這裡涉及到一個類BluetoothShareTaskMetaData,它是定義在BluetoothShareTask.java中的繼承BaseColumns的數據接口。而BluetoothShareTask.java是負責藍牙共享文件相關的數據輔助類。通過該類中的Uri定義,我們很容易發現藍牙的共享文件均存儲於ContentProvider中,具體實現類為BluetoothShareProvider.java。

(7)現在來具體看一下消息處理的方法handleMessage()。通過標志消息,判斷出是清除所有內容還是清除單項內容。通過new一個新線程BtShareClearHistoryThread來進行清除所有內容的操作。在該線程的run()方法中,執行clearAllItems()進行清除工作。核心代碼如下:

            int columnIndex = this.mCursor.getColumnIndexOrThrow(BluetoothShareTaskMetaData._ID);

            for (Uri uri : this.mUris) {
                ContentValues updateValues = new ContentValues();
                updateValues.put(BluetoothShareTaskMetaData.TASK_STATE, BluetoothShareTask.STATE_CLEARED);
                BluetoothShareTabActivity.this.getContentResolver().update(uri, updateValues, null, null);
            }

目前來看,通過獲取ContentResolver,然後使用update()方法。但是還未想清楚這到底是怎麼聯系到清除功能上的。

(8)在列表界面,每個item是支持單擊操作的。

對於Download來說,代碼中使用BluetoothShareTask.Direction.in來表示,而且分下載成功和失敗兩種情況。如果下載成功,單擊後可以打開該文件,打開成功後,會自動從列表中刪除該項記錄;如果下載失敗,單擊後會通過MessageActivity的createIntent()方法,啟動MessageActivity。該Activity主要用來顯示一個Dialog對話框,用以詢問是否需要重新下載。當然,當該對話框關閉後,會回調startActivityForResult()方法,進行記錄的刪除操作。

對於Upload來說,代碼中使用BluetoothShareTask.Direction.out來表示,分為三種情況:上傳失敗且文件路徑OK,上傳失敗的其他情況,上傳成功。三種情況均要調用MessageActivity的createIntent()方法,在第一種情況中彈出的對話框會詢問是否需要重新傳送,如果點擊確認將重新傳送。最終,當對話框關閉後,也會回調startActivityForResult()方法,進行記錄的刪除操作。重新傳送的Intent的核心代碼如下,至於其他部分邏輯已經說清除,就不粘貼出來了。

                // check to re-send the failed outgoing file
                Intent resendIntent = new Intent(Intent.ACTION_SEND);
                resendIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                resendIntent.setType(mTask.getMimeType());
                resendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mTask.getObjectUri()));
                resendIntent.putExtra(BluetoothShareGatewayActivity.EXTRA_DEVICE_ADDRESS, BluetoothAdapter
                        .getDefaultAdapter().getRemoteDevice(mTask.getPeerAddr()));

(9)在消除內容後,會進行Notification的取消,將調用NotificationFactory.java。它是一個Notification處理類,只有一個返回NotificationId的方法,很簡單。該方法中又調用BluetoothProfile.java,該類對各藍牙協議進行了編號定義。

(10)在第8點中講到,單擊Download列表項,如果該項是下載成功的,則可以打開該文件。這裡調用了SystemUtils.java的getOpenFileIntent()方法。SystemUtils.java是藍牙文件存儲的輔助類,在藍牙接收文件的過程中還將講到。

9.8藍牙掃描

觸發藍牙開啟掃描的方式有四個:

(1)在藍牙設置主界面,點擊藍牙打開按鈕,這時即可自動開啟掃描,在掃描過程中【SEARCH FOR DEVICES】按鈕為不可點擊狀態,直到掃描完畢,按鈕變為可點擊狀態。
(2)在上面狀態下,點擊【SEARCH FOR DEVICES】按鈕可以重新進行掃描。
(3)在藍牙打開的狀態下,每次進入藍牙設置主界面,都會重新進行掃描。
(4)在共享文件的時候,點擊【Allow】打開藍牙後,也會自動進行掃描。
(*5)在上面掃描完畢後,點擊【Scan for devices】,可以重新進行掃描。由於這個操作都是在共享文件的時候發生的,因此可以與第(4)並歸為一個方式。

第4個方式比較特殊,截圖如下(在文件傳輸的時候還會講到):
藍牙傳送文件時的掃描界面
圖X 藍牙傳送文件時的掃描界面

從邏輯上講,藍牙掃描的具體流程又分兩步:掃描設備信息,返回設備信息並顯示。

9.8.1邏輯上的設備信息掃描

邏輯上的掃描設備信息的時序圖如下:
藍牙掃描流程時序圖
圖X 藍牙掃描流程時序圖

(1)配對之前首先要進行掃描工作,在BluetoothSettings.java中調用startScanning(),在該方法中調用LocalBluetoothAdapter.java中的startScanning(Boolean force)方法,在其中又調用重寫的方法startScanning(boolean force, int type)方法,將BluetoothAdapter. TYPE_BR_EDR_ONLY參數傳進去。這個參數是在MTK自己實現的BluetoothAdapter中進行的定義,路徑如下:/mediatek/frameworks-ext/base/core.java.android.bluetooth,注意與Android源碼自身的BluetoothAdapter進行區分。

(2)在Scanning之前,需要進行一些狀態的判斷。比如如果手機是否已經處於搜索狀態等,方法為isDiscovering()。比如是否是強制掃描,這裡的強制掃描我暫時還不清楚具體是什麼功能,但是代碼中即是由布爾值參數force在控制。代碼如下:

    /// M: new API for startScanning with the type passing to BluetoothAdapter @{
    void startScanning(boolean force, int type) {
        // Only start if we're not already scanning
        if (!mAdapter.isDiscovering()) {
            if (!force) {
                // Don't scan more than frequently than SCAN_EXPIRATION_MS,
                // unless forced
                if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
                    return;
                }

                // If we are playing music, don't scan unless forced.
                A2dpProfile a2dp = mProfileManager.getA2dpProfile();
                if (a2dp != null && a2dp.isA2dpPlaying()) {
                    return;
                }
            }

            if (mAdapter.startDiscovery(type)) {
                mLastScan = System.currentTimeMillis();
            }
        }
    }

可以看出,首先判斷手機是否在掃描狀態,則判斷是否為強制掃描。如果最大掃描時間大於當前時間?或者正在播放音樂,都將返回,除非是強制掃描。

如果手機既不在掃描狀態,也沒有播放音樂,將會進行startDiscovery()操作,即開啟掃描功能。

(3)此時,首先判斷藍牙狀態是否開啟,然後調用BluetoothService.java的startDiscovery(type)方法。這裡需要強調一個參數type,它是int類型,用來指明是以哪種模式進行掃描。在此方法中將調用startDiscoveryNative(mode)進行掃描,這個方法在JNI層,在這裡先不繼續追究了。

9.8.2返回設備信息並顯示

進行到上面的最後一步,我們在手機上最直觀的感受是:附近的設備作為一個列表依次顯示在藍牙設置主界面,如下圖所示:
藍牙搜索完畢
圖X 藍牙搜索完畢

而這些設備的信息是得到的?又是怎麼顯示出來的?本節就是來講述這個容易被想當然忽視的部分。我們可以推斷出,它一定是經過了從底層硬件設備給出自身信息,然後一層層傳上來,直至傳至最上層藍牙設置界面的過程。邏輯上的返回設備信息並顯示的流程時序圖如下:

9.9藍牙配對

(1)首先還是從BluetoothSettings.java開始,它是繼承於DeviceListPreferenceFragment.java,在掃描完畢後,掃描得到的附近的藍牙設備信息將通過Preference顯示。當單擊任何一個設備後,將調用onDevicePreferenceClick()方法。代碼如下:

    @Override
    void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
        mLocalAdapter.stopScanning();
        super.onDevicePreferenceClick(btPreference);
    }

(2)在onDevicePreferenceClick()中調用BluetoothDevicePreference.java的onClicked()方法。

    void onClicked() {
        int bondState = mCachedDevice.getBondState();

        if (mCachedDevice.isConnected()) {
            askDisconnect();
        } else if (bondState == BluetoothDevice.BOND_BONDED) {
            Log.d(TAG, "connect");
            mCachedDevice.connect(true);
        } else if (bondState == BluetoothDevice.BOND_NONE) {
            pair();
        }
    }

先獲取當前綁定狀態,如果當前已經連接,則請求斷開連接;如果已經綁定,則連接;如果還未綁定,則調用pair()方法進行配對。

(3)在流程圖中,我將如何獲取綁定狀態的過程也畫了出來,可以看到,最終會走到BluetoothBondState.java中,這個類用來存儲設備的綁定狀態。根據類注釋,bluez並不會跟蹤設備的即時綁定狀態,所以我們通過這個類來進行保存並跟蹤。它的狀態保存在HashMap中,因此最終通過哈希表的get()方法獲得。

(4) askDisconnect()方法會顯示一個Dialog對話框,在其中將調用CachedBluetoothDevice.java的disconnect()方法。這個類代表了一個遠程的藍牙設備,它包含了該設備的各種屬性,諸如地址(address)、名稱(name)、RSSI等等,以及會在該設備上進行的一些操作功能,諸如連接(connect)、配對(pair)、取消連接(disconnect)等等。代碼如下:

   void disconnect() {
        for (LocalBluetoothProfile profile : mProfiles) {
            disconnect(profile);
        }
        // Disconnect  PBAP server in case its connected
        // This is to ensure all the profiles are disconnected as some CK/Hs do not
        // disconnect  PBAP connection when HF connection is brought down
        PbapServerProfile PbapProfile = mProfileManager.getPbapProfile();
        if (PbapProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED)
        {
            PbapProfile.disconnect(mDevice);
        }
    }

(5)可以看到,在該方法中將遍歷LocalBluetoothProfile中的所有協議,並依次調用disconnect(profile)進行斷開連接。這個方法對參數進行了重載,最終調用LocalBluetoothProfile.java的disconnect()方法。而LocalBluetoothProfile.java是一個接口文件,定義了和藍牙協議相關的各種基礎功能,具體實現由各個不同協議分別去做。

代碼中的注釋部分講了當HF連接還沒有關閉時一些CK/Hs不會斷開PBAP的連接,因此在disconnect()方法中調用了disconnect(profile)方法後還會進行再次的Pbap協議的關閉。

(6)回到第2點的代碼中,如果已經綁定,則調用connect()方法。首先調用ensurePaired()方法確認是否已經配對,如果還未綁定,則調用startPairing()方法開始配對,返回false表示還未配對;否則,直接返回true,表示已經配對。這塊的代碼如下:

    private boolean ensurePaired() {
        if (getBondState() == BluetoothDevice.BOND_NONE) {
            startPairing();
            return false;
        } else {
            return true;
        }
    }

(7)startPairing()方法的代碼如下:

    boolean startPairing() {
        // Pairing is unreliable while scanning, so cancel discovery
        if (mLocalAdapter.isDiscovering()) {
            mLocalAdapter.cancelDiscovery();
        }

        if (!mDevice.createBond()) {
            return false;
        }

        mConnectAfterPairing = true;  // auto-connect after pairing
        return true;
    }

注釋中說的比較清楚,當正在進行掃描的時候,配對會非常不穩定,因此在該方法中首先使用isDiscovering()判斷當前設備是否正處於掃描狀態,如果返回true,則調用cancelDiscovery()取消掃描。

藍牙配對的過程:
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

Share file:
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

Share history:
這裡寫圖片描述

附錄

藍牙各協議
Hands-free Profile(HFP)
手機音頻服務,用於連接單聲道藍牙耳機或車載藍牙,傳輸語音和數據信息。
實現AG角色

Advanced Audio Distribution Profile(A2DP)
媒體音頻服務,用於傳輸立體聲語音
實現Source角色

Audio/Video Remote Control Profile(AVRCP)
藍牙耳機控制手機上的音樂播放、暫停等。
實現Target角色

Object Push Profile(OPP)
對象傳輸協議,用於傳輸普通文件(如mp3、圖片等)。
實現Client&Server角色

Human Device Interface Profile(HID)
廣泛用於藍牙鍵盤、鼠標等人機交互設備
實現Host角色

Personal Area Networking Profile(PAN)
實現NAP Service角色

Phone Book Access Profile(PBAP) 電話本訪問

Basic Image Profile(BIP) 圖片傳輸

Basic Printing Profile(BPP) 控制打印

File Transfer Profile(FTP) 文件傳輸

SIM Access Profile(SAM,SIM) 芯片訪問

Proximity Profile(PRX) 距離感應

Message Access Profile(MAP) 信息訪問

Serial Port Profile(SPP) 虛擬串口

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved