Android藍牙學習筆記

一 Bluetooth基本概念java

  藍牙是無線數據和語音傳輸的開放式標準,它將各類通訊設備、計算機及其終端設備、各類數字數據系統、甚至家用電器採用無線方式聯接起來。它的傳輸距離爲10cm~10m,若是增長功率或是加上某些外設即可達到100m的傳輸距離。它採用2.4GHzISM頻段和調頻、跳頻技術,使用權向糾錯編碼、ARQ、TDD和基帶協議。TDMA每時隙爲0.625μs,基帶符合速率爲1Mb/s。藍牙支持64kb/s實時語音傳輸和數據傳輸,語音編碼爲CVSD,發射功率分別爲1mW、2.5mW和100mW,並使用全球統一的48比特的設備識別碼。因爲藍牙採用無線接口來代替有線電纜鏈接,具備很強的移植性,而且適用於多種場合,加上該技術功耗低、對人體危害小,並且應用簡單、容易實現,因此易於推廣。
  藍牙技術的系統結構分爲三大部分:底層硬件模塊、中間協議層和高層應用。底層硬件部分包括無線跳頻(RF)、基帶(BB)和鏈路管理(LM)。無線跳頻層經過2.4GHz無需受權的ISM頻段的微波,實現數據位流的過濾和傳輸,本層協議主要定義了藍牙收發器在此頻帶正常工做所須要知足的條件。基帶負責跳頻以及藍牙數據和信息幀的傳輸。鏈路管理負責鏈接、創建和拆除鏈路並進行安全控制。linux

 

二 AndroidBluetooth架構android

1. Android藍牙系統分爲四個層次,內核層、BlueZ庫、BlueTooth的適配庫、BlueTooth的JNI部分、Java框架層、應用層。下面先來分析Android的藍牙協議棧。
framework層:實現了Headset/Handsfree 和A2DP/AVRCPprofile,但其實現方式不一樣Handset/Handfree是直接在bluez的RFCOMMSocket上開發的,沒有利用bluez的audioplugin,而A2DP/AVRCP是在bluez的audioplugin基礎上開發的,大大下降了實現的難度。
Android的藍牙協議棧採用BlueZ來實現,BlueZ分爲兩部分:內核代碼和用戶態程序及工具集。
library層:libbluedroid.so等
bluez層:這是bluez用戶空間的庫,開源的bluetooth代碼,包括不少協議,生成libbluetooth.so。
Linuxkernel層:bluez協議棧、uart驅動,h4協議,hci,l2cap, sco, rfcommgit

