藍牙我們應該很早就聽過,最常見的就是原來我們偶爾通過手機上的藍牙來傳輸文件。貌似在低功耗藍牙出現之前,藍牙我們使用的并不多,藍牙的產品貌似也不是很多。2010年6月30號藍牙技術聯盟推出了低功耗藍牙,經過幾年的發展,市場上基于低功耗系列的硬件產品越來越多,開發硬件,軟件的廠商,公司越來越多。
藍牙發展至今經歷了8個版本的更新。1.1、1.2、2.0、2.1、3.0、4.0、4.1、4.2。那么在1.x~3.0之間的我們稱之為傳統藍牙,4.x開始的藍牙我們稱之為低功耗藍牙也就是藍牙ble,當然4.x版本的藍牙也是向下兼容的。android手機必須系統版本4.3及以上才支持BLE API。低功耗藍牙較傳統藍牙,傳輸速度更快,覆蓋范圍更廣,安全性更高,延遲更短,耗電極低等等優點。這也是為什么近年來智能穿戴的東西越來越多,越來越火。還有傳統藍牙與低功耗藍牙通信方式也有所不同,傳統的一般通過socket方式,而低功耗藍牙是通過Gatt協議來實現。若是之前沒做過傳統藍牙開發,也是可以直接上手低功耗藍牙開發的。因為它們在通信協議上都有所改變,關聯不大。當然有興趣的可以去下載些傳統藍牙開發的demo看看,在看看低功耗藍牙的demo。兩者的不同之處自然容易看出來。好了,我們下面開始講低功耗藍牙開發吧。低功耗藍牙也叫BLE,下面都稱之為BLE。
BLE分為三部分:Service,Characteristic,Descriptor。這三部分都用UUID作為唯一標識符。UUID為這種格式:0000ffe1-0000-1000-8000-00805f9b34fb。比如有3個Service,那么就有三個不同的UUID與Service對應。這些UUID都寫在硬件里,我們通過BLE提供的API可以讀取到。
一個BLE終端可以包含多個Service, 一個Service可以包含多個Characteristic,一個Characteristic包含一個value和多個Descriptor,一個Descriptor包含一個Value。Characteristic是比較重要的,是手機與BLE終端交換數據的關鍵,讀取設置數據等操作都是操作Characteristic的相關屬性。
在很多方面,藍牙是一種能夠發送或接受兩個不同的設備之間傳輸的數據。 Android平臺包含了藍牙框架,使設備以無線方式與其他藍牙設備進行數據交換的支持。
Android提供藍牙API來執行這些不同的操作。
掃描其他藍牙設備
獲取配對設備列表
連接到通過服務發現其他設備
Android提供BluetoothAdapter類藍牙通信。通過調用創建的對象的靜態方法getDefaultAdapter()。其語法如下給出。
private BluetoothAdapter BA;
BA = BluetoothAdapter.getDefaultAdapter();
為了使用設備的藍牙,調用下列藍牙ACTION_REQUEST_ENABLE的意圖。其語法如下:
Intent turnOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(turnOn, 0);
開發過Android 藍牙4.0 BLE的同學都知道,Android的藍牙開發有非常多的坑,其中不同機型之間的兼容性就是一個很令人頭疼的問題,很多問題究其原因是在手機端和智能設備之間發送請求指令和回調時,其方式是異步請求的,即請求完立即結束,等待回調,而回調又在不同的線程中,因此當交互比較頻繁并且之間有依賴關系的時候,很容易錯誤。很多Android手機針對這種情況在底層實現了對藍牙請求的同步,即有一個requestQueue,使得一個藍牙請求能夠在上一個藍牙請求的reponse結束之后再發送,保證了順序執行和正確性。但是有些Android手機沒有提供這樣的requestQueue,使得錯誤率大大增加,往往這些問題還不容易發現,從而造成了Android藍牙開發的兼容性問題。所以,要想做好兼容性,我們必須在app端的應用層就應該提供一套完整的requestQueue請求同步策略。
對于之前沒接觸過藍牙開發,現在手上又有個藍牙BLE項目需要做的人,先看下這些概念還是很重要的。因為我之前就是這樣,之前沒有接觸過藍牙方面的開發,然后來了個藍牙的項目,于是就到網上百度了一番,于是有點茫然,產生了幾點疑惑:
1:發現藍牙有傳統藍牙和低功耗藍牙(ble)之分。那么什么是傳統藍牙,什么又是低功耗藍牙?之前又沒做過藍牙開發,我該用哪種方式去開發我這個項目?用最新的 方式的話,傳統方式藍牙開發我是不是該要先了解?
2:藍牙到底有哪些版本?哪些版本稱為傳統藍牙?哪些版本稱為低功耗藍牙?
3:傳統藍牙和低功耗藍牙有什么區別?為什么低功耗藍牙的出現使得智能能穿戴越來越流行?
下面詳細講解Android藍牙模塊的使用方法。
1、使用藍牙的響應權限
XML/HTML代碼
《uses-permission android:name=“android.permission.BLUETOOTH” /》
《uses-permission android:name=“android.permission.BLUETOOTH_ADMIN” /》
2、配置本機藍牙模塊
在這里首先要了解對藍牙操作一個核心類BluetoothAdapter。
Java代碼
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
//直接打開系統的藍牙設置面板
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 0x1);
//直接打開藍牙
adapter.enable();
//關閉藍牙
adapter.disable();
//打開本機的藍牙發現功能(默認打開120秒,可以將時間最多延長至300秒)
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);//設置持續時間(最多300秒)Intent discoveryIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
3、搜索藍牙設備
使用BluetoothAdapter的startDiscovery()方法來搜索藍牙設備。
startDiscovery()方法是一個異步方法,調用后會立即返回。該方法會進行對其他藍牙設備的搜索,該過程會持續12秒。該方法調用后,搜索過程實際上是在一個System Service中進行的,所以可以調用cancelDiscovery()方法來停止搜索(該方法可以在未執行discovery請求時調用)。
請求Discovery后,系統開始搜索藍牙設備,在這個過程中,系統會發送以下三個廣播:
ACTION_DISCOVERY_START:開始搜索
ACTION_DISCOVERY_FINISHED:搜索結束
ACTION_FOUND:找到設備,這個Intent中包含兩個extra fields:EXTRA_DEVICE和EXTRA_CLASS,分別包含BluetooDevice和BluetoothClass。
我們可以自己注冊相應的BroadcastReceiver來接收響應的廣播,以便實現某些功能。
Java代碼
// 創建一個接收ACTION_FOUND廣播的BroadcastReceiver
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 發現設備
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// 從Intent中獲取設備對象
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 將設備名稱和地址放入array adapter,以便在ListView中顯示
mArrayAdapter.add(device.getName() + “\n” + device.getAddress());
}
}
};
// 注冊BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // 不要忘了之后解除綁定
?
4、藍牙Socket通信
如果打算建議兩個藍牙設備之間的連接,則必須實現服務器端與客戶端的機制。當兩個設備在同一個RFCOMM channel下分別擁有一個連接的BluetoothSocket,這兩個設備才可以說是建立了連接。
服務器設備與客戶端設備獲取BluetoothSocket的途徑是不同的。服務器設備是通過accepted一個incoming connection來獲取的,而客戶端設備則是通過打開一個到服務器的RFCOMM channel來獲取的。
服務器端的實現
通過調用BluetoothAdapter的listenUsingRfcommWithServiceRecord(String, UUID)方法來獲取BluetoothServerSocket(UUID用于客戶端與服務器端之間的配對)。
調用BluetoothServerSocket的accept()方法監聽連接請求,如果收到請求,則返回一個BluetoothSocket實例(此方法為block方法,應置于新線程中)。
如果不想在accept其他的連接,則調用BluetoothServerSocket的close()方法釋放資源(調用該方法后,之前獲得的BluetoothSocket實例并沒有close。但由于RFCOMM一個時刻只允許在一條channel中有一個連接,則一般在accept一個連接后,便close掉BluetoothServerSocket)。
Java代碼
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) { }
}
}
客戶端的實現
通過搜索得到服務器端的BluetoothService。
調用BluetoothService的listenUsingRfcommWithServiceRecord(String, UUID)方法獲取BluetoothSocket(該UUID應該同于服務器端的UUID)。
調用BluetoothSocket的connect()方法(該方法為block方法),如果UUID同服務器端的UUID匹配,并且連接被服務器端accept,則connect()方法返回。
注意:在調用connect()方法之前,應當確定當前沒有搜索設備,否則連接會變得非常慢并且容易失敗。
Java代碼
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) { }
}
}
連接管理(數據通信)
分別通過BluetoothSocket的getInputStream()和getOutputStream()方法獲取InputStream和OutputStream。
使用read(bytes[])和write(bytes[])方法分別進行讀寫操作。
注意:read(bytes[])方法會一直block,知道從流中讀取到信息,而write(bytes[])方法并不是經常的block(比如在另一設備沒有及時read或者中間緩沖區已滿的情況下,write方法會block)。
Java代碼
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) { }
}
}
評論