安卓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)限,。
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>" 如果想聲明你的app只為具有BLE的設(shè)備提供,,在manifest文件中包括:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/> 但是如果想讓你的app提供給那些不支持BLE的設(shè)備,,需要在manifest中包括上面代碼并設(shè)置required="false",然后在運(yùn)行時(shí)可以通過使用PackageManager.hasSystemFeature()確定BLE的可用性,。
// 使用此檢查確定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(); } 設(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,。
// 初始化藍(lán)牙適配器 final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); 開啟藍(lán)牙
接下來,你需要確認(rèn)藍(lán)牙是否開啟,。調(diào)用isEnabled())去檢測藍(lán)牙當(dāng)前是否開啟,。如果該方法返回false,藍(lán)牙被禁用。下面的代碼檢查藍(lán)牙是否開啟,,如果沒有開啟,,將顯示錯(cuò)誤提示用戶去設(shè)置開啟藍(lán)牙。
// 確保藍(lán)牙在設(shè)備上可以開啟 if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } 發(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è)備.
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);
}
...
}
... } 如果你只想掃描指定類型的外圍設(shè)備,,可以改為調(diào)用startLeScan(UUID[], BluetoothAdapter.LeScanCallback)),需要提供你的app支持的GATT services的UUID對象數(shù)組,。
作為BLE掃描結(jié)果的接口,下面是BluetoothAdapter.LeScanCallback的實(shí)現(xiàn),。
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(); } }); } }; 注意:只能掃描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)用,。
mBluetoothGatt = device.connectGatt(this, false, mGattCallback); 連接到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è)備交互。
//通過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);
}
}
...
};
... } 當(dāng)一個(gè)特定的回調(diào)被觸發(fā)的時(shí)候,,它會調(diào)用相應(yīng)的broadcastUpdate()輔助方法并且傳遞給它一個(gè)action,。注意在該部分中的數(shù)據(jù)解析按照藍(lán)牙心率測量配置文件規(guī)格進(jìn)行。
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);
} 返回DeviceControlActivity, 這些事件由一個(gè)BroadcastReceiver來處理:
// 通過服務(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)); } } }; 讀取BLE變量
你的android app完成與GATT服務(wù)端連接和發(fā)現(xiàn)services后,,就可以讀寫支持的屬性。例如,,這段代碼通過服務(wù)端的services和 characteristics迭代,,并且將它們顯示在UI上。
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);
}
...
}
... } 接收GATT通知
當(dāng)設(shè)備上的特性改變時(shí)會通知BLE應(yīng)用程序,。這段代碼顯示了如何使用setCharacteristicNotification( )給一個(gè)特性設(shè)置通知。
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); 如果對一個(gè)特性啟用通知,當(dāng)遠(yuǎn)程藍(lán)牙設(shè)備特性發(fā)送變化,,回調(diào)函數(shù)onCharacteristicChanged( ))被觸發(fā)。
@Override // 廣播更新 public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } 關(guān)閉客戶端App
當(dāng)你的app完成BLE設(shè)備的使用后,,應(yīng)該調(diào)用close( )),,系統(tǒng)可以合理釋放占用資源。
public void close() { if (mBluetoothGatt == null) { return; } mBluetoothGatt.close(); mBluetoothGatt = null; }</code
|