2. Bluetooth代碼層次結構
(1)JAVA層
frameworks/base/core/java/android/bluetooth/ 包含了bluetooth的JAVA類。
(2)JNI層
frameworks/base/core/jni/android_bluetooth_開頭的文件, 定義了bluez經過JNI到上層的接口。
frameworks/base/core/jni/android_server_bluetoothservice.cpp 調用硬件適配層的接口system/bluetooth/bluedroid/bluetooth.c
(3)bluez庫
external/bluez/ 這是bluez用戶空間的庫,開源的bluetooth代碼,包括不少協議,生成libbluetooth.so。
(4)硬件適配層
system/bluetooth/bluedroid/bluetooth.c 包含了對硬件操做的接口
system/bluetooth/data/*一些配置文件,複製到/etc/bluetooth/。還有其餘一些測試代碼和工具。數據庫

內核代碼主要由BlueZ核心協議和驅動程序組成;藍牙協議實如今內核源代碼net/bluetooth中,驅動程序位於內核源代碼目錄driver/bluetooth中。用戶態程序及工具集主要包括應用程序接口和BlueZ工具集,位於Android源代碼目錄externel/bluetooth(注:Android版本不同,有的在externel/bluez目錄下)中。編程

 

三 Bluetooth協議棧分析安全

1. 藍牙協議棧服務器

藍牙協議棧的體系結構由底層硬件模塊、中間協議層和高端應用層三部分組成。
(1)底層硬件模塊
組成:
  鏈路管理協議(LinkManagerProtocol,LMP);
  基帶(BaseBand,BB);
  射頻(RadioFrequency,RF)。
功能:
  射頻(RF)經過2.4GHz的ISM頻段實現數據流的過濾和傳輸。
  基帶(BB)提供兩種不一樣的物理鏈路,即同步面向鏈接鏈路(SynchronousConnection Oriented,SCO)和異步無鏈接鏈路(AsynchronousConnectionLess,ACL),負責跳頻和藍牙數據,及信息幀的傳輸,且對全部類型的數據包提供不一樣層次的前向糾錯碼  (FrequencyError Correction,FEC)或循環冗餘度差錯校驗(CyclicRedundancyCheck,CRC)。
  鏈路管理協議(LMP)負責兩個或多個設備鏈路的創建和拆除,及鏈路的安全和控制,如鑑權和加密、控制和協商基帶包的大小等,它爲上層軟件模塊提供了不一樣的訪問入口。
  主機控制器接口(HostControllerInterface,HCI)是藍牙協議中軟硬件之間的接口,提供了一個調用下層BB、LMP、狀態和控制寄存器等硬件的統一命令,上下兩個模塊接口之間的消息和數據的傳遞必須經過HCI的解釋才能進行。網絡

(2)中間協議層
組成:
  邏輯鏈路控制和適配協議(LogicalLinkControl and Adaptation Protocol,L2CAP);
  服務發現協議(ServiceDiscoveryProtocol,SDP);
  串口仿真協議(或稱線纜替換協議RFCOMM);
  二進制電話控制協議(TelephonyControlprotocolSpectocol,TCS)。
功能:
  L2CAP位於基帶(BB)之上,向上層提供面向鏈接的和無鏈接的數據服務,它主要完成數據的拆裝、服務質量控制、協議的複用、分組的分割和重組,及組提取等功能。
  SDP是一個基於客戶/服務器結構的協議,它工做在L2CAP層之上,爲上層應用程序提供一種機制來發現可用的服務及其屬性,服務的屬性包括服務的類型及該服務所需的機制或協議信息。
  RFCOMM是一個仿真有線鏈路的無線數據仿真協議,符合ETSI標準的TS07.10串口仿真協議,它在藍牙基帶上仿真RS-232的控制和數據信號,爲原先使用串行鏈接的上層業務提供傳送能力。
  TCS定義了用於藍牙設備之間創建語音和數據呼叫的控制信令(CallControl Signalling),並負責處理藍牙設備組的移動管理過程。架構

(3)高端應用層
組成:
  點對點協議(Point-to-PointProtocol,PPP);
  傳輸控制協議/網絡層協議(TCP/IP);
  用戶數據包協議(UserDatagramProtocol,UDP);
  對象交換協議(ObjectExchangProtocol,OBEX);
  無線應用協議(WirelessApplicationProtocol,WAP);
  無線應用環境(WirelessApplicationEnvironment,WAE);
功能:
  PPP定義了串行點對點鏈路應當如何傳輸因特網協議數據,主要用於LAN接入、撥號網絡及傳真等應用規範。
  TCP/IP、UDP定義了因特網與網絡相關的通訊及其餘類型計算機設備和外圍設備之間的通訊。
  OBEX支持設備間的數據交換,採用客戶/服務器模式提供與HTTP(超文本傳輸協議)相同的基本功能。可用於交換的電子商務卡、我的日程表、消息和便條等格式。
  WAP用於在數字蜂窩電話和其餘小型無線設備上實現因特網業務,支持移動電話瀏覽網頁、收取電子郵件和其餘基於因特網的協議。
  WAE提供用於WAP電話和我的數字助理(PersonalDigitalAssistant,PDA)所需的各類應用軟件。

2. Android與藍牙協議棧的關係

藍牙系統的核心是BlueZ,所以JNI和上層都圍繞跟BlueZ的溝通進行。JNI和Android應用層,跟BlueZ溝通的主要手段是D-BUS,這是一套被普遍採用的IPC通訊機制,跟Android框架使用的Binder相似。BlueZ以D-BUS爲基礎,給其餘部分提供主要接口。

 

四 Bluetooth之HCI層分析

藍牙系統的HCI層是位於藍牙系統的L2CAP(邏輯鏈路控制與適配協議)層和LMP(鏈路管理協議)層之間的一層協議。HCI爲上層協議提供了進入LM的統一接口和進入基帶的統一方式。在HCI的主機(Host)和HCI主機控制器(HostController)之間會存在若干傳輸層,這些傳輸層是透明的,只需完成傳輸數據的任務,沒必要清楚數據的具體格式。目前,藍牙的SIG規定了四種與硬件鏈接的物理總線方式:USB、RS23二、UART和PC卡。其中經過RS232串口線方式進行鏈接具備差錯校驗。藍牙系統的協議模型如圖3所示。

1. HCI層與基帶的通訊方式

HCI是經過的方式來傳送數據、命令和事件的。全部在主機和主機控制器之間的通訊都以包的形式進行。包括每一個命令的返回參數都經過特定的事件包來傳輸。HCI有數據、命令和事件三種包,其中數據包是雙向的,命令包只能從主機發往主機控制器,而事件包始終是主機控制器發向主機的。主機發出的大多數命令包都會觸發主機控制器產生相應的事件包做爲響應。
命令包分爲六種類型:
  a.鏈路控制命令;
  b.鏈路政策和模式命令;
  c.主機控制和基帶命令;
  d.信息命令;
  e.狀態命令;
  f.測試命令。
事件包也可分爲三種類型:
  a.通用事件,包括命令完成包(CommandComplete)和命令狀態包(CommandStatus);
  b.測試事件;
  c.出錯時發生的事件,如產生丟失(FlushOccured)和數據緩衝區溢出(DataBuffer Overflow)。
數據包則可分爲ACL和SCO的數據包。


2. 包的分析及研究

命令包:命令包中的OCF(OpcodeCommand Field)和OGF(OpcodeGroup Field)是用於區分命令種的。ParameterLength表示所帶參數的長度,以字節數爲單位,隨後就是所帶的參數列表。下面以Inquiry命令爲例對HCI的命令包作具體說明。
在Inquiry命令中,OGF=0x01表示此命令屬於鏈路控制命令,同時OCF=0x0001則表示此命令爲鏈路控制命令中的Inquiry命令。OCF與OGF共佔2字節,又因爲底位字節在前,則它們在命令包爲0x0104。在Inquiry命令中,參數ParameterLength爲5。Inquiry命令帶3個參數,第一個參數爲LAP(lowaddress part), 它將用來產生Baseband中查詢命令包的包頭中的AccessCode。第二個參數爲Inquiry_Length,它時表示在Inquiry命令中止前所定義的最大時間,超過此時間,Inquiry命令將終止。第三個參數爲NUM_Response,它的值爲0X00表示設備響應數不受限制,只爲0x00-0xff則表示在Inquiry命令終止前最大的設備響應數。所以,若LAP=0x9e8b00,Inquiry_Length=0x05,NUM_Response=0x05,則協議上層調用Inquiry命令是HCI向基帶發的明令包將爲:0x0104 05 00 8b 9e 05 05。

事件包:事件包的EventCode用來區分不一樣的事件包,ParameterLength表示所帶參數的長度,以字節數爲單位,隨後就是所帶的參數列表。以CommandStatus Event事件包爲例對HCI的事件包進行具體說明。
當主機控制器收到主機發來的如上面所提到的Inquiry命令包並開始處理時,它就會向主機發送CommandStatus Event事件包,此事件包爲:0x0f04 00 0a 01 04。0xOf表示此事件包爲CommandStatusEvent事件包,0x04表示此事件包帶4字節長度的參數,0x00爲此事件包的第一個參數即Status,表示命令包正在處理。0x0a爲事件包的第二個參數NUM_HCI_Command_Packets,表示主機最多可在向主機控制器發10個命令包。0x0104 爲第三個參數Command_Opcode,表示此事件包是對Inquiry命令包的響應。

數據包:ACL和SCO數據包中的ConnectionHandle即鏈接句柄是一個12比特的標誌符,用於惟一確認兩臺藍牙設備間的數據或語音鏈接,能夠看做是兩臺藍牙設備間惟一的數據通道的標識。兩臺設備間只能有一條ACL鏈接,也就是隻有一個ACL的鏈接句柄,相應L2CAP的信道都是創建在這個鏈接句柄表示的數據通道上;兩臺設備間能夠有多個SCO的鏈接,則一對設備間會有多個SCO的鏈接句柄。鏈接句柄在兩設備鏈接期間一直存在,無論設備處於什麼狀態。在ACL數據包中,Flags分爲PBFlag和BCFlag,PBFlag爲包的界限標誌,PBFlag=0x00表示此數據包爲上層協議包(如L2CAP包)的起始部分;PBFlag=0x01表示此數據包爲上層協議包(如L2CAP包)的後續部分。BCFlag爲廣播發送的標誌,BCFlag=0x00表示無廣播發送,只是點對點的發送;BCFlag=0x01表示對全部處於激活狀態的從設備進行廣播發送,BCFlag=0x02表示對全部的從設備包括處於休眠狀態的從設備進行廣播發送。ACL和SCO數據包中的DataTotal Length 都表示所載荷的數據的長度,以字節位單位。

3. 通訊過程的研究與分析

當主機與基帶之間用命令的方式進行通訊時,主機向主機控制器發送命令包。主機控制器完成一個命令,大多數狀況下,它會向主機發出一個命令完成事件包(CommandComplete Packet),包中攜帶命令完成的信息。有些命令不會收到命令完成事件,而會收到命令狀態事件包(CommandStatusPacket),當收到該事件則表示主機發出的命令已經被主機控制器接收並開始處理,過一段時間該命令被執行完畢時,主機控制器會向主機發出相應的事件包來通知主機。若是命令參數有誤,則會在命令狀態事件中給出相應錯誤碼。假如錯誤出如今一個返回CommandComplete事件包的命令中,則此CommandComplete事件包不必定含有此命令所定義的全部參數。狀態參數做爲解釋錯誤緣由同時也是第一個返回的參數,老是要返回的。假如緊隨狀態參數以後是鏈接句柄或藍牙的設備地址,則此參數也老是要返回,這樣可判別出此CommandComplete事件包屬於那個實例的一個命令。在這種狀況下,事件包中鏈接句柄或藍牙的設備地址應與命令包種的相應參數一致。假如錯誤出如今一個不返回CommandComplete事件包的命令中,則事件包包含的全部參數都不必定是有效的。主機必須根據於此命令相聯繫的事件包中的狀態參數來決定它們的有效性。

 

五 Bluetooth之編程實現

1. HCI層編程

HostController Interface(HCI)是用來溝通Host和Module。Host一般就是PC,Module則是以各類物理鏈接形式(USB,serial,pc-card等)鏈接到PC上的bluetoothDongle。
在Host這一端:application,SDP,L2cap等協議都是軟件形式提出的(Bluez中是以kernel層程序)。
在Module這一端:LinkManager, BB, 等協議都是硬件中firmware提供的。
而HCI則比較特殊,它一部分在軟件中實現,用來給上層協議和程序提供訪問接口(Bluez中,hci.chci_usb.c,hci_sock.c等). 另外一部分也是在Firmware中實現,用來將軟件部分的指令等用底層協議明白的方式傳遞給底層。
居於PC的上層程序與協議和居於Modules的下層協議之間經過HCI溝通,有4種不一樣形式的傳輸:Commands,Event, ACL Data, SCO/eSCO Data。

2. 相關函數學習

(1) int hci_open_dev(int dev_id);
這個function用來打開一個HCISocket。它首先打開一個HCIprotocol的Socket,並將此Socket與deviceID=參數dev_id的Dongle綁定起來。只有bind後,它纔將Socket句柄與Dongle對應起來。
注意,全部的HCICommand發送以前,都須要使用hci_open_dev打開並綁定。

(2) int hci_close_dev(int dd);
簡單的關閉使用hci_open_dev打開的Socket。

(3) int hci_send_req(int dd, struct hci_request *r, int to);
向HCISocket發送request
BlueZ提供這個function很是有用,它能夠實現一切Host向Modules發送Command的功能。當應用程序須要向Dongle(對應爲一個bind後的Socket)發送Command時,調用此function. 下面詳細解釋此function和用法:
arg1: dd對應一個使用hci_open_dev()打開的Socket(Dongle)。
arg3: to則爲等待Dongle執行並回覆命令結果的timeout.以毫秒爲單位。
arg2: hci_request* r 最爲重要,首先看它的結構:
struct hci_request {
uint16_t ogf; //Opcode Group
uint16_t ocf; //Opcode Command
int event; //此Command產生的Event類型。
void *cparam; //Command 參數
int clen; //Command參數長度
void *rparam; //Response 參數
int rlen; //Response 參數長度
};
ogf,ocf就是GroupCode和CommandCode。這兩項先肯定下來,而後能夠查HCISpec,察看輸入參數(cparam)以及輸出參數(rparam)含義。至於他們的結構以及參數長度,則在~/include/net/bluetooth/hci.h中有定義。
至於event,若是設置,它會被setsockopt設置於Socket。

(4) int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to);
獲得指定DongleBDAddr,
arg1:HCISocket,使用hci_open_dev()打開的Socket(Dongle)。
arg2:輸出參數,其中會放置bdaddr.
arg3:以milliseconds爲單位的timeout.

(5) 讀寫DongleName:
inthci_read_local_name(int dd, int len, char *name, int to);
inthci_write_local_name(int dd, const char *name, int to);
arg1:HCISocket,使用hci_open_dev()打開的Socket(Dongle)。
arg2:讀取或設置Name。
arg3:以milliseconds爲單位的timeout.
注意:這裏的Name與IOCTLHCIGETDEVINFO 獲得hci_dev_info中的name不一樣。


(6) 獲得HCIVersion:
inthci_read_local_version(int dd, struct hci_version *ver, int to);

(7) 獲得已經UP的DongleBDaddr
int hci_devba(int dev_id, bdaddr_t *bdaddr);
dev_id: Dongle Device ID.
bdaddr: 輸出參數,指定Dongle若是UP,則放置其BDAddr。

(8) inquiry遠程BluetoothDevice
int hci_inquiry(int dev_id, int len, int nrsp, const uint8_t *lap,inquiry_info **ii, long flags);
命令指定的Dongle去搜索周圍全部bluetoothdevice. 並將搜索到的BluetoothDevice bdaddr 傳遞回來。
arg1:dev_id指定DongleDevice ID。若是此值小於0,則會使用第一個可用的Dongle。
arg2:len這次inquiry的時間長度(每增長1,則增長1.25秒時間)
arg3:nrsp這次搜索最大搜索數量,若是給0。則此值會取255。
arg4:lap BDADDR中LAP部分,Inquiry時這塊值缺省爲0X9E8B33.一般使用NULL。則自動設置。
arg5:ii存放搜索到BluetoothDevice的地方。給一個存放inquiry_info指針的地址,它會自動分配空間。並把那個空間頭地址放到其中。
arg6:flags搜索flags.使用IREQ_CACHE_FLUSH,則會真正從新inquiry。不然可能會傳回上次的結果。
返回值是此次Inquiry到的BluetoothDevice數目。
注意:若是*ii不是本身分配的,而是讓hci_inquiry()本身分配的,則須要調用bt_free()來幫它釋放空間。

(9) 獲得指定BDAddr的reomtedevice Name:
int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char*name, int to);
arg1:使用hci_open_dev()打開的Socket。
arg2:對方BDAddr.
arg3:name長度。
arg4:(out)放置name的位置。
arg5:等待時間。

(10)讀取鏈接的信號強度
int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to);
注意,全部對鏈接的操做,都會有一個參數Handle。


3. L2CAP層編程

邏輯鏈接控制和適配協議(L2CAP)爲上層協議提供面向鏈接和無鏈接的數據服務,並提供多協議功能和分割重組操做。L2CAP充許上層協議和應用軟件傳輸和接收最大長度爲64K的L2CAP數據包。
L2CAP基於通道(channel)的概念。通道(Channel)是位於基帶(baseband)鏈接之上的邏輯鏈接。每一個通道以多對一的方式綁定一個單一協議(singleprotocol)。多個通道能夠綁定同一個協議,但一個通道不能夠綁定多個協議。每一個在通道里接收到的L2CAP數據包被傳到相應的上層協議。多個通道可共享同一個基帶鏈接。
L2CAP使用L2CAP鏈接請求(Connection Request)命令中的PSM字段實現協議複用。L2CAP能夠複用發給上層協議的鏈接請求,這些上層協議包括服務發現協議SDP(PSM=0x0001)、RFCOMM(PSM=0x0003)和電話控制(PSM=0x0005)等。
L2CAP編程很是重要,它和HCI基本就是LinuxBluetooth編程的基礎了。幾乎全部協議的鏈接,斷連,讀寫都是用L2CAP鏈接來作的

建立L2CAPSocket:
socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP);
domain=PF_BLUETOOTH, type能夠是多種類型, protocol=BTPROTO_L2CAP
綁定:
memset(&addr,0, sizeof(addr));
addr.l2_family= AF_BLUETOOTH;
bacpy(&addr.l2_bdaddr, &bdaddr); //bdaddr爲本地DongleBDAddr
if(bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
  perror("Can'tbind socket");
  goto error;
}
鏈接:
memset(&addr,0, sizeof(addr));
addr.l2_family= AF_BLUETOOTH;
bacpy(addr.l2_bdaddr, src);
addr.l2_psm= xxx;
if(connect(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
  perror("Can'tconnect");
  goto error;
}
注意:
structsockaddr_l2
{
  sa_family_t l2_family; //必須爲 AF_BLUETOOTH
  unsignedshort l2_psm; //與前面PSM對應,這一項很重要
  bdaddr_t l2_bdaddr; //Remote Device BDADDR
  unsignedshort l2_cid;
};
發送數據到RemoteDevice:send()或write()均可以。接收數據: revc()或read()均可以


4. SDP層編程

服務發現協議(SDP或BluetoothSDP)在藍牙協議棧中對藍牙環境中的應用程序有特殊的含意,發現哪一個服務是可用的和肯定這些可用服務的特徵。SDP定義了bluetoothclient發現可用bluetoothserver服務和它們的特徵的方法。這個協議定義了客戶如何可以尋找基於特定屬性的服務而不須要客戶知道可用服務的任何知識。SDP提供發現新服務的方法,在當客戶登陸到正在操做的藍牙服務器的一個區域時是可用的時。Servicediscovery機制提供client應用程序偵測server應用程序提供的服務的能力,而且可以獲得服務的特性。服務的品質包含服務type或服務class.

SDP也提供SDPserver與SDPclient之間的通信。SDPserver維護着一個服務條目(servicerecord)列表. 每一個服務條目描述一個單獨的服務屬性。SDPclient能夠經過發送SDPrequest來獲得服務條目。

若是一個client或者依附於client之上的應用程序決定使用某個service. 它建立一個單獨的鏈接到service的提供者。SDP只提供偵測Service的機制,但不提供如何利用這些Service的機制。Sam以爲,這裏實際上是說:SDP只提供偵測Service的辦法,但如何用,SDP無論。

每一個Bluetooth Device最多隻能擁有一個SDP Server。若是一個Bluetooth Device只擔任Client,那它不須要SDPServer。但一個BluetoothDevice能夠同時擔當SDPServer和SDPclient. 
(1) ServiceRecord(Service 條目):一個service是一個實體爲另外一個實體提供信息,執行動做或控制資源。一個service能夠由軟件,硬件或軟硬件結合提供。全部的Service信息都包含於一個ServiceRecord內。一個ServiceRecord 包含一個Serviceattribute(Service屬性)list.

(2) ServiceAttribute(Service 屬性):每一個Service屬性描述servcie的特性.一個ServiceAttribute由2部分:AttributeID + Attribute Value。

(3) ServiceClass:每一個Service都是某個ServiceClass的實例. Service Class定義了ServiceRecord中包含的Service屬性. 屬性ID,屬性值都被定義好了。每一個ServiceClass也有一個獨特ID。這個ServiceClass標識符包含在屬性值ServiceClassIDList屬性中。
並描繪爲UUID。自從ServiceRecord中的屬性格式以及含義依賴於ServiceClass後,ServiceClassIDList屬性變得很是重要。

(4) SearchingFor Service: ServiceSearch transaction容許client獲得ServiceRecord Handle。一旦SDPClient獲得ServiceRecord Handle,它就能夠請求這個Record內具體屬性的值。若是某個屬性值UUID,則能夠經過查找UUID查到這個屬性。UUID:universally uniqueidentifier.(惟一性標識符)

總之,DP協議棧使用request/response模式工做,每一個傳輸過程包括一個requestprotocol data unit(PDU)和一個responsePDU. SDP使用L2CAP鏈接傳輸數據。在發送RequestPDU但未收到ResponsePDU以前,不能向同一個server再發送RequestPDU。

 

六 Bluetooth之啓動過程實現

對於藍牙不管最底層的硬件驅動如何實現,都會在HCI層進行統一。也就是說,HCI在主機端的驅動主要是爲上層提供統一接口,讓上層協議不依賴於具體的硬件實現。HCI在硬件中的固件與HCI在主機端的驅動通訊方式有多種,好比UART,USB和SDIO等。

全部的設備在HCI層面前都被抽象爲一個hci_dev結構體,所以,不管實際的設備是哪一種藍牙設備、經過什麼方式鏈接到主機,都須要向HCI層和藍牙核心層註冊一個hci_dev設備,註冊過程由hci_registe_dev()函數來完成,同時也能夠經過hci_unregister_dev()函數卸載一個藍牙設備。
具體的藍牙驅動有不少,經常使用的在linux內核都自帶有驅動。好比:hci_vhci.c爲藍牙虛擬主控制器驅動程序,hci_uart.c(或者hci_ldisc.c)爲串口接口主控制器驅動程序,btusb.c爲USB接口主控制器驅動程序,btsdio.c爲SDIO主控制器驅動程序。

1. Bluetooth啓動步驟
(1) 串口驅動必需要先就緒(uart藍牙而言),這是cpu和藍牙模塊之間的橋樑。
(2) 藍牙初始化,模塊上電和PSKEY的設置。
(3) 經過hciattach創建串口和藍牙協議層之間的數據鏈接通道。

2. Bluetooth啓動流程
(1) 打開藍牙電源,經過rfkill來enable;(system/bluetooth/bluedroid/bluetooth.c)
(2) 啓動servicehciattch -n -s 115200 /dev/ttyS2 bcm2035 115200;
(3) 檢測HCI是否成功(接受HCIDEVUPsocket來判斷或hciconfig hci0 up);
(4) hcid deamon start up。

3. Bluetooth數據流向
(1) uart口取得藍牙模塊的數據;
(2) uart口經過ldisc傳給hci_uart;
(3) hci_uart傳給在其上的bcsp;
(4) bcsp傳給hci層;
(5) hci層傳給l2cap層
(6) l2cap層再傳給rfcomm;

藍牙模塊上電:通常是經過一個GPIO來控制的,一般是先高再低再高;

PSKEY的設置:經過串口發送命令給藍牙模塊,對於串口必需要知道的是要能通信,必須得設好波特率,另一方面藍牙模塊的晶振頻率也必需要設,不然它不知道該怎麼跳了;固然不一樣的芯片可能初始化的過程也不同,也許還要下載firmware等等,通常是經過bccmd來完成的。
通過上面的設置基本上藍牙模塊以及能夠正常工做了;
可是尚未和上面的協議層創建紐帶關係,也就是說從uart收到的數據尚未傳給hci層;如何把uart也就是藍牙模塊傳上來的數據交給hci層,在驅動裏面是經過一個叫作disc的機制完成的,這個機制本意是用來作過濾或者限制收上來的字符的,可是在藍牙驅動裏面則直接把數據傳給了藍牙協議層,不再回到串口的控制了;

4. Bluez控制流程
class bluetoothsetting是UI的入口,經過按buttonscan進入搜索狀態,applicaton層調用bluetoothdevice, 接着就是bluetoothservice的調用,bluetoothservice調用native方法,到此所有的java程序結束了。下面的調用都是JNI,cpp實現的。android_server_bluetoothservice.cpp裏面實現了native方法,最終經過dbus封裝,調用HCID deamon的functionDiscoverDevice。

5. Bluetooth啓動過程分析

(1) 各協議層的註冊
a.在af_bluetooth.c中首先調用bt_init()函數完成初始化,打印信息"Bluetooth: Core ver 2.16"隨後調用函數sock_register()註冊sock,打印信息"Bluetooth: HCI device and connection manager initialized"
b.接着調用函數hci_sock_init(), l2cap_init(), sco_init()實現各個協議的初始化
c.在hci_sock.c中,註冊bt_sock協議,打印信息"Bluetooth: HCI socket layer initialized"
d.在L2cap_core.c中,調用l2cap_init_socks()函數,完成初始化,打印信息"Bluetooth: L2CAP socket layer initialized"
e.在sco.c中,註冊BTPROTO_SCO協議,完成初始化,打印信息"Bluetooth: SCO socket layer initialized"

(2) 各硬件的初始化
a.在Hci_ldisc.c中,完成hci_uart_init的初始化,打印信息"Bluetooth: HCI UART driver ver 2.2"
b.在Hci_h4.c中,完成h4_init的初始化,打印信息"Bluetooth: HCI H4 protocol initialized"
c.在Hci_ath.c中,完成ath_Init的初始化,打印信息"Bluetooth: HCIATH3Kprotocol initialized"
d.在hci_ibs.c中,完成ibs_init的初始化,打印信息"Bluetooth: HCI_IBSprotocol initialized"
e.在Core.c中,完成rfcomm_init的初始化,其中調用函數rfcomm_init_ttys()、rfcomm_init_sockets(),實現rfcomm的初始化,打印信息"Bluetooth: RFCOMMver 1.11"
f.在TTY.C中,完成rfcomm_init_ttys的初始化,實現rfcomm_tty_driver的註冊,打印信息"Bluetooth: RFCOMMTTY layer initialized"
g.在Sock.c中,完成rfcomm_init_sockets的初始化,實現BTPROTO_RFCOMM的註冊,打印信息"Bluetooth: RFCOMMsocket layer initialized"
經過rfcomm完成uart串口的初始化代碼在kernel\net\bluetooth\rfcomm

(3) bnep-藍牙網絡封裝協議
在Core.c中,完成bnep_init的初始化,實現bnep的初始化,打印信息"Bluetooth: BNEP(Ethernet Emulation) ver 1.3/Bluetooth: BNEP filters: protocol" 完成藍牙網絡封裝協議的初始化代碼在kernel\net\bluetooth\bnep

 

七 Bluetooth之驅動移植

1. android系統配置
build\target\board\generic下面的generic.mk增長:
BOARD_HAVE_BLUETOOTH:= true 這個是因爲編譯相關藍牙代碼時須要這個宏,見system\bluetooth\android.mk
ifeq($(BOARD_HAVE_BLUETOOTH),true)
include$(all-subdir-makefiles)
endif
在 external\bluetooth也一樣存在此宏起做用

2. 啓動項修改
system/core/rootdir下init.rc文件增長:
service hciattach /system/bin/hciattach -n -s 115200 /dev/ttyS2 bcm2035115200
user bluetooth
group bluetooth net_bt_admin
disabled
oneshot
請放在 servicebluetoothd /system/bin/bluetoothd -n 相似這種語句的後面任意位置便可。

3. 電源管理rfkill驅動
kernel/driver/bluetooth/bluetooth-power.c 高通的這個文件基本上不用動。
在kernel/arch/arm/mach_msm7x27.c: static int bluetooth_power(int on)中實現:
上電:
  把bt_resetpin和bt_reg_onpin拉低
  mdelay(10);
  把bt_resetpin和bt_reg_onpin拉高
  mdelay(150);
掉電:
  把bt_resetpin和bt_reg_onpin拉低

4. Rebuild Androidimage and reboot

命令行測試:
echo 0 >/sys/class/rfkill/rfkill0/state //BT下電
echo 1 >/sys/class/rfkill/rfkill0/state //BT上電
brcm_patchram_plus -d --patch ram/etc/firmware/BCM4329B1_002.002.023.0061.0062.hcd/dev/ttyHS0
hciattach -s 115200 /dev/ttyHS0 any 沒任何錯誤提示是能夠用如下測試
hciconfig hci0 up
hcitool scan

5. 實現BT睡眠喚醒機制

Kernel/drivers/bluetooth/bluesleep.c 通常來講這個文件改動比較少,但可能邏輯上會有些問題。須要小的改動(4.4內核中已經沒有這個文件了)。
在kernel/arch/arm/mach_xxx/board_xxx.c:bluesleep_resources中定義gpio_host_wake(BT喚醒host腳)、gpio_ext_wake(host喚醒BT腳)、host_wake(BT喚醒host的中斷號)。
注:各個平臺的board_xxx.c文件名字不一樣,請客戶確認(目前已經被設備樹替代)

6.系統集成

(1)在init.qcom.rc中確認有下面的內容:
service hciattach /system/bin/sh /system/etc/init.qcom.bt.sh
user bluetooth
group qcom_oncrpc bluetooth net_bt_admin
disabled
oneshot

(2)修改init.qcom.bt.sh
確認有:
BLUETOOTH_SLEEP_PATH=/proc/bluetooth/sleep/proto(沒有這個路徑)
echo1 > $BLUETOOTH_SLEEP_PATH
/system/bin/hciattach-n/dev/ttyHS0 any 3000000 flow & 改成:
./brcm_patchram_plus--enable_lpm–enable_hci --patchram /system/etc/wifi/BCM4329BT.hcd--baudrate3000000 /dev/ttyHS0 &
注掉:高通下載firmware的命令。最後,從新編譯system。此時BT應該能運行了。

 

八 Bluetooth之調試與編譯

1. Bluetooth驅動調試

調試你的藍牙實現,能夠經過讀跟藍牙相關的logs(adblogcat)和查找ERROR和警告消息。Android使用Bluez,同時會帶來一些有用的調式工具。下面的片斷爲了提供一個建議的例子:
1 hciconfig -a    # print BT chipset address and features. Useful to check if you can communicate with your BT chipset.
2 hcidump -XVt # print live HCI UART traffic.
3 hcitool scan   # scan for local devices. Useful to check ifRX/TX works.
4 l2ping ADDRESS # ping another BT device. Useful to check ifRX/TX works.
5 sdptool records ADDRESS # request the SDP records of another BTdevice.

守護進程日誌
hcid(STDOUT)和hciattach(STDERR)的守護進程日誌缺省是被寫到/dev/null。編輯init.rc和init.PLATFORM.rc在logwrapper下運行這些守護進程,把它們輸出到logcat。
hciconfig -a 和 hcitool
若是你編譯你本身的system.img,除了hcitool掃描不行,hciconfig -a是能夠工做的,嘗試安裝固件到藍牙芯片。

2. Bluetooth 調試工具

BlueZ爲調試和與藍牙子系統通訊提供不少設置命令行工具,包含下面這些:
(1) hciconfig
(2) hcitool
(3) hcidump
(4) sdptool
(5) dbus-send
dbus-monitor

 

九 Bluetooth之應用程序開發

1.Bluetooth的API開發

Android平臺包含了對Bluetooth協議棧的支持,容許機器經過Bluetooth設備進行無線數據交換。應用框架經過AndroidBluetoothAPI訪問Bluetooth功能模塊。這些API能讓應用無線鏈接其餘Bluetooth設備,實現點對點和多點之間的通訊。
運用藍牙API,Android應用程序能夠完成以下操做:
(1) 掃描其餘Bluetooth設備。
(2) 查詢配對Bluetooth設備的本地Bluetooth適配器。
(3) 創建RFCOMM通道。
(4) 經過服務探索鏈接到其餘設備。
(5) 與其餘設備進行數據傳輸。
(6) 管理多個鏈接

2. The Basics開發

本文描述如何使用AndroidBluetoothAPIs完成Bluetooth通信的4個必要任務:設置Bluetooth,搜尋本地配對或者可用的Bluetooth設備,鏈接Bluetooth設備,與Bluetooth設備進行數據傳輸。

全部可用的Bluetooth APIs都包含在android.bluetooth包中。下面是創建Bluetooth鏈接須要用到的類和接口的總結:
(1) BluetoothAdapter
描述本地Bluetooth適配器(Bluetooth接收器)。BluetoothAdapter是全部Bluetooth相關活動的入口。運用BluetoothAdapter能夠發現其餘Bluetooth設備,查詢鏈接(或配對)的設備列表,用已知MAC地址實例化一個BluetoothDevice對象,建立一個BluetoothServerSocket對象偵聽其餘設備的通訊。
(2) BluetoothDevice
描述一個遠程Bluetooth設備。能夠用它經過一個BluetoothSocket請求一個遠程設備的鏈接,或者查詢遠程設備的名稱、地址、類、鏈接狀態等信息。
(3) BluetoothSocket
描述一個BluetoothSocket接口(相似於TCPSocket)。應用經過InputStream和OutputStream與另一個Bluetooth設備交換數據,即它是應用與另一個設備交換數據的鏈接點。
(4) BluetoothServerSocket
BluetoothServerSocket是一個開放的socket服務器,用來偵聽鏈接進來的請求(相似於RCPServerSocket)。爲了鏈接兩個Android設備,一個設備必須使用該類來開啓一個socket作服務器,當另一個設備對它發起鏈接請求時而且請求被接受時,BluetoothServerSocket會返回一個鏈接的BluetoothSocket對象。
(5) BluetoothClass
BluetoothClass是用來定義設備類和它的服務的只讀屬性集。然而,它並非可靠的描述設備支持的全部Bluetooth配置和服務,而只是一些設備類型的有用特徵。
(6) BluetoothProfile
BluetoothProfile是兩個設備基於藍牙通信的無線接口描述。Profile定義了設備如何實現一種鏈接或者應用,你能夠把Profile理解爲鏈接層或者應用層協議。好比,若是一家公司但願它們的Bluetooth芯片支持全部的Bluetooth耳機,那麼它只要支持HeadSetProfile便可,而無須考慮該芯片與其它Bluetooth設備的通信與兼容性問題。若是你想購買Bluetooth產品,你應該瞭解你的應用須要哪些Profile來完成,而且確保你購買的Bluetooth產品支持這些Profile。
(7) BluetoothHeadset
提供移動電話的Bluetooth耳機支持。包括Bluetooth耳機和Hands-Free(v1.5) profiles。
(8) BluetoothA2dp
定義兩個設備間如何經過Bluetooth鏈接進行高質量的音頻傳輸。A2DP(AdvancedAudio Distribution Profile):高級音頻傳輸模式。
(9) BluetoothProfile.ServiceListener
一個接口描述,在與服務鏈接或者斷鏈接的時候通知BluetoothProfileIPC(這是內部服務運行的一個特定的模式<profile>)。

3. BluetoothPermissions開發

要使用Bluetooth功能,至少須要2個Bluetooth權限:BLUETOOTH 和 BLUETOOTH_ADMIN.
BLUETOOTH:用來受權任何Bluetooth通訊,如請求鏈接,接受鏈接,傳輸數據等。
BLUETOOTH_ADMIN:用來受權初始化設備搜索或操做Bluetooth設置。大多數應用須要它的惟一場合是用來搜索本地Bluetooth設備。本受權的其餘功能不該該被使用,除非是須要修改Bluetooth設置的「powermanager(電源管理)」應用。

注意:須要BLUETOOTH_ADMIN權限的場合,BLUETOOTH權限也是必需的。須要在manifest文件中聲明Bluetooth權限,示例以下:
<manifest... >
<uses-permissionandroid:name="android.permission.BLUETOOTH" />
...
</manifest>

4. Setting Up Bluetooth服務

在用Bluetooth通信以前,須要確認設備是否支持Bluetooth,若是支持,還得確保Bluetooth是可用的。若是設備不支持Bluetooth,須要優雅的將Bluetooth置爲不可用。若是支持Bluetooth,但沒有開啓,能夠在應用中請求開啓Bluetooth。該設置使用BluetoothAdapter. 經過兩個步驟完成。

(1) 獲取BluetoothAdapter
BluetoothAdapter是每一個Bluetooth的Activity都須要用到的。用靜態方法getDefaultAdapter()獲取BluetoothAdapter,返回一個擁有Bluetooth適配器的BluetoothAdapter對象。若是返回null,說明設備不支持Bluetooth,關於Bluetooth的故事到此就結束了(由於你幹不了什麼了)。示例:
BluetoothAdaptermBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter == null) {
  //Device does not support Bluetooth
}

(2) EnableBluetooth
接下來,就是確保Bluetooth功能是開啓的。調用isEnabled()來檢查Bluetooth當前是不是開啓的。用ACTION_REQUEST_ENABLEactionIntent調用startActivityForResult()來請求開啓Bluetooth,這會經過系統設置發出一個Bluetooth使能請求(而且不會中止本應用程序)。示例:
if(!mBluetoothAdapter.isEnabled()) {
  IntentenableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
  startActivityForResult(enableBtIntent,REQUEST_ENABLE_BT);
}

用戶請求使能Bluetooth時,會顯示一個對話框。選擇「Yes」,系統會使能Bluetooth,而且焦點會返回你的應用程序。若是使能Bluetooth成功,你的Activity會在onActivityResult()回調函數中收到RESULT_OK的結果碼。若是Bluetooth使能因發生錯誤(或用戶選擇了「No」)而失敗,收到的結果碼將是RESULT_CANCELED。

做爲可選項,應用也能夠偵聽ACTION_STATE_CHANGED broadcastIntent,這樣不管Bluetooth狀態什麼時候被改變系統都會發出broadcast(廣播)。該廣播包含附加的字段信息EXTRA_STATE和EXTRA_PREVIOUS_STATE分別表明新的和舊的Bluetooth狀態,該字段可能的值爲STATE_TURNING_ON,STATE_ON, STATE_TURNING_OFF,和STATE_OFF。應用運行時,偵聽ACTION_STATE_CHANGED廣播來檢測Bluetooth狀態的改變是頗有用的。

提示:啓用Bluetooth可被發現功能可以自動開啓Bluetooth。若是在完成Activity以前須要持續的使能Bluetooth可被發現功能,那麼上面的第2步就能夠忽略。


5. Finding Devices服務

使用BluetoothAdapter能夠經過設備搜索或查詢配對設備找到遠程Bluetooth設備。
Devicediscovery(設備搜索)是一個掃描搜索本地已使能Bluetooth設備而且從搜索到的設備請求一些信息的過程(有時候會收到相似「discovering」,「inquiring」或「scanning」)。可是,搜索到的本地Bluetooth設備只有在打開被發現功能後纔會響應一個discovery請求,響應的信息包括設備名,類,惟一的MAC地址。發起搜尋的設備可使用這些信息來初始化跟被發現的設備的鏈接。
一旦與遠程設備的第一次鏈接被創建,一個pairing請求就會自動提交給用戶。若是設備已配對,配對設備的基本信息(名稱,類,MAC地址)就被保存下來了,可以使用BluetoothAPI來讀取這些信息。使用已知的遠程設備的MAC地址,鏈接能夠在任什麼時候候初始化而沒必要先完成搜索(固然這是假設遠程設備是在可鏈接的空間範圍內)。

須要記住,配對和鏈接是兩個不一樣的概念:
配對意思是兩個設備相互意識到對方的存在,共享一個用來鑑別身份的鏈路鍵(link-key),可以與對方創建一個加密的鏈接。鏈接意思是兩個設備如今共享一個RFCOMM信道,可以相互傳輸數據。目前AndroidBluetooth API's要求設備在創建RFCOMM信道前必須配對(配對是在使用BluetoothAPI初始化一個加密鏈接時自動完成的)。

下面描述如何查詢已配對設備,搜索新設備:
注意:Android的電源設備默認是不能被發現的。用戶能夠經過系統設置讓它在有限的時間內能夠被發現,或者能夠在應用程序中要求用戶使能被發現功能。

(1) Queryingpaired devices
在搜索設備前,查詢配對設備看須要的設備是否已是已經存在是很值得的,能夠調用getBondedDevices()來作到,該函數會返回一個描述配對設備BluetoothDevice的結果集。例如,可使用ArrayAdapter查詢全部配對設備而後顯示全部設備名給用戶:

Set<BluetoothDevice>pairedDevices = mBluetoothAdapter.getBondedDevices();
//If there are paired devices
if(pairedDevices.size() > 0) {
    //Loop through paired devices
    for(BluetoothDevice device : pairedDevices) {
        //Add the name and address to an array adapter to show in a ListView
        mArrayAdapter.add(device.getName()+ "\n" + device.getAddress());
    }
}

BluetoothDevice對象中須要用來初始化一個鏈接惟一須要用到的信息就是MAC地址。

(2) Discoveringdevices
要開始搜索設備,只需簡單的調用startDiscovery()。該函數時異步的,調用後當即返回,返回值表示搜索是否成功開始。搜索處理一般包括一個12秒鐘的查詢掃描,而後跟隨一個頁面顯示搜索到設備Bluetooth名稱。
應用中能夠註冊一個帶CTION_FOUND Intent的BroadcastReceiver,搜索到每個設備時都接收到消息。對於每個設備,系統都會廣播ACTION_FOUND Intent,該Intent攜帶着額外的字段信息EXTRA_DEVICE和EXTRA_CLASS,分別包含一個BluetoothDevice和一個BluetoothClass。下面的示例顯示如何註冊和處理設備被發現後發出的廣播:

//Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    publicvoid onReceive(Context context, Intent intent) {
        Stringaction = intent.getAction();
        //When discovery finds a device
        if(BluetoothDevice.ACTION_FOUND.equals(action)) {
            //Get the BluetoothDevice object from the Intent
            BluetoothDevicedevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            //Add the name and address to an array adapter to show in a ListView
            mArrayAdapter.add(device.getName()+ "\n" + device.getAddress());
        }
    }
};
//Register the BroadcastReceiver
IntentFilterfilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy

警告:完成設備搜索對於Bluetooth適配器來講是一個重量級的處理,要消耗大量它的資源。一旦你已經找到一個設備來鏈接,請確保你在嘗試鏈接前使用了cancelDiscovery()來中止搜索。一樣,若是已經保持了一個鏈接的時候,同時執行搜索設備將會顯著的下降鏈接的帶寬,因此在鏈接的時候不該該執行搜索發現。

(3) Enabling discover ability
若是想讓本地設備被其餘設備發現,能夠帶ACTION_REQUEST_DISCOVERABLEaction Intent調用startActivityForResult(Intent,int)方法。該方法會提交一個請求經過系統剛設置使設備出於能夠被發現的模式(而不影響應用程序)。默認狀況下,設備在120秒後變爲能夠被發現的。能夠經過額外增長EXTRA_DISCOVERABLE_DURATIONIntent自定義一個值,最大值是3600秒,0表示設備老是能夠被發現的(小於0或者大於3600則會被自動設置爲120秒)。下面示例設置時間爲300:

IntentdiscoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,300);
startActivity(discoverableIntent);

詢問用戶是否容許打開設備能夠被發現功能時會顯示一個對話框。若是用戶選擇「Yes」,設備會在指定時間事後變爲能夠被發現的。Activity的onActivityResult()回調函數被調用,結果碼等於設備變爲能夠被發現所需時長。若是用戶選擇「No」或者有錯誤發生,結果碼會是Activity.RESULT_CANCELLED。

提示:若是Bluetooth沒有啓用,啓用Bluetooth可被發現功能可以自動開啓Bluetooth。
在規定的時間內,設備會靜靜的保持能夠被發現模式。若是想在能夠被發現模式被更改時受到通知,能夠用ACTION_SCAN_MODE_CHANGEDIntent註冊一個BroadcastReceiver,包含額外的字段信息EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE分別表示新舊掃描模式,其可能的值爲SCAN_MODE_CONNECTABLE_DISCOVERABLE(discoverablemode),SCAN_MODE_CONNECTABLE(notin discoverable mode but still able to receiveconnections),SCAN_MODE_NONE(notin discoverable mode and unable to receive connections)。
若是隻須要鏈接遠程設備就不須要打開設備的能夠被發現功能。只在應用做爲一個服務器socket的宿主用來接收進來的鏈接時才須要使能能夠被發現功能,由於遠程設備在初始化鏈接前必須先發現了你的設備。


6. ConnectingDevices服務

爲了創建兩個設備之間的應用的鏈接,須要完成服務器端和客戶端,由於一個設備必須打開一個服務器socket而另一個設備必須初始化鏈接(用服務器端的MAC地址)。服務器和客戶端在各自得到一個基於同一個RFCOMM信道的已鏈接的BluetoothSocket對象後就被認爲鏈接已經創建。這個時候,雙方設備能夠獲取輸入輸出流,數據傳輸能夠開始了。本節描述如何在兩個設備之間初始化鏈接。
服務器設備和客戶端設備用不一樣的方式獲取各自須要的BluetoothSocket對象。服務器端的在接收一個進來的鏈接時獲取到,客戶端的在打開一個與服務器端的RFCOMM信道的時候獲取到。
一個實現技巧是自動把每一個設備做爲服務器,這樣就擁有了一個打開的socket用來偵聽鏈接。而後任一設備就可以發起與另外一個設備的鏈接,併成爲客戶端。另外,一個設備也能夠明確的成爲「host」,並打開一個服務端socket,另外一個設備能夠簡單的發起鏈接。
注意:若是兩個設備以前沒有配對,那麼在鏈接處理過程當中Android應用框架會自動顯示一個配對請求的通知或對話框給用戶。所以,當嘗試鏈接設備時,應用不須要關心設備是否已經配對。RFCOMM鏈接會阻塞直到用戶成功將設備配對(若是用戶拒絕配對或者配對超時了鏈接會失敗)。

(1)Connectingas a server

若是要鏈接兩個設備,其中一個必須充當服務器,經過持有一個打開的BluetoothServerSocket對象。服務器socket的做用是偵聽進來的鏈接,若是一個鏈接被接受,提供一個鏈接好的BluetoothSocket對象。從BluetoothServerSocket獲取到BluetoothSocket對象以後,BluetoothServerSocket就能夠(也應該)丟棄了,除非你還要用它來接收更多的鏈接。

下面是創建服務器socket和接收一個鏈接的基本步驟:
①經過調用listenUsingRfcommWithServiceRecord(String, UUID)獲得一個BluetoothServerSocket對象。
該字符串爲服務的識別名稱,系統將自動寫入到一個新的服務發現協議(SDP)數據庫接入口到設備上的(名字是任意的,能夠簡單地是應用程序的名稱)項。UUID也包括在SDP接入口中,將是客戶端設備鏈接協議的基礎。也就是說,當客戶端試圖鏈接本設備,它將攜帶一個UUID用來惟一標識它要鏈接的服務,UUID必須匹配,鏈接纔會被接受。
② 經過調用accept()來偵聽鏈接請求。
這是一個阻塞的調用,知道有鏈接進來或者產生異常纔會返回。只有遠程設備發送一個鏈接請求,而且攜帶的UUID與偵聽它socket註冊的UUID匹配,鏈接請求才會被接受。若是成功,accept()將返回一個鏈接好的BluetoothSocket對象。
③ 除非須要再接收另外的鏈接,不然的話調用close()。
close()釋放serversocket和它的資源,但不會關閉鏈接accept()返回的鏈接好的BluetoothSocket對象。與TCP/IP不一樣,RFCOMM同一時刻一個信道只容許一個客戶端鏈接,所以大多數狀況下意味着在BluetoothServerSocket接受一個鏈接請求後應該當即調用close()。
accept()調用不該該在主ActivityUI線程中進行,由於這是個阻塞的調用,會妨礙其餘的交互。常常是在在一個新線程中作BluetoothServerSocket或BluetoothSocket的全部工做來避免UI線程阻塞。注意全部BluetoothServerSocket或BluetoothSocket的方法都是線程安全的。

下面是一個簡單的接受鏈接的服務器組件代碼示例:

privateclass AcceptThread extends Thread {
    privatefinal BluetoothServerSocket mmServerSocket;
    publicAcceptThread() {
        //Use a temporary object that is later assigned to mmServerSocket,
        //because mmServerSocket is final
        BluetoothServerSockettmp = null;
        try{
            //MY_UUID is the app's UUID string, also used by the client code
            tmp= mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME,MY_UUID);
        }catch (IOException e) { }
        mmServerSocket= tmp;
    }

    publicvoid run() {
        BluetoothSocketsocket = null;
        //Keep listening until exception occurs or a socket is returned
        while(true) {
            try{
                socket= mmServerSocket.accept();
            }catch (IOException e) {
                break;
            }
            //If a connection was accepted
            if(socket != null) {
                //Do work to manage the connection (in a separate thread)
                manageConnectedSocket(socket);
                mmServerSocket.close();
                break;
            }
        }
    }

    /**Will cancel the listening socket, and cause the thread to finish */
    publicvoid cancel() {
        try{
            mmServerSocket.close();
        }catch (IOException e) { }
    }
}

