安卓4.3(API 18)為BLE的核心功能提供平臺支持和API,App可以利用它來發(fā)現(xiàn)設(shè)備、查詢服務(wù)和讀寫特性。相比傳統(tǒng)的藍(lán)牙,BLE更顯著的特點(diǎn)是低功耗。這一優(yōu)點(diǎn)使android App可以與具有低功耗要求的BLE設(shè)備通信,如近距離傳感器、心臟速率監(jiān)視器、健身設(shè)備等。
關(guān)鍵術(shù)語和概念
Generic Attribute Profile(GATT)—GATT配置文件是一個(gè)通用規(guī)范,用于在BLE鏈路上發(fā)送和接收被稱為“屬性”的數(shù)據(jù)塊。目前所有的BLE應(yīng)用都基于GATT。 藍(lán)牙SIG規(guī)定了許多低功耗設(shè)備的配置文件。配置文件是設(shè)備如何在特定的應(yīng)用程序中工作的規(guī)格說明。注意一個(gè)設(shè)備可以實(shí)現(xiàn)多個(gè)配置文件。例如,一個(gè)設(shè)備可能包括心率監(jiān)測儀和電量檢測。
Attribute Protocol(ATT)—GATT在ATT協(xié)議基礎(chǔ)上建立,也被稱為GATT/ATT。ATT對在BLE設(shè)備上運(yùn)行進(jìn)行了優(yōu)化,為此,它使用了盡可能少的字節(jié)。每個(gè)屬性通過一個(gè)唯一的的統(tǒng)一標(biāo)識符(UUID)來標(biāo)識,每個(gè)String類型UUID使用128 bit標(biāo)準(zhǔn)格式。屬性通過ATT被格式化為characteristics和services。
Characteristic 一個(gè)characteristic包括一個(gè)單一變量和0-n個(gè)用來描述characteristic變量的descriptor,characteristic可以被認(rèn)為是一個(gè)類型,類似于類。
Descriptor Descriptor用來描述characteristic變量的屬性。例如,一個(gè)descriptor可以規(guī)定一個(gè)可讀的描述,或者一個(gè)characteristic變量可接受的范圍,或者一個(gè)characteristic變量特定的測量單位。
Service service是characteristic的集合。例如,你可能有一個(gè)叫“Heart Rate Monitor(心率監(jiān)測儀)”的service,它包括了很多characteristics,如“heart rate measurement(心率測量)”等。你可以在bluetooth.org 找到一個(gè)目前支持的基于GATT的配置文件和服務(wù)列表。
角色和責(zé)任
以下是Android設(shè)備與BLE設(shè)備交互時(shí)的角色和責(zé)任:
中央 VS 外圍設(shè)備。 適用于BLE連接本身。中央設(shè)備掃描,尋找廣播;外圍設(shè)備發(fā)出廣播。
GATT 服務(wù)端 VS GATT 客戶端。決定了兩個(gè)設(shè)備在建立連接后如何互相交流。
為了方便理解,想象你有一個(gè)Android手機(jī)和一個(gè)用于活動跟蹤BLE設(shè)備,手機(jī)支持中央角色,活動跟蹤器支持外圍(為了建立BLE連接你需要注意兩件事,只支持外圍設(shè)備的兩方或者只支持中央設(shè)備的兩方不能互相通信)。
當(dāng)手機(jī)和運(yùn)動追蹤器建立連接后,他們開始向另一方傳輸GATT數(shù)據(jù)。哪一方作為服務(wù)器取決于他們傳輸數(shù)據(jù)的種類。例如,如果運(yùn)動追蹤器想向手機(jī)報(bào)告?zhèn)鞲衅鲾?shù)據(jù),運(yùn)動追蹤器是服務(wù)端。如果運(yùn)動追蹤器更新來自手機(jī)的數(shù)據(jù),手機(jī)會作為服務(wù)端。
在這份文檔的例子中,android app(運(yùn)行在android設(shè)備上)作為GATT客戶端。app從gatt服務(wù)端獲得數(shù)據(jù),gatt服務(wù)端即支持Heart Rate Profile(心率配置)的BLE心率監(jiān)測儀。但是你可以自己設(shè)計(jì)android app去扮演GATT服務(wù)端角色。更多信息見BluetoothGattServer。
BLE權(quán)限
為了在app中使用藍(lán)牙功能,必須聲明藍(lán)牙權(quán)限BLUETOOTH。利用這個(gè)權(quán)限去執(zhí)行藍(lán)牙通信,例如請求連接、接受連接、和傳輸數(shù)據(jù)。
如果想讓你的app啟動設(shè)備發(fā)現(xiàn)或操縱藍(lán)牙設(shè)置,必須聲明BLUETOOTH_ADMIN權(quán)限。注意:如果你使用BLUETOOTH_ADMIN權(quán)限,你也必須聲明BLUETOOTH權(quán)限。
在你的app manifest文件中聲明藍(lán)牙權(quán)限。
<code><uses-permission android:name="android.permission.BLUETOOTH"/>
</code>
<code><uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>"
</code>
如果想聲明你的app只為具有BLE的設(shè)備提供,在manifest文件中包括:
<code><uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/></code>
但是如果想讓你的app提供給那些不支持BLE的設(shè)備,需要在manifest中包括上面代碼并設(shè)置required="false",然后在運(yùn)行時(shí)可以通過使用PackageManager.hasSystemFeature()確定BLE的可用性。
<code>
// 使用此檢查確定BLE是否支持在設(shè)備上,然后你可以有選擇性禁用BLE相關(guān)的功能
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}</code>
設(shè)置BLE
你的app能與BLE通信之前,你需要確認(rèn)設(shè)備是否支持BLE,如果支持,確認(rèn)已經(jīng)啟用。注意如果<uses-feature.../>設(shè)置為false,這個(gè)檢查才是必需的。
如果不支持BLE,那么你應(yīng)該適當(dāng)?shù)亟貌糠諦LE功能。如果支持BLE但被禁用,你可以無需離開應(yīng)用程序而要求用戶啟動藍(lán)牙。使用BluetoothAdapter兩步完成該設(shè)置。
獲取 BluetoothAdapter
所有的藍(lán)牙活動都需要藍(lán)牙適配器。BluetoothAdapter代表設(shè)備本身的藍(lán)牙適配器(藍(lán)牙無線)。整個(gè)系統(tǒng)只有一個(gè)藍(lán)牙適配器,而且你的app使用它與系統(tǒng)交互。下面的代碼片段顯示了如何得到適配器。注意該方法使用getSystemService()]返回BluetoothManager,然后將其用于獲取適配器的一個(gè)實(shí)例。Android 4.3(API 18)引入BluetoothManager。
<code>
// 初始化藍(lán)牙適配器
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();</code>
開啟藍(lán)牙
接下來,你需要確認(rèn)藍(lán)牙是否開啟。調(diào)用isEnabled())去檢測藍(lán)牙當(dāng)前是否開啟。如果該方法返回false,藍(lán)牙被禁用。下面的代碼檢查藍(lán)牙是否開啟,如果沒有開啟,將顯示錯(cuò)誤提示用戶去設(shè)置開啟藍(lán)牙。
<code>// 確保藍(lán)牙在設(shè)備上可以開啟
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}</code>
發(fā)現(xiàn)BLE設(shè)備
為了發(fā)現(xiàn)BLE設(shè)備,使用startLeScan())方法。這個(gè)方法需要一個(gè)參數(shù)BluetoothAdapter.LeScanCallback。你必須實(shí)現(xiàn)它的回調(diào)函數(shù),那就是返回的掃描結(jié)果。因?yàn)閽呙璺浅O碾娏?,你?yīng)當(dāng)遵守以下準(zhǔn)則:
只要找到所需的設(shè)備,停止掃描。
不要在循環(huán)里掃描,并且對掃描設(shè)置時(shí)間限制。以前可用的設(shè)備可能已經(jīng)移出范圍,繼續(xù)掃描消耗電池電量。
下面代碼顯示了如何開始和停止一個(gè)掃描:
// 掃描和顯示可以提供的藍(lán)牙設(shè)備.
<code>
public class DeviceScanActivity extends ListActivity {
private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
private Handler mHandler;
// 10秒后停止尋找.
private static final long SCAN_PERIOD = 10000;
...
private void scanLeDevice(final boolean enable) {
if (enable) {
// 經(jīng)過預(yù)定掃描期后停止掃描
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);
}
...
}
...
}</code>
如果你只想掃描指定類型的外圍設(shè)備,可以改為調(diào)用startLeScan(UUID[], BluetoothAdapter.LeScanCallback)),需要提供你的app支持的GATT services的UUID對象數(shù)組。
作為BLE掃描結(jié)果的接口,下面是BluetoothAdapter.LeScanCallback的實(shí)現(xiàn)。
<code>
private LeDeviceListAdapter mLeDeviceListAdapter;
...
// Device scan callback.
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();
}
});
}
};</code>
注意:只能掃描BLE設(shè)備或者掃描傳統(tǒng)藍(lán)牙設(shè)備,不能同時(shí)掃描BLE和傳統(tǒng)藍(lán)牙設(shè)備。
連接到GATT服務(wù)端
與一個(gè)BLE設(shè)備交互的第一步就是連接它——更具體的,連接到BLE設(shè)備上的GATT服務(wù)端。為了連接到BLE設(shè)備上的GATT服務(wù)端,需要使用connectGatt( )方法。這個(gè)方法需要三個(gè)參數(shù):一個(gè)Context對象,自動連接(boolean值,表示只要BLE設(shè)備可用是否自動連接到它),和BluetoothGattCallback調(diào)用。
<code>
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);</code>
連接到GATT服務(wù)端時(shí),由BLE設(shè)備做主機(jī),并返回一個(gè)BluetoothGatt實(shí)例,然后你可以使用這個(gè)實(shí)例來進(jìn)行GATT客戶端操作。請求方(Android app)是GATT客戶端。BluetoothGattCallback用于傳遞結(jié)果給用戶,例如連接狀態(tài),以及任何進(jìn)一步GATT客戶端操作。
在這個(gè)例子中,這個(gè)BLE APP提供了一個(gè)activity(DeviceControlActivity)來連接,顯示數(shù)據(jù),顯示該設(shè)備支持的GATT services和characteristics。根據(jù)用戶的輸入,這個(gè)activity與BluetoothLeService通信,通過Android BLE API實(shí)現(xiàn)與BLE設(shè)備交互。
<code>
//通過BLE API服務(wù)端與BLE設(shè)備交互
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();
private BluetoothManager mBluetoothManager; //藍(lán)牙管理器
private BluetoothAdapter mBluetoothAdapter; //藍(lán)牙適配器
private String mBluetoothDeviceAddress; //藍(lán)牙設(shè)備地址
private BluetoothGatt mBluetoothGatt;
private int mConnectionState = STATE_DISCONNECTED;
private static final int STATE_DISCONNECTED = 0; //設(shè)備無法連接
private static final int STATE_CONNECTING = 1; //設(shè)備正在連接狀態(tài)
private static final int STATE_CONNECTED = 2; //設(shè)備連接完畢
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的不同類型的回調(diào)方法
private final BluetoothGattCallback mGattCallback =
new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {//當(dāng)連接狀態(tài)發(fā)生改變
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {//當(dāng)藍(lán)牙設(shè)備已經(jīng)連接
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) {//當(dāng)設(shè)備無法連接
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
}
}
@Override
// 發(fā)現(xiàn)新服務(wù)端
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);
}
}
...
};
...
}</code>
當(dāng)一個(gè)特定的回調(diào)被觸發(fā)的時(shí)候,它會調(diào)用相應(yīng)的broadcastUpdate()輔助方法并且傳遞給它一個(gè)action。注意在該部分中的數(shù)據(jù)解析按照藍(lán)牙心率測量配置文件規(guī)格進(jìn)行。
<code>
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);
// 這是心率測量配置文件。
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
int flag = characteristic.getProperties();
int format = -1;
if ((flag & x01) != 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 {
// 對于所有其他的配置文件,用十六進(jìn)制格式寫數(shù)據(jù)
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);
}</code>
返回DeviceControlActivity, 這些事件由一個(gè)BroadcastReceiver來處理:
<code>
// 通過服務(wù)控制不同的事件
// ACTION_GATT_CONNECTED: 連接到GATT服務(wù)端
// ACTION_GATT_DISCONNECTED: 未連接GATT服務(wù)端.
// ACTION_GATT_SERVICES_DISCOVERED: 未發(fā)現(xiàn)GATT服務(wù).
// ACTION_DATA_AVAILABLE: 接受來自設(shè)備的數(shù)據(jù),可以通過讀或通知操作獲得。
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)) {
// 在用戶接口上展示所有的services and characteristics
displayGattServices(mBluetoothLeService.getSupportedGattServices());
} else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
}
}
};</code>
讀取BLE變量
你的android app完成與GATT服務(wù)端連接和發(fā)現(xiàn)services后,就可以讀寫支持的屬性。例如,這段代碼通過服務(wù)端的services和 characteristics迭代,并且將它們顯示在UI上。
<code>
public class DeviceControlActivity extends Activity {
...
// 演示如何遍歷支持GATT Services/Characteristics
// 這個(gè)例子中,我們填充綁定到UI的ExpandableListView上的數(shù)據(jù)結(jié)構(gòu)
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>>();
// 循環(huán)可用的GATT Services.
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>();
// 循環(huán)可用的Characteristics.
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);
}
...
}
...
}</code>
接收GATT通知
當(dāng)設(shè)備上的特性改變時(shí)會通知BLE應(yīng)用程序。這段代碼顯示了如何使用setCharacteristicNotification( )給一個(gè)特性設(shè)置通知。
<code>
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);</code>
如果對一個(gè)特性啟用通知,當(dāng)遠(yuǎn)程藍(lán)牙設(shè)備特性發(fā)送變化,回調(diào)函數(shù)onCharacteristicChanged( ))被觸發(fā)。
<code>
@Override
// 廣播更新
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}</code>
關(guān)閉客戶端App
當(dāng)你的app完成BLE設(shè)備的使用后,應(yīng)該調(diào)用close( )),系統(tǒng)可以合理釋放占用資源。
<code>
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}</code>