聲明:轉載請註明出處http://www.jianshu.com/p/54bc88207050
Android 4.3(API level 18)引入了藍牙低功耗(BLE)核心功能,並提供了API可供app來搜索設備,查詢服務以及傳輸信息。
藍牙低功耗通常用於以下幾個方面:
- 在兩個相近的設備之間傳輸少量數據。
- 與距離傳感器進行交互,比如Google Beacons。這樣可以基於當前位置來定製用戶個性化體驗。
相比經典藍牙,藍牙低功耗(BLE)可以顯著減少電量消耗。這使得Android應用可以與對電量有着更嚴格限制的設備通信,比如位置傳感器,心率監視器,以及健身設備。
關鍵術語和概念
以下是對BLE關鍵術語和概念的總結:
-
Generic Attribute Profile (GATT)--GATT profile是發送和接收少量數據的一種通用規範,正如大家知道的在BLE連接上的“attributes”。當前所有的低功耗應用profile都是基於GATT。
- 藍牙技術聯盟爲低功耗設備定義了很多profile。profile就是指定設備在特定的應用中如何工作的一種規範 。舉個例子,一個設備可以包含一個心率監視器和一個電量狀態監測器。
Attribute Protocol (ATT)--GATT是建立在ATT的基礎之上。所以也稱之爲GATT/ATT。ATT在BLE設備上運行經過了優化,因此,它會儘可能使用更少的字節數組。每個屬性通過UUID來唯一確定,UUID是一種128位的標準化格式字符串ID來標識信息的唯一性。屬性會被格式化爲特徵(characteristics )與服務(services)後通過ATT來傳輸。
特徵(Characteristic)--一個特徵包含了一個單獨的值和0-n個描述符(descriptors ,用來描述特徵的值)。一個特徵可以看做是一種類型,類似一個class。
描述符(Descriptor)--描述符定義了屬性用來描述特徵值。比如,描述符可以是人們可讀的描述語句,可以是特徵值得可接受範圍,或者是特徵值的測量單位。
服務(Service)--服務是特徵的集合。比如,你有一個叫做‘心率監視器’的服務,在這個服務裏包含一個“心率測量”的特徵。你可以在bluetooth.org上查看一系列基於GATT的profile與服務。
角色與職責
以下是Android設備與BLE設備交互時的角色和職責:
- 中心 vs 外設。這適用於BLE自身的連接。中心設備掃描,搜索廣告,外設發送廣告。
- GATT服務 vs GATT客戶端。這決定了兩個設備之間如何通信一旦他們建立了連接。
爲了更好的理解不同,想象下假如你有一個Android手機與一個具有藍牙低功耗的活動追蹤器設備。手機支持中心角色;活動追蹤器支持外設角色(爲了建立連接兩個設備必須是不同的角色,不能同時爲同一個角色)。
一旦手機與藍牙設備建立了連接。他們就會傳輸GATT媒體數據到另一設備。根據傳輸的數據,它們其中一個需要作爲服務端。比如,如果活動追蹤設備想要上傳傳感數據到手機,那麼該活動追蹤器就作爲服務端。如果活動追蹤設備想要接收手機傳來的數據,那麼手機就是服務端。
在文章中舉的例子中,Android應用是GATT客戶端。app從GATT服務端獲取數據,這個服務也就是支持 心率Profile 的心率監視器。當然你也可以是你的Android app扮演服務端角色。可以查閱BluetoothGattServer獲取更多信息。
BLE權限
爲了在你的應用中使用藍牙,你需要申明藍牙權限BLUETOOTH。你需要這個權限來實現任何的藍牙通信,比如請求連接,接收連接以及傳輸數據。
如果你想是你的app開啓設備搜索以及管理藍牙設置,你必須申明BLUETOOTH_ADMIN權限。注意:如果你申明瞭BLUETOOTH_ADMIN權限,那麼你必須同時申明BLUETOOTH權限。
在你的manifest文件裏申明權限,如下:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
如果你想你的應用只能在支持BLE的設備上使用,你可以添加以下申明:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
然而,如果你想在不支持BLE的設備上使用你的app,你也需要添加上面的申明,只是需要將required設爲false。這樣你可以在運行時通過調用 PackageManager.hasSystemFeature()來判斷該設備是否支持BLE。
// 用以下方式來判斷設備是否支持BLE,從而選擇性的禁用BLE相關特性
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
注意:BLE經常與位置有聯繫。所以爲了不帶過濾器的使用
BluetoothLeScanner,你必須申明ACCESS_COARSE_LOCATION或者ACCESS_FINE_LOCATION權限來訪問用戶的位置信息。如果沒有這些權限,那麼掃描將會沒有任何結果。
設置BLE
在你的應用使用BLE通信之前,你需要確認你的設備是否支持BLE,如果支持,那麼確保藍牙是打開的。注意這個判斷只有在<uses-feature/>
標籤設爲false的時候纔是必須的。
如果設備不支持BLE,那麼你應該優雅的禁用任何藍牙功能。如果支持BLE,但還沒開啓的話,你可以讓用戶在應用內打開藍牙。你可以使用BluetoothAdapter通過以下兩步來完成該操作:
1.獲取BluetoothAdapter
BluetoothAdapter對於所有藍牙活動都是必須。BluetoothAdapter代表設備自己的藍牙適配器。整個設備系統中只有一個藍牙適配器,你的應用可以用這個對象來和它交互。以下代碼片展示瞭如何獲取這個適配器。注意這裏使用getSystemService()來返回一個BluetoothManager實例,這可以用來獲取適配器。Android 4.3(API 18)才引入的BluetoothManager:
private BluetoothAdapter mBluetoothAdapter;
...
// 初始化藍牙適配器
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
2.開啓藍牙
接下來,你需要確認藍牙是否打開。調用
isEnabled()方法來確認當前藍牙是否打開。如果返回false,則藍牙未開啓。下面的代碼片判斷藍牙是否開啓。如果沒有,則提示用戶開啓藍牙:
// 確保設備支持藍牙,並已開啓,如果未開啓,則顯示一個彈窗來獲取用戶權限打開藍牙
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
注意:傳入startActivityForResult的常量REQUEST_ENABLE_BT 是定義在本地的int類型,系統會將這個常量作爲
requestCode
參數回傳到onActivityResult方法。
搜索BLE設備
你可以使用 startLeScan()方法來搜索設備。這個方法需要傳入一個回調參數BluetoothAdapter.LeScanCallback。你必須實現這個回調接口,因爲搜索結果會通過這個回調返回。由於掃描是耗電的,你需要遵循以下準則:
- 當搜索到你想要的設備就應該停止掃描
- 不能無限的掃描下去,需要給掃描做個時間限制。之前可以訪問的設備可能超出距離限制,繼續掃描只會消耗電量。
以下代碼片展示瞭如何開始以及結束掃描:
/**
* 一個用於掃描的Activity並展示搜到的設備
*/
public class DeviceScanActivity extends ListActivity {
private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
private Handler mHandler;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
...
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
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服務的UUID對象數組。
以下是 BluetoothAdapter.LeScanCallback接口實現,用來返回掃描結果:
private LeDeviceListAdapter mLeDeviceListAdapter;
...
// 設備掃描回調
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mLeDeviceListAdapter.addDevice(device);
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
};
注意:正如在文章 Bluetooth中描述的,你只能掃描BLE設備或者經典藍牙設備其中一種,不能同時掃描。
連接GATT服務
和BLE設備通信的第一步就是連上它--更具體的講就是連接上設備的GATT服務。你可以使用connectGatt()方法來連接設備上的GATT服務。這個方法需要傳入三個參數:一個Context對象,autoConnect(用來標記是否當BLE設備被搜到時就立刻連接上),還有個BluetoothGattCallback回調參數:
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
上面代碼會連接上BLE設備中的GATT服務,並返回一個BluetoothGatt實例,你可以用這個實例來做GATT客戶端的操作。調用者(Android app)是GATT客戶端。 BluetoothGattCallback回調用來向客戶端返回結果,比如連接狀態以及更多的GATT客戶端操作。
這個本文的例子中,BLE app提供一個Activity(DeviceControlActivity
)來連接,展示數據,以及展示藍牙設備所支持的GATT服務與特徵。基於用戶的輸入,這個活動會和BluetoothLeService服務通信,這個服務通過Android BLE API來和BLE設備交互:
// 通過BLE API和設備交互的服務
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;
private int mConnectionState = STATE_DISCONNECTED;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
public final static String ACTION_GATT_CONNECTED =
"com.example.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED =
"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE =
"com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA =
"com.example.bluetooth.le.EXTRA_DATA";
public final static UUID UUID_HEART_RATE_MEASUREMENT =
UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);
// BLE API定義的各種回調方法
private final BluetoothGattCallback mGattCallback =
new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
Log.i(TAG, "Connected to GATT server.");
Log.i(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
}
}
@Override
// 發現新的服務是回調
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
// 讀取特徵回調
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
...
};
...
}
當某個特定的回調觸發時,會調用broadcastUpdate()方法,並傳入一個action。注意這裏的數據解析是對應於本例子中的藍牙心率測量儀:
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
// 這是心率測量儀profile特有的數據解析方式
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
int flag = characteristic.getProperties();
int format = -1;
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Log.d(TAG, "Heart rate format UINT16.");
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.d(TAG, "Heart rate format UINT8.");
}
final int heartRate = characteristic.getIntValue(format, 1);
Log.d(TAG, String.format("Received heart rate: %d", heartRate));
intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
} else {
// 對於其他profile,將數據轉換爲16進制
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for(byte byteChar : data)
stringBuilder.append(String.format("%02X ", byteChar));
intent.putExtra(EXTRA_DATA, new String(data) + "\n" +
stringBuilder.toString());
}
}
sendBroadcast(intent);
}
回到DeviceControlActivity,上面的廣播會被BroadcastReceiver接收到:
// 處理各種事件
// ACTION_GATT_CONNECTED: 連接到設備
// ACTION_GATT_DISCONNECTED:斷開連接
// ACTION_GATT_SERVICES_DISCOVERED: 發現GATT服務
// ACTION_DATA_AVAILABLE: 從設備接收數據,這可以是讀操作或 通知的結果
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
mConnected = true;
updateConnectionState(R.string.connected);
invalidateOptionsMenu();
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
mConnected = false;
updateConnectionState(R.string.disconnected);
invalidateOptionsMenu();
clearUI();
} else if (BluetoothLeService.
ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
//向用戶展示所有支持的服務與特徵
displayGattServices(mBluetoothLeService.getSupportedGattServices());
} else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
}
}
};
讀取BLE屬性
一旦你的應用連接上GATT服務並搜到服務,那麼就可以讀寫所支持的屬性。比如,以下代碼片就是迭代設備的服務與特徵並將它們展現在UI上:
public class DeviceControlActivity extends Activity {
...
//演示瞭如何遍歷服務和特徵,在這個例子中將數據結構展示到樹形列表上
private void displayGattServices(List<BluetoothGattService> gattServices) {
if (gattServices == null) return;
String uuid = null;
String unknownServiceString = getResources().
getString(R.string.unknown_service);
String unknownCharaString = getResources().
getString(R.string.unknown_characteristic);
ArrayList<HashMap<String, String>> gattServiceData =
new ArrayList<HashMap<String, String>>();
ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
= new ArrayList<ArrayList<HashMap<String, String>>>();
mGattCharacteristics =
new ArrayList<ArrayList<BluetoothGattCharacteristic>>();
// 循環遍歷服務
for (BluetoothGattService gattService : gattServices) {
HashMap<String, String> currentServiceData =
new HashMap<String, String>();
uuid = gattService.getUuid().toString();
currentServiceData.put(
LIST_NAME, SampleGattAttributes.
lookup(uuid, unknownServiceString));
currentServiceData.put(LIST_UUID, uuid);
gattServiceData.add(currentServiceData);
ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
new ArrayList<HashMap<String, String>>();
List<BluetoothGattCharacteristic> gattCharacteristics =
gattService.getCharacteristics();
ArrayList<BluetoothGattCharacteristic> charas =
new ArrayList<BluetoothGattCharacteristic>();
// 循環遍歷特徵
for (BluetoothGattCharacteristic gattCharacteristic :
gattCharacteristics) {
charas.add(gattCharacteristic);
HashMap<String, String> currentCharaData =
new HashMap<String, String>();
uuid = gattCharacteristic.getUuid().toString();
currentCharaData.put(
LIST_NAME, SampleGattAttributes.lookup(uuid,
unknownCharaString));
currentCharaData.put(LIST_UUID, uuid);
gattCharacteristicGroupData.add(currentCharaData);
}
mGattCharacteristics.add(charas);
gattCharacteristicData.add(gattCharacteristicGroupData);
}
...
}
...
}
接收GATT通知
通常當設備上的某個特定特徵改變了就需要通知BLE app。以下代碼片展示瞭如何給特徵設置通知,調用setCharacteristicNotification()方法:
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);
一旦特徵設置了通知,那麼當遠程設備的特徵發生改變時就會回調 onCharacteristicChanged() 方法:
@Override
// 特徵通知
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
關閉App客戶端
一旦你的應用結束使用BLE設備,你就應該調用close方法來釋放系統資源:
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
好了以上就是官方關於藍牙BLE的介紹,可能還是比較籠統,下一篇我會結合一個實際的例子來介紹下。
官方原文:https://developer.android.com/guide/topics/connectivity/bluetooth-le.html#close
.
.