本例中,僅僅只接受一個進來的鏈接,一旦鏈接被接受獲取到BluetoothSocket,就發送獲取到的BluetoothSocket給一個單獨的線程,而後關閉BluetoothServerSocket並跳出循環。
注意:accept()返回BluetoothSocket後,socket已經鏈接了,因此在客戶端不該該調用connnect()。
manageConnectedSocket()是一個虛方法,用來初始化線程好傳輸數據。
一般應該在處理完偵聽到的鏈接後當即關閉BluetoothServerSocket。在本例中,close()在獲得BluetoothSocket後立刻被調用。還須要在線程中提供一個公共的方法來關閉私有的BluetoothSocket,中止服務端socket的偵聽。

(2) Connecting as a client

爲了實現與遠程設備的鏈接,你必須首先得到一個表明遠程設備BluetoothDevice對象。而後使用BluetoothDevice對象來獲取一個BluetoothSocket來實現來接。
下面是基本的步驟:
①用BluetoothDevice調用createRfcommSocketToServiceRecord(UUID)獲取一個BluetoothSocket對象。
這個初始化的BluetoothSocket會鏈接到BluetoothDevice。UUID必須匹配服務器設備在打開BluetoothServerSocket時用到的UUID(用listenUsingRfcommWithServiceRecord(String,UUID))。能夠簡單的生成一個UUID串而後在服務器和客戶端都使用該UUID。
② 調用connect()完成鏈接
當調用這個方法的時候,系統會在遠程設備上完成一個SDP查找來匹配UUID。若是查找成功而且遠程設備接受鏈接,就共享RFCOMM信道,connect()會返回。這也是一個阻塞的調用,無論鏈接失敗仍是超時(12秒)都會拋出異常。
注意:要確保在調用connect()時沒有同時作設備查找,若是在查找設備,該鏈接嘗試會顯著的變慢,慢得相似失敗了。

