Android ble藍牙開發
BLE介紹
安卓4.3(API 18)爲BLE的核心功能提供平臺支持和API,App可以利用它來發現設備、查詢服務和讀寫特性。相比傳統的藍牙,BLE更顯著的特點是低功耗。這一優點使AndroidApp可以與具有低功耗要求的BLE設備通信,如近距離傳感器、心臟速率監視器、健身設備等。
BLE開發
BLE權限添加
爲了在app中使用藍牙功能,必須聲明藍牙權限BLUETOOTH。利用這個權限去執行藍牙通信,例如請求連接、接受連接、和傳輸數據。如果想讓你的app啓動設備發現或操縱藍牙設置,必須聲明BLUETOOTH_ADMIN權限。注意:如果你使用BLUETOOTH_ADMIN權限,你也必須聲明BLUETOOTH權限。在你的app manifest文件中聲明藍牙權限。
設置BLE
你的app能與BLE通信之前,你需要確認設備是否支持BLE,如果支持,確認已經啓用。雖然現在的手機基本都支持BLE,但是考慮到程序的健碩性,如果設置爲false,這個檢查是必需的。
獲取:所有的藍牙活動都需要藍牙適配器。BluetoothAdapter代表設備本身的藍牙適配器(藍牙無線)。整個系統只有一個藍牙適配器,而且你的app使用它與系統交互。下面的代碼片段顯示瞭如何得到適配器。注意該方法使用getSystemService()]返回BluetoothManager,然後將其用於獲取適配器的一個實例。Android 4.3(API 18)引入BluetoothManager。
// 初始化藍牙適配器
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
有了mBluetoothAdapter之後就可以判斷當前藍牙開關狀態、藍牙未開啓情況下代碼裏面自動開啓藍牙、以及掃描周邊的ble設備
開啓藍牙
接下來,你需要確認藍牙是否開啓。調用isEnabled())去檢測藍牙當前是否開啓。如果該方法返回false,藍牙被禁用。下面的代碼檢查藍牙是否開啓,如果沒有開啓,可以提示用戶去設置開啓藍牙。
// 確保藍牙在設備上可以開啓
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
//藍牙未開啓
}
發現BLE設備
爲了發現BLE設備,使用startLeScan())方法。這個方法需要一個參數BluetoothAdapter.LeScanCallback。你必須實現它的回調函數,那就是返回的掃描結果。因爲掃描非常消耗電量,你應當遵守以下準則:
1·只要找到所需的設備,停止掃描。
2·不要在循環裏掃描,並且對掃描設置時間限制。以前可用的設備可能已經移出範圍,繼續掃描消耗電池電量。
以下代碼顯示如何掃描設備和停止掃描設備
// 10秒後停止尋找.
private static final long SCAN_PERIOD = 10000;
private void scanLeDevice(final boolean enable) {
if (enable) {
// 經過預定掃描期後停止掃描
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
...
}
...
}
如果你只想掃描指定類型的外圍設備,可以改爲調用startLeScan(UUID[], BluetoothAdapter.LeScanCallback)),需要提供你的app支持的GATT services的UUID對象數組。
掃描的信息在LeScallCallback裏面返回
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
//device 裏面包含設備的mac地址和設備的名稱
//scanRecord裏面就是ble設備發出的廣播包數據
//rssi表示ble設備的信號值,該值爲負數,值越大表示信號值越好
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mLeDeviceListAdapter.addDevice(device);
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
};
連接到GATT服務端
與一個BLE設備交互的第一步就是連接它——更具體的,連接到BLE設備上的GATT服務端。爲了連接到BLE設備上的GATT服務端,需要使用connectGatt( )方法。這個方法需要三個參數:一個Context對象,自動連接(boolean值,表示只要BLE設備可用是否自動連接到它),和BluetoothGattCallback調用。
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
連接到GATT服務端時,由BLE設備做主機,並返回一個BluetoothGatt實例,然後你可以使用這個實例來進行GATT客戶端操作。請求方(Android app)是GATT客戶端。BluetoothGattCallback用於傳遞結果給用戶,例如連接狀態,以及任何進一步GATT客戶端操作。
private final BluetoothGattCallback mGattCallback =
new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {//當連接狀態發生改變
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {//當藍牙設備已經連接
//獲取ble設備上面的服務
Log.i(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {//當設備無法連接
}
}
@Override
//調用discoverServices後的回調
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取服務成功
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
// 讀寫特性
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
}
}
...
};
...
}
發送數據
首先通過UUID拿到對應的服務,再通過UUID拿到服務的特徵,設置特徵的屬性是BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE。設置成功後可以在該特徵值上發送數據到ble設備和接收ble設備的數據。看到這裏也許各位不熟ble開發和剛ble開發的看官也許就一臉懵逼,我只是想發送數據到ble設備,怎麼一下子搞出個UUID 服務和特徵值了,難道就不能和B/S開發一樣,連接之後我把數據發送到一個接口,服務器端就返回我需要的數據那麼簡單。這還得從ble藍牙的架構說起。
BLE分爲三部分Service、Characteristic、Descriptor,這三部分都由UUID作爲唯一標示符。一個藍牙4.0的終端可以包含多個Service,一個Service可以包含多個Characteristic,一個Characteristic包含一個Value和多個Descriptor,一個Descriptor包含一個Value。service是characteristic的集合.一個characteristic包括一個單一變量和0-n個用來描述characteristic變量的descriptor.Descriptor用來描述characteristic變量的屬性。例如,一個descriptor可以規定一個可讀的描述,或者一個characteristic變量可接受的範圍,或者一個characteristic變量特定的測量單位。一般來說,Characteristic是手機與BLE終端交換數據的關鍵.。
舉個栗子:當我們想要用手機與BLE設備進行通信時,實際上也就相當於我們要去找一個學生交流,首先我們需要搭建一個管道,也就是我們需要先獲取得到一個BluetoothGatt,其次我們需要知道這個學生在哪一個班級,學號是什麼,這也就是我們所說的serviceUUID,和charUUID。這裏我們還需要注意一下,找到這個學生後並不是直接和他交流,他就好像一箇中介一樣,在手機和BLE終端設備之間幫助這兩者傳遞着信息,我們手機所發數據要先經過他,在由他傳遞到BLE設備上,而BLE設備上的返回信息,也是先傳遞到他那邊,然後手機再從他那邊進行讀取。
在發送數據之前需先設置特徵的具有notificaion功能
private BluetoothGatt mBluetoothGatt;
BluetoothGattCharacteristic characteristic;
boolean enabled;
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
mBluetoothGatt.writeDescriptor(descriptor);
設置完成後回調
@Override
public final void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
//設置成功
if (status == BluetoothGatt.GATT_SUCCESS) {
}
}
設置成功後就開始發送數據了。
//將指令放置進特徵中
characteristic.setValue(data);
//設置回覆形式characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
//開始寫數據
mBluetoothGatt.writeCharacteristic(chharacteristic);
寫入數據成功後回調
protected void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
//發送數據成功啦啦啦
}
如何設備回覆數據則會回調
@Override
public final void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
}
關閉客戶端App
當你的app完成BLE設備的使用後,應該調用close( )),系統可以合理釋放佔用資源。
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
最後分享我在BLE 開發中遇到的坑和一些經驗
1 在所有藍牙的回調中不要操作UI。我是不會告訴你我是怎麼發現這個坑的。
2 在所有的藍牙回調中不要執行耗時操作。
3 發送數據要等到上一條數據發送成功後再發下一條數據,畢竟BLE設備運算沒有手機快,這裏可以推薦一個開源藍牙連接工具https://github.com/NordicSemiconductor/Android-nRF-Toolbox,裏面非常好的對發送的數據做了一個數據隊列。
4 合理的控制掃描過程,一般出現133錯誤的時候重連就可以先去掃描再去連接。若掃描不到時不要馬上又去掃描,不然你把手機放那一夜,把設備遠離它,第二天回來看手機時會驚喜的發現手機沒電自動關機了
遇到的坑
1 斷線重連的時候總是報133錯誤,
斷線後不要馬上去連接.先掃描設備,掃描到設備後再去連接。
2 掃描不到設備
手動關閉藍牙再打開藍牙開關。這個可能是重連裏面的掃描引起的,如果設備未在周邊,一直去掃描的話,後來設備在身邊也可能掃描不到設備。如果未能連接設備,也不能一直去掃描。掃描不到設備時說明設備並不到周邊,可以延遲多少時間後再去掃描
3 連接設備後發送數據,發送數據的回調函數也已經走了。沒有接收到數據
查看設置特徵值的描述值
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
mBluetoothGatt.writeDescriptor(descriptor)的回調裏面是不是回調成功了
4反覆斷開藍牙後再重連導致連接失敗
斷開藍牙後應該調用close()方法釋放資源.連接時應該設置超時,在超時時間內繼續去連接,基本低、中、高端機都能重新連接上。
5 連接上之後自動斷開連接,重連上之後又自動斷開連接,如此反覆。
我們的BLE設備在某些低端機會遇到這種問題。聽固件工程師說是BLE設備藍牙芯片頻率和手機藍牙頻率問題,需調BLE設備頻率。遇到這種問題APP就束手無策了。
6 反覆操作斷開和連接導致系統藍牙掛掉(無響應)
基本也是沒有合理釋放資源導致
7 調用掃描操作導致APP無響應
查看系統藍牙是否掛掉了。基本和問題6類似
參考文章:
http://www.cnblogs.com/cxk1995/p/5693979.html
https://developer.android.com/guide/topics/connectivity/bluetooth-le.html#roles