若是還有同窗不知道藍牙4.0能夠作什麼請查看Android+藍牙 4.0 將帶來什麼?,如今能夠穿戴設備也大多用的是藍牙4.0,如 智能體質秤,智能手環,智能血壓計等等。 html
原文地址:http://developer.android.com/guide/topics/connectivity/bluetooth-le.html java
安卓4.3(API 18)爲BLE的核心功能提供平臺支持和API,App能夠利用它來發現設備、查詢服務和讀寫特性。相比傳統的藍牙,BLE更顯著的特色是低功耗。這一優勢使android App能夠與具備低功耗要求的BLE設備通訊,如近距離傳感器、心臟速率監視器、健身設備等。 android
關鍵術語和概念
- Generic Attribute Profile(GATT)—GATT配置文件是一個通用規範,用於在BLE鏈路上發送和接收被稱爲「屬性」的數據塊。目前全部的BLE應用都基於GATT。 藍牙SIG規定了許多低功耗設備的配置文件。配置文件是設備如何在特定的應用程序中工做的規格說明。注意一個設備能夠實現多個配置文件。例如,一個設備可能包括心率監測儀和電量檢測。
- Attribute Protocol(ATT)—GATT在ATT協議基礎上創建,也被稱爲GATT/ATT。ATT對在BLE設備上運行進行了優化,爲此,它使用了儘量少的字節。每一個屬性經過一個惟一的的統一標識符(UUID)來標識,每一個String類型UUID使用128 bit標準格式。屬性經過ATT被格式化爲characteristics和services。
- Characteristic 一個characteristic包括一個單一變量和0-n個用來描述characteristic變量的descriptor,characteristic能夠被認爲是一個類型,相似於類。
- Descriptor Descriptor用來描述characteristic變量的屬性。例如,一個descriptor能夠規定一個可讀的描述,或者一個characteristic變量可接受的範圍,或者一個characteristic變量特定的測量單位。
- Service service是characteristic的集合。例如,你可能有一個叫「Heart Rate Monitor(心率監測儀)」的service,它包括了不少characteristics,如「heart rate measurement(心率測量)」等。你能夠在bluetooth.org 找到一個目前支持的基於GATT的配置文件和服務列表。
角色和責任
如下是Android設備與BLE設備交互時的角色和責任: 數組
- 中央 VS 外圍設備。 適用於BLE鏈接自己。中央設備掃描,尋找廣播;外圍設備發出廣播。
- GATT 服務端 VS GATT 客戶端。決定了兩個設備在創建鏈接後如何互相交流。
爲了方便理解,想象你有一個Android手機和一個用於活動跟蹤BLE設備,手機支持中央角色,活動跟蹤器支持外圍(爲了創建BLE鏈接你須要注意兩件事,只支持外圍設備的兩方或者只支持中央設備的兩方不能互相通訊)。 服務器
當手機和運動追蹤器創建鏈接後,他們開始向另外一方傳輸GATT數據。哪一方做爲服務器取決於他們傳輸數據的種類。例如,若是運動追蹤器想向手機報告傳感器數據,運動追蹤器是服務端。若是運動追蹤器更新來自手機的數據,手機會做爲服務端。 數據結構
在這份文檔的例子中,android app(運行在android設備上)做爲GATT客戶端。app從gatt服務端得到數據,gatt服務端即支持Heart Rate Profile(心率配置)的BLE心率監測儀。可是你能夠本身設計android app去扮演GATT服務端角色。更多信息見BluetoothGattServer。 app
BLE權限
爲了在app中使用藍牙功能,必須聲明藍牙權限BLUETOOTH。利用這個權限去執行藍牙通訊,例如請求鏈接、接受鏈接、和傳輸數據。 ide
若是想讓你的app啓動設備發現或操縱藍牙設置,必須聲明BLUETOOTH_ADMIN權限。注意:若是你使用BLUETOOTH_ADMIN權限,你也必須聲明BLUETOOTH權限。 函數
在你的app manifest文件中聲明藍牙權限。 post
- <uses-permission android:name="android.permission.BLUETOOTH"/>
- <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
若是想聲明你的app只爲具備BLE的設備提供,在manifest文件中包括:
-
- <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
可是若是想讓你的app提供給那些不支持BLE的設備,須要在manifest中包括上面代碼並設置required="false",而後在運行時能夠經過使用PackageManager.hasSystemFeature()肯定BLE的可用性。
- <span style="font-size:14px;font-weight: normal;">// 使用此檢查肯定BLE是否支持在設備上,而後你能夠有選擇性禁用BLE相關的功能
- if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
- Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
- finish();
- }</span>
設置BLE
你的app能與BLE通訊以前,你須要確認設備是否支持BLE,若是支持,確認已經啓用。注意若是<uses-feature.../>設置爲false,這個檢查纔是必需的。
若是不支持BLE,那麼你應該適當地禁用部分BLE功能。若是支持BLE但被禁用,你能夠無需離開應用程序而要求用戶啓動藍牙。使用BluetoothAdapter兩步完成該設置。
-
獲取 BluetoothAdapter
全部的藍牙活動都須要藍牙適配器。BluetoothAdapter表明設備自己的藍牙適配器(藍牙無線)。整個系統只有一個藍牙適配器,並且你的app使用它與系統交互。下面的代碼片斷顯示瞭如何獲得適配器。注意該方法使用getSystemService()]返回BluetoothManager,而後將其用於獲取適配器的一個實例。Android 4.3(API 18)引入BluetoothManager。
- // 初始化藍牙適配器
- final BluetoothManager bluetoothManager =
- (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
- mBluetoothAdapter = bluetoothManager.getAdapter();
-
開啓藍牙
接下來,你須要確認藍牙是否開啓。調用isEnabled())去檢測藍牙當前是否開啓。若是該方法返回false,藍牙被禁用。下面的代碼檢查藍牙是否開啓,若是沒有開啓,將顯示錯誤提示用戶去設置開啓藍牙。
- // 確保藍牙在設備上能夠開啓
- if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
- Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
- startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
- }
發現BLE設備
爲了發現BLE設備,使用startLeScan())方法。這個方法須要一個參數BluetoothAdapter.LeScanCallback。你必須實現它的回調函數,那就是返回的掃描結果。由於掃描很是消耗電量,你應當遵照如下準則:
- 只要找到所需的設備,中止掃描。
- 不要在循環裏掃描,而且對掃描設置時間限制。之前可用的設備可能已經移出範圍,繼續掃描消耗電池電量。
下面代碼顯示瞭如何開始和中止一個掃描:
- /**
- * 掃描和顯示能夠提供的藍牙設備.
- */
- 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) {
- // 通過預約掃描期後中止掃描
- 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);
- }
- ...
- }
- ...
- }
做爲BLE掃描結果的接口,下面是BluetoothAdapter.LeScanCallback的實現。若是你只想掃描指定類型的外圍設備,能夠改成調用startLeScan(UUID[], BluetoothAdapter.LeScanCallback)),須要提供你的app支持的GATT services的UUID對象數組。
- 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();
- }
- });
- }
- };
鏈接到GATT服務端注意:只能掃描BLE設備或者掃描傳統藍牙設備,不能同時掃描BLE和傳統藍牙設備。
與一個BLE設備交互的第一步就是鏈接它——更具體的,鏈接到BLE設備上的GATT服務端。爲了鏈接到BLE設備上的GATT服務端,須要使用connectGatt( )方法。這個方法須要三個參數:一個Context對象,自動鏈接(boolean值,表示只要BLE設備可用是否自動鏈接到它),和BluetoothGattCallback調用。
- mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
在這個例子中,這個BLE APP提供了一個activity(DeviceControlActivity)來鏈接,顯示數據,顯示該設備支持的GATT services和characteristics。根據用戶的輸入,這個activity與BluetoothLeService通訊,經過Android BLE API實現與BLE設備交互。鏈接到GATT服務端時,由BLE設備作主機,並返回一個BluetoothGatt實例,而後你可使用這個實例來進行GATT客戶端操做。請求方(Android app)是GATT客戶端。BluetoothGattCallback用於傳遞結果給用戶,例如鏈接狀態,以及任何進一步GATT客戶端操做。
- //經過BLE API服務端與BLE設備交互
- 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);
- // 這是心率測量配置文件。
- 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 {
- // 對於全部其餘的配置文件,用十六進制格式寫數據
- 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: 鏈接到GATT服務端
- // ACTION_GATT_DISCONNECTED: 未鏈接GATT服務端.
- // 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)) {
- // 在用戶接口上展現全部的services and characteristics
- displayGattServices(mBluetoothLeService.getSupportedGattServices());
- } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
- displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
- }
- }
- };
讀取BLE變量
你的android app完成與GATT服務端鏈接和發現services後,就能夠讀寫支持的屬性。例如,這段代碼經過服務端的services和 characteristics迭代,而且將它們顯示在UI上。
- public class DeviceControlActivity extends Activity {
- ...
- // 演示如何遍歷支持GATT Services/Characteristics
- // 這個例子中,咱們填充綁定到UI的ExpandableListView上的數據結構
- 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>>();
- // 循環可用的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>();
- // 循環可用的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通知
當設備上的特性改變時會通知BLE應用程序。這段代碼顯示瞭如何使用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
當你的app完成BLE設備的使用後,應該調用close( )),系統能夠合理釋放佔用資源。
- public void close() {
- if (mBluetoothGatt == null) {
- return;
- }
- mBluetoothGatt.close();
- mBluetoothGatt = null;
- }