下面是一個完成Bluetooth鏈接的樣例線程:

private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
    public ConnectThread(BluetoothDevice device) {
        //Use a temporary object that is later assigned to mmSocket,
        //because mmSocket is final
        BluetoothSockettmp = null;
        mmDevice= device;
        //Get a BluetoothSocket to connect with the given BluetoothDevice
        try{
            //MY_UUID is the app's UUID string, also used by the server code
            tmp= device.createRfcommSocketToServiceRecord(MY_UUID);
        }catch (IOException e) { }
        mmSocket= tmp;
    }
    
    publicvoid run() {
        //Cancel discovery because it will slow down the connection
        mBluetoothAdapter.cancelDiscovery();
        try{
            //Connect the device through the socket. This will block
            //until it succeeds or throws an exception
            mmSocket.connect();
        }catch (IOException connectException) {
            //Unable to connect; close the socket and get out
            try{
                mmSocket.close();
            }catch (IOException closeException) { }
        return;
        }
        //Do work to manage the connection (in a separate thread)
        manageConnectedSocket(mmSocket);
    }
    
    /**Will cancel an in-progress connection, and close the socket */
    publicvoid cancel() {
        try{
            mmSocket.close();
        }catch (IOException e) { }
    }
}

注意到cancelDiscovery()在鏈接操做前被調用。在鏈接以前,無論搜索有沒有進行,該調用都是安全的,不須要確認(固然若是有要確認的需求,能夠調用isDiscovering())。manageConnectedSocket()是一個虛方法,用來初始化線程好傳輸數據。
在對BluetoothSocket的處理完成後,記得調用close()來關閉鏈接的socket和清理全部的內部資源。


