對于Android的英文幫助文檔,總是看了記不住,,遠不如對中文資料那么印象深,,所以下面的敘述都是對Android幫助文檔Bluetooth的翻譯。
一,、Bluetooth
Android平臺包含了對Bluetooth協(xié)議棧的支持,,允許機器通過Bluetooth設(shè)備進行無線數(shù)據(jù)交換。應(yīng)用框架通過Android
Bluetooth API訪問Bluetooth功能模塊,。 這些API能讓應(yīng)用無線連接其他Bluetooth設(shè)備,實現(xiàn)點對點和多點之間的通信,。
運用藍牙API,,Android應(yīng)用程序可以完成如下操作:
1、掃描其他Bluetooth設(shè)備,。
2,、查詢配對Bluetooth設(shè)備的本地Bluetooth適配器。
3,、建立RFCOMM通道,。
4、通過服務(wù)探索連接到其他設(shè)備,。
5,、與其他設(shè)備進行數(shù)據(jù)傳輸。
6,、管理多個連接
二,、The Basics
本文描述如何使用Android Bluetooth APIs完成Bluetooth通訊的4個必要任務(wù):設(shè)置Bluetooth,搜尋本地配對或者可用的Bluetooth設(shè)備,,連接Bluetooth設(shè)備,,與Bluetooth設(shè)備進行數(shù)據(jù)傳輸。
所有可用的Bluetooth APIs都包含在android.bluetooth包中,。下面是建立Bluetooth連接需要用到的類和接口的總結(jié):
1,、BluetoothAdapter
描述本地Bluetooth適配器(Bluetooth接收器)。BluetoothAdapter是所有Bluetooth相關(guān)活動的入口,。運用
BluetoothAdapter可以發(fā)現(xiàn)其他Bluetooth設(shè)備,,查詢連接(或配對)的設(shè)備列表,用已知MAC地址實例化一個
BluetoothDevice對象,,創(chuàng)建一個BluetoothServerSocket對象偵聽其他設(shè)備的通信,。
2、BluetoothDevice
描述一個遠程Bluetooth設(shè)備,??梢杂盟ㄟ^一個BluetoothSocket請求一個遠程設(shè)備的連接,或者查詢遠程設(shè)備的名稱、地址,、類,、連接狀態(tài)等信息。
3,、BluetoothSocket
描述一個Bluetooth Socket接口(類似于TCP Socket),。應(yīng)用通過InputStream和OutputStream.與另外一個Bluetooth設(shè)備交換數(shù)據(jù),即它是應(yīng)用與另外一個設(shè)備交換數(shù)據(jù)的連接點,。
4,、BluetoothServerSocket
描述一個開放的socket服務(wù)器,用來偵聽連接進來的請求(類似于RCP
ServerSocket),。為了連接兩個Android設(shè)備,,一個設(shè)備必須使用該類來開啟一個socket做服務(wù)器,當另外一個設(shè)備對它發(fā)起連接請求時
并且請求被接受時,,BluetoothServerSocket會返回一個連接的BluetoothSocket對象,。
5、BluetoothClass
描述一個Bluetooth設(shè)備的一般規(guī)格和功能,。這個是用來定義設(shè)備類和它的服務(wù)的只讀屬性集,。然而,它并不是可靠的描述設(shè)備支持的所有Bluetooth配置和服務(wù),,而只是一些設(shè)備類型的有用特征,。
6、BluetoothProfile
描述Bluetooth Profile的接口,。Bluetooth Profile是兩個設(shè)備基于藍牙通訊的無線接口描述,。
(對Bluetooth Profile的詳細解釋,來自百度:為了更容易的保持Bluetooth設(shè)備之間的兼容,,Bluetooth規(guī)范中定義了
Profile,。Profile定義了設(shè)備如何實現(xiàn)一種連接或者應(yīng)用,你可以把Profile理解為連接層或者應(yīng)用層協(xié)議,。
比如,,如果一家公司希望它們的Bluetooth芯片支援所有的Bluetooth耳機,那么它只要支持HeadSet
Profile即可,,而無須考慮該芯片與其它Bluetooth設(shè)備的通訊與兼容性問題,。如果你想購買Bluetooth產(chǎn)品,你應(yīng)該了解你的應(yīng)用需要哪
些Profile來完成,,并且確保你購買的Bluetooth產(chǎn)品支持這些Profile,。)
7、BluetoothHeadset
提供移動電話的Bluetooth耳機支持,。包括Bluetooth耳機和Hands-Free (v1.5) profiles,。
8,、BluetoothA2dp
定義兩個設(shè)備間如何通過Bluetooth連接進行高質(zhì)量的音頻傳輸。
A2DP(Advanced Audio Distribution Profile):高級音頻傳輸模式,。
9,、BluetoothProfile.ServiceListener
一個接口描述,在與服務(wù)連接或者斷連接的時候通知BluetoothProfile IPC(這是內(nèi)部服務(wù)運行的一個特定的模式<profile>),。
三,、Bluetooth Permissions
要使用Bluetooth功能,至少需要2個Bluetooth權(quán)限:BLUETOOTH和BLUETOOTH_ADMIN.
BLUETOOTH:用來授權(quán)任何Bluetooth通信,,如請求連接,,接受連接,傳輸數(shù)據(jù)等,。
BLUETOOTH_ADMIN:用來授權(quán)初始化設(shè)備搜索或操作Bluetooth設(shè)置,。大多數(shù)應(yīng)用需要它的唯一場合是用來搜索本地Bluetooth設(shè)
備。本授權(quán)的其他功能不應(yīng)該被使用,,除非是需要修改Bluetooth設(shè)置的“power manager(電源管理)”應(yīng)用,。
注意:需要BLUETOOTH_ADMIN權(quán)限的場合,,BLUETOOTH權(quán)限也是必需的,。
需要在manifest文件中聲明Bluetooth權(quán)限,示例如下:
<manifest ... >
<uses-permission android:name="android.permission.BLUETOOTH" />
...
</manifest>
四,、Setting Up Bluetooth
在用Bluetooth通訊之前,,需要確認設(shè)備是否支持Bluetooth,如果支持,,還得確保Bluetooth是可用的,。
如果設(shè)備不支持Bluetooth,需要優(yōu)雅的將Bluetooth置為不可用,。如果支持Bluetooth,,但沒有開啟,可以在應(yīng)用中請求開啟Bluetooth,。該設(shè)置使用BluetoothAdapter.通過兩個步驟完成,。
1、獲取BluetoothAdapter
BluetoothAdapter是每個Bluetooth的Activity都需要用到的,。用靜態(tài)方法getDefaultAdapter()獲取
BluetoothAdapter,,返回一個擁有Bluetooth
適配器的BluetoothAdapter對象。如果返回null,,說明設(shè)備不支持Bluetooth,,關(guān)于Bluetooth的故事到此就結(jié)束了(因為
你干不了什么了)。示例:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// Device does not support Bluetooth
}
2,、Enable Bluetooth
接下來,,就是確保Bluetooth功能是開啟的,。調(diào)用isEnabled()來檢查Bluetooth當前是否是開啟的。用
ACTION_REQUEST_ENABLE action
Intent調(diào)用startActivityForResult()來請求開啟Bluetooth,,這會通過系統(tǒng)設(shè)置發(fā)出一個Bluetooth使能請求
(并且不會停止本應(yīng)用程序),。示例:
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
用戶請求使能Bluetooth時,會顯示一個對話框,。選擇“Yes”,,系統(tǒng)會使能Bluetooth,并且焦點會返回你的應(yīng)用程序,。
如果使能Bluetooth成功,,你的Activity會在onActivityResult()回調(diào)函數(shù)中收到RESULT_OK的結(jié)果碼。如果Bluetooth使能因發(fā)生錯誤(或用戶選擇了“No”)而失敗,,收到的結(jié)果碼將是RESULT_CANCELED,。
作為可選項,應(yīng)用也可以偵聽ACTION_STATE_CHANGED broadcast
Intent,,這樣無論Bluetooth狀態(tài)何時被改變系統(tǒng)都會發(fā)出broadcast(廣播),。該廣播包含附加的字段信息EXTRA_STATE和
EXTRA_PREVIOUS_STATE分別代表新的和舊的Bluetooth狀態(tài),該字段可能的值為STATE_TURNING_ON,
STATE_ON, STATE_TURNING_OFF,
和STATE_OFF,。應(yīng)用運行時,,偵聽ACTION_STATE_CHANGED廣播來檢測Bluetooth狀態(tài)的改變是很有用的。
提示:啟用Bluetooth可被發(fā)現(xiàn)功能能夠自動開啟Bluetooth,。如果在完成Activity之前需要持續(xù)的使能Bluetooth可被發(fā)現(xiàn)功能,,那么上面的第2步就可以忽略。
五,、Finding Devices
使用BluetoothAdapter可以通過設(shè)備搜索或查詢配對設(shè)備找到遠程Bluetooth設(shè)備,。
Device
discovery(設(shè)備搜索)是一個掃描搜索本地已使能Bluetooth設(shè)備并且從搜索到的設(shè)備請求一些信息的過程(有時候會收到類似
“discovering”,“inquiring”或“scanning”),。但是,,搜索到的本地Bluetooth設(shè)備只有在打開被發(fā)現(xiàn)功能后才會響
應(yīng)一個discovery請求,響應(yīng)的信息包括設(shè)備名,,類,,唯一的MAC地址。發(fā)起搜尋的設(shè)備可以使用這些信息來初始化跟被發(fā)現(xiàn)的設(shè)備的連接,。
一旦與遠程設(shè)備的第一次連接被建立,,一個pairing請求就會自動提交給用戶。如果設(shè)備已配對,,配對設(shè)備的基本信息(名稱,,類,MAC地址)就被保存下
來了,,能夠使用Bluetooth
API來讀取這些信息,。使用已知的遠程設(shè)備的MAC地址,,連接可以在任何時候初始化而不必先完成搜索(當然這是假設(shè)遠程設(shè)備是在可連接的空間范圍內(nèi))。
需要記住,,配對和連接是兩個不同的概念:
配對意思是兩個設(shè)備相互意識到對方的存在,,共享一個用來鑒別身份的鏈路鍵(link-key),能夠與對方建立一個加密的連接,。
連接意思是兩個設(shè)備現(xiàn)在共享一個RFCOMM信道,,能夠相互傳輸數(shù)據(jù)。
目前Android Bluetooth API's要求設(shè)備在建立RFCOMM信道前必須配對(配對是在使用Bluetooth API初始化一個加密連接時自動完成的),。
下面描述如何查詢已配對設(shè)備,,搜索新設(shè)備。
注意:Android的電源設(shè)備默認是不能被發(fā)現(xiàn)的,。用戶可以通過系統(tǒng)設(shè)置讓它在有限的時間內(nèi)可以被發(fā)現(xiàn),,或者可以在應(yīng)用程序中要求用戶使能被發(fā)現(xiàn)功能。
1,、Querying paired devices
在搜索設(shè)備前,,查詢配對設(shè)備看需要的設(shè)備是否已經(jīng)是已經(jīng)存在是很值得的,可以調(diào)用getBondedDevices()來做到,,該函數(shù)會返回一個描述配對
設(shè)備BluetoothDevice的結(jié)果集,。例如,可以使用ArrayAdapter查詢所有配對設(shè)備然后顯示所有設(shè)備名給用戶:
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,、Discovering devices
要開始搜索設(shè)備,,只需簡單的調(diào)用startDiscovery(),。該函數(shù)時異步的,調(diào)用后立即返回,,返回值表示搜索是否成功開始,。搜索處理通常包括一個12秒鐘的查詢掃描,然后跟隨一個頁面顯示搜索到設(shè)備Bluetooth名稱,。
應(yīng)用中可以注冊一個帶CTION_FOUND
Intent的BroadcastReceiver,,搜索到每一個設(shè)備時都接收到消息。對于每一個設(shè)備,,系統(tǒng)都會廣播ACTION_FOUND
Intent,,該Intent攜帶著而外的字段信息EXTRA_DEVICE和EXTRA_CLASS,分別包含一個BluetoothDevice和一
個BluetoothClass,。下面的示例顯示如何注冊和處理設(shè)備被發(fā)現(xiàn)后發(fā)出的廣播:
// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = 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
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
警告:完成設(shè)備搜索對于Bluetooth適配器來說是一個重量級的處理,,要消耗大量它的資源。一旦你已經(jīng)找到一個設(shè)備來連接,,請確保你在嘗試連接
前使用了cancelDiscovery()來停止搜索,。同樣,,如果已經(jīng)保持了一個連接的時候,同時執(zhí)行搜索設(shè)備將會顯著的降低連接的帶寬,,所以在連接的
時候不應(yīng)該執(zhí)行搜索發(fā)現(xiàn),。
3、Enabling discoverability
如果想讓本地設(shè)備被其他設(shè)備發(fā)現(xiàn),,可以帶ACTION_REQUEST_DISCOVERABLE action
Intent調(diào)用startActivityForResult(Intent, int)
方法,。該方法會提交一個請求通過系統(tǒng)剛設(shè)置使設(shè)備出于可以被發(fā)現(xiàn)的模式(而不影響應(yīng)用程序)。默認情況下,,設(shè)備在120秒后變?yōu)榭梢员话l(fā)現(xiàn)的,。可以通過額
外增加EXTRA_DISCOVERABLE_DURATION
Intent自定義一個值,,最大值是3600秒,,0表示設(shè)備總是可以被發(fā)現(xiàn)的(小于0或者大于3600則會被自動設(shè)置為120秒)。下面示例設(shè)置時間為
300:
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
詢問用戶是否允許打開設(shè)備可以被發(fā)現(xiàn)功能時會顯示一個對話框,。如果用戶選擇“Yes”,,設(shè)備會在指定時間過后變?yōu)榭梢员话l(fā)現(xiàn)的。Activity的
onActivityResult()回調(diào)函數(shù)被調(diào)用,,結(jié)果碼等于設(shè)備變?yōu)榭梢员话l(fā)現(xiàn)所需時長,。如果用戶選擇“No”或者有錯誤發(fā)生,結(jié)果碼會是
Activity.RESULT_CANCELLED,。
提示:如果Bluetooth沒有啟用,,啟用Bluetooth可被發(fā)現(xiàn)功能能夠自動開啟Bluetooth。
在規(guī)定的時間內(nèi),,設(shè)備會靜靜的保持可以被發(fā)現(xiàn)模式,。如果想在可以被發(fā)現(xiàn)模式被更改時受到通知,可以用ACTION_SCAN_MODE_CHANGED
Intent注冊一個BroadcastReceiver,,包含額外的字段信息EXTRA_SCAN_MODE和
EXTRA_PREVIOUS_SCAN_MODE分別表示新舊掃描模式,,其可能的值為
SCAN_MODE_CONNECTABLE_DISCOVERABLE(discoverable
mode),SCAN_MODE_CONNECTABLE(not in discoverable
mode but still able to receive connections),,SCAN_MODE_NONE(not in
discoverable mode and unable to receive connections),。
如果只需要連接遠程設(shè)備就不需要打開設(shè)備的可以被發(fā)現(xiàn)功能。只在應(yīng)用作為一個服務(wù)器socket的宿主用來接收進來的連接時才需要使能可以被發(fā)現(xiàn)功能,,因為遠程設(shè)備在初始化連接前必須先發(fā)現(xiàn)了你的設(shè)備,。
六、Connecting Devices
為了建立兩個設(shè)備之間的應(yīng)用的連接,,需要完成服務(wù)器端和客戶端,,因為一個設(shè)備必須打開一個服務(wù)器socket而另外一個設(shè)備必須初始化連接(用服務(wù)器端的
MAC地址)。服務(wù)器和客戶端在各自獲得一個基于同一個RFCOMM信道的已連接的BluetoothSocket對象后就被認為連接已經(jīng)建立,。這個時
候,,雙方設(shè)備可以獲取輸入輸出流,,數(shù)據(jù)傳輸可以開始了。本節(jié)描述如何在兩個設(shè)備之間初始化連接,。
服務(wù)器設(shè)備和客戶端設(shè)備用不同的方式獲取各自需要的BluetoothSocket對象,。服務(wù)器端的在接收一個進來的連接時獲取到,客戶端的在打開一個與服務(wù)器端的RFCOMM信道的時候獲取到,。
一個實現(xiàn)技巧是自動把每個設(shè)備作為服務(wù)器,,這樣就擁有了一個打開的socket用來偵聽連接。然后任一設(shè)備就能夠發(fā)起與另一個設(shè)備的連接,,并成為客戶端,。另外,一個設(shè)備也可以明確的成為“host”,,并打開一個服務(wù)端socket,,另一個設(shè)備可以簡單的發(fā)起連接。
注意:如果兩個設(shè)備之前沒有配對,,那么在連接處理過程中Android應(yīng)用框架會自動顯示一個配對請求的通知或?qū)υ捒蚪o用戶,。因此,當嘗試連接設(shè)備時,,應(yīng)
用不需要關(guān)心設(shè)備是否已經(jīng)配對,。RFCOMM連接會阻塞直到用戶成功將設(shè)備配對(如果用戶拒絕配對或者配對超時了連接會失敗)。
1,、Connecting as a server
如果要連接兩個設(shè)備,,其中一個必須充當服務(wù)器,通過持有一個打開的BluetoothServerSocket對象,。服務(wù)器socket的作用是偵聽進來
的連接,,如果一個連接被接受,提供一個連接好的BluetoothSocket對象,。從BluetoothServerSocket獲取到
BluetoothSocket對象之后,,BluetoothServerSocket就可以(也應(yīng)該)丟棄了,,除非你還要用它來接收更多的連接,。
下面是建立服務(wù)器socket和接收一個連接的基本步驟:
1.通過調(diào)用listenUsingRfcommWithServiceRecord(String, UUID)得到一個BluetoothServerSocket對象。
該字符串為服務(wù)的識別名稱,,系統(tǒng)將自動寫入到一個新的服務(wù)發(fā)現(xiàn)協(xié)議(SDP)數(shù)據(jù)庫接入口到設(shè)備上的(名字是任意的,,可以簡單地是應(yīng)用程序的名稱)項。
UUID也包括在SDP接入口中,,將是客戶端設(shè)備連接協(xié)議的基礎(chǔ),。也就是說,當客戶端試圖連接本設(shè)備,,它將攜帶一個UUID用來唯一標識它要連接的服
務(wù),,UUID必須匹配,,連接才會被接受。
2.通過調(diào)用accept()來偵聽連接請求,。
這是一個阻塞的調(diào)用,,知道有連接進來或者產(chǎn)生異常才會返回。只有遠程設(shè)備發(fā)送一個連接請求,,并且攜帶的UUID與偵聽它socket注冊的UUID匹配,,連接請求才會被接受。如果成功,,accept()將返回一個連接好的BluetoothSocket對象,。
3.除非需要再接收另外的連接,否則的話調(diào)用close(),。
close()釋放server
socket和它的資源,,但不會關(guān)閉連接accept()返回的連接好的BluetoothSocket對象。與TCP/IP不同,,RFCOMM同一時刻
一個信道只允許一個客戶端連接,,因此大多數(shù)情況下意味著在BluetoothServerSocket接受一個連接請求后應(yīng)該立即調(diào)用close()。
accept()調(diào)用不應(yīng)該在主Activity
UI線程中進行,,因為這是個阻塞的調(diào)用,,會妨礙其他的交互。經(jīng)常是在在一個新線程中做BluetoothServerSocket或
BluetoothSocket的所有工作來避免UI線程阻塞,。注意所有BluetoothServerSocket或BluetoothSocket的
方法都是線程安全的,。
示例:
下面是一個簡單的接受連接的服務(wù)器組件代碼示例:
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
// Use a temporary object that is later assigned to mmServerSocket,
// because mmServerSocket is final
BluetoothServerSocket tmp = 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;
}
public void run() {
BluetoothSocket socket = 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 */
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) { }
}
}
本例中,僅僅只接受一個進來的連接,,一旦連接被接受獲取到BluetoothSocket,,就發(fā)送獲取到的BluetoothSocket給一個單獨的線程,然后關(guān)閉BluetoothServerSocket并跳出循環(huán),。
注意:accept()返回BluetoothSocket后,,socket已經(jīng)連接了,所以在客戶端不應(yīng)該呼叫connnect(),。
manageConnectedSocket()是一個虛方法,,用來初始化線程好傳輸數(shù)據(jù)。
通常應(yīng)該在處理完偵聽到的連接后立即關(guān)閉BluetoothServerSocket,。在本例中,,close()在得到BluetoothSocket后
馬上被調(diào)用。還需要在線程中提供一個公共的方法來關(guān)閉私有的BluetoothSocket,,停止服務(wù)端socket的偵聽,。
2、Connecting as a client
為了實現(xiàn)與遠程設(shè)備的連接,你必須首先獲得一個代表遠程設(shè)備BluetoothDevice對象,。然后使用BluetoothDevice對象來獲取一個BluetoothSocket來實現(xiàn)來接,。
下面是基本的步驟:
1.用BluetoothDevice調(diào)用createRfcommSocketToServiceRecord(UUID)獲取一個BluetoothSocket對象。
這個初始化的BluetoothSocket會連接到BluetoothDevice,。UUID必須匹配服務(wù)器設(shè)備在打開
BluetoothServerSocket
時用到的UUID(用listenUsingRfcommWithServiceRecord(String,
UUID)),。可以簡單的生成一個UUID串然后在服務(wù)器和客戶端都使用該UUID,。
2.調(diào)用connect()完成連接
當調(diào)用這個方法的時候,,系統(tǒng)會在遠程設(shè)備上完成一個SDP查找來匹配UUID。如果查找成功并且遠程設(shè)備接受連接,,就共享RFCOMM信道,,connect()會返回。這也是一個阻塞的調(diào)用,,不管連接失敗還是超時(12秒)都會拋出異常,。
注意:要確保在調(diào)用connect()時沒有同時做設(shè)備查找,如果在查找設(shè)備,,該連接嘗試會顯著的變慢,,慢得類似失敗了。
實例:
下面是一個完成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
BluetoothSocket tmp = 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;
}
public void 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 */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
注意到cancelDiscovery()在連接操作前被調(diào)用,。在連接之前,,不管搜索有沒有進行,該調(diào)用都是安全的,,不需要確認(當然如果有要確認的需求,,可以調(diào)用isDiscovering())。
manageConnectedSocket()是一個虛方法,,用來初始化線程好傳輸數(shù)據(jù),。
在對BluetoothSocket的處理完成后,記得調(diào)用close()來關(guān)閉連接的socket和清理所有的內(nèi)部資源,。
七,、Managing a Connection
如果已經(jīng)連接了兩個設(shè)備,他們都已經(jīng)擁有各自的連接好的BluetoothSocket對象,。那就是一個有趣的開始,,因為你可以在設(shè)備間共享數(shù)據(jù)了。使用BluetoothSocket,,傳輸任何數(shù)據(jù)通常來說都很容易了:
1.通過socket獲取輸入輸出流來處理傳輸(分別使用getInputStream()和getOutputStream()),。
2.用read(byte[])和write(byte[])來實現(xiàn)讀寫。
僅此而已,。
當然,還是有很多細節(jié)需要考慮的,。首要的,,需要用一個專門的線程來實現(xiàn)流的讀寫,。只是很重要的,因為read(byte[])和
write(byte[])都是阻塞的調(diào)用,。read(byte[])會阻塞直到流中有數(shù)據(jù)可讀,。write(byte[])通常不會阻塞,但是如果遠程
設(shè)備調(diào)用read(byte[])不夠快導(dǎo)致中間緩沖區(qū)滿,,它也可能阻塞,。所以線程中的主循環(huán)應(yīng)該用于讀取InputStream。線程中也應(yīng)該有單獨的
方法用來完成寫OutputStream,。
示例:
下面是一個如上面描述那樣的例子:
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = 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
int bytes; // 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) { }
}
}
構(gòu)造函數(shù)中得到需要的流,,一旦執(zhí)行,線程會等待從InputStream來的數(shù)據(jù),。當read(byte[])返回從流中讀到的字節(jié)后,,數(shù)據(jù)通過父類的成員Handler被送到主Activity,然后繼續(xù)等待讀取流中的數(shù)據(jù),。
向外發(fā)送數(shù)據(jù)只需簡單的調(diào)用線程的write()方法,。
線程的cancel()方法時很重要的,以便連接可以在任何時候通過關(guān)閉BluetoothSocket來終止,。它應(yīng)該總在處理完Bluetooth連接后被調(diào)用,。
八、Working with Profiles
從Android 3.0開始,,Bluetooth API就包含了對Bluetooth profiles的支持,。Bluetooth
profile是基于藍牙的設(shè)備之間通信的無線接口規(guī)范。例如Hands-Free
profile(免提模式),。如果移動電話要連接一個無線耳機,,他們都要支持Hands-Free profile。
你在你的類里可以完成BluetoothProfile接口來支持某一Bluetooth profiles,。Android Bluetooth API完成了下面的Bluetooth profile:
Headset:Headset
profile提供了移動電話上的Bluetooth耳機支持,。Android提供了BluetoothHeadset類,它是一個協(xié)議,,用來通過
IPC(interprocess communication)控制Bluetooth Headset
Service,。BluetoothHeadset既包含Bluetooth Headset profile也包含Hands-Free
profile,還包括對AT命令的支持,。
A2DP:Advanced Audio Distribution Profile (A2DP) profile,,高級音頻傳輸模式。Android提供了BluetoothA2dp類,,這是一個通過IPC來控制Bluetooth A2DP的協(xié)議,。
下面是使用profile的基本步驟:
1.獲取默認的Bluetooth適配器。
2.使用getProfileProxy()來建立一個與profile相關(guān)的profile協(xié)議對象的連接。在下面的例子中,,profile協(xié)議對象是BluetoothHeadset的一個實例,。
3.設(shè)置BluetoothProfile.ServiceListener。該listener通知BluetoothProfile IPC客戶端,,當客戶端連接或斷連服務(wù)器的時候,。
4.在onServiceConnected()內(nèi),得到一個profile協(xié)議對象的句柄,。
5. 一旦擁有了profile協(xié)議對象,,就可以用它來監(jiān)控連接的狀態(tài),完成于該profile相關(guān)的其他操作,。
例如,,下面的代碼片段顯示如何連接到一個BluetoothHeadset協(xié)議對象,用來控制Headset profile:
BluetoothHeadset mBluetoothHeadset;
// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// Establish connection to the proxy.
mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.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);
1,、Vendor-specific AT commands
從Android 3.0開始,,應(yīng)用程序可以注冊偵聽預(yù)定義的Vendor-specific AT命令這樣的系統(tǒng)廣播(如Plantronics
+XEVENT
command)。例如,,應(yīng)用可以接收到一個廣播,,該廣播表明連接的設(shè)備電量過低,然后通知用戶做好其他需要的操作,。創(chuàng)建一個帶
ACTION_VENDOR_SPECIFIC_HEADSET_EVENT intent的broadcast receiver來為耳機處理
Vendor-specific AT commands,。