7. Managing aConnection服務

若是已經鏈接了兩個設備,他們都已經擁有各自的鏈接好的BluetoothSocket對象。那就是一個有趣的開始,由於你能夠在設備間共享數據了。使用BluetoothSocket,傳輸任何數據一般來講都很容易了:
(1) 經過socket獲取輸入輸出流來處理傳輸(分別使用getInputStream()和getOutputStream())。
(2) 用read(byte[])和write(byte[])來實現讀寫。僅此而已。
固然,仍是有不少細節須要考慮的。首要的,須要用一個專門的線程來實現流的讀寫。只是很重要的,由於read(byte[])和write(byte[])都是阻塞的調用。read(byte[])會阻塞直到流中有數據可讀。write(byte[])一般不會阻塞,可是若是遠程設備調用read(byte[])不夠快致使中間緩衝區滿,它也可能阻塞。因此線程中的主循環應該用於讀取InputStream。線程中也應該有單獨的方法用來完成寫OutputStream。

下面是一個如上面描述那樣的例子:

private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;

    public ConnectedThread(BluetoothSocket socket) {
        mmSocket= socket;
        InputStreamtmpIn = null;
        OutputStreamtmpOut = null;
        //Get the input and output streams, using temp objects because
        //member streams are final
        try{
            tmpIn= socket.getInputStream();
            tmpOut= socket.getOutputStream();
        }catch (IOException e) { }
        mmInStream= tmpIn;
        mmOutStream= tmpOut;
    }

    public void run() {
        byte[]buffer = new byte[1024]; // buffer store for the stream
        intbytes; // bytes returned from read()
        //Keep listening to the InputStream until an exception occurs
        while(true) {
            try{
                //Read from the InputStream
                bytes= mmInStream.read(buffer);
                //Send the obtained bytes to the UI Activity
                mHandler.obtainMessage(MESSAGE_READ,bytes, -1, buffer)
                .sendToTarget();
            }catch (IOException e) {
                break;
            }
        }
    }

    /*Call this from the main Activity to send data to the remote device */
    public void write(byte[] bytes) {
        try{
            mmOutStream.write(bytes);
        }catch (IOException e) { }
    }
        /*Call this from the main Activity to shutdown the connection */
    public void cancel() {
        try{
            mmSocket.close();
        }catch (IOException e) { }
    }
}

構造函數中獲得須要的流,一旦執行,線程會等待從InputStream來的數據。當read(byte[])返回從流中讀到的字節後,數據經過父類的成員Handler被送到主Activity,而後繼續等待讀取流中的數據。向外發送數據只需簡單的調用線程的write()方法。
線程的cancel()方法時很重要的,以便鏈接能夠在任什麼時候候經過關閉BluetoothSocket來終止。它應該總在處理完Bluetooth鏈接後被調用。


9. Working withProfiles服務

從Android3.0開始,Bluetooth API就包含了對Bluetoothprofiles的支持。Bluetoothprofile是基於藍牙的設備之間通訊的無線接口規範。例如Hands-Freeprofile(免提模式)。若是移動電話要鏈接一個無線耳機,他們都要支持Hands-Freeprofile。
你在你的類裏能夠完成BluetoothProfile接口來支持某一Bluetoothprofiles。AndroidBluetooth API完成了下面的Bluetoothprofile:
Headset:Headsetprofile提供了移動電話上的Bluetooth耳機支持。Android提供了BluetoothHeadset類,它是一個協議,用來經過IPC(interprocess communication)控制BluetoothHeadset Service。BluetoothHeadset既包含BluetoothHeadset profile也包含Hands-Freeprofile,還包括對AT命令的支持。
A2DP:AdvancedAudio Distribution Profile (A2DP)profile,高級音頻傳輸模式。Android提供了BluetoothA2dp類,這是一個經過IPC來控制BluetoothA2DP的協議。

下面是使用profile的基本步驟:
① 獲取默認的Bluetooth適配器。
② 使用getProfileProxy()來創建一個與profile相關的profile協議對象的鏈接。在下面的例子中,profile協議對象是BluetoothHeadset的一個實例。
③ 設置BluetoothProfile.ServiceListener。該listener通知BluetoothProfileIPC客戶端,當客戶端鏈接或斷連服務器的時候。
④ 在onServiceConnected()內,獲得一個profile協議對象的句柄。
⑤ 一旦擁有了profile協議對象,就能夠用它來監控鏈接的狀態,完成於該profile相關的其餘操做。

例如,下面的代碼片斷顯示如何鏈接到一個BluetoothHeadset協議對象,用來控制Headsetprofile:

BluetoothHeadsetmBluetoothHeadset;
//Get the default adapter
BluetoothAdaptermBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//Establish connection to the proxy.
mBluetoothAdapter.getProfileProxy(context,mProfileListener, BluetoothProfile.HEADSET);
private BluetoothProfile.ServiceListener mProfileListener = newBluetoothProfile.ServiceListener() {
    public void onServiceConnected(int profile, BluetoothProfile proxy) {
        if(profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset= (BluetoothHeadset) proxy;
        }
    }
    public void onServiceDisconnected(int profile) {
        if(profile == BluetoothProfile.HEADSET) {
         mBluetoothHeadset= null;
        }
    }
};
//... call functions on mBluetoothHeadset
//Close proxy connection after use.
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);
相關文章
相關標籤/搜索