代碼地址以下:
http://www.demodashi.com/demo/15062.htmlhtml
隨着物聯網時代的到來,愈來愈多的智能硬件設備開始流行起來,好比智能手環、心率檢測儀、以及各式各樣的智能傢俱和玩具類產品。安卓4.3(API 18)爲BLE的核心功能提供平臺支持和API,App能夠利用它來發現設備、查詢服務和讀寫特性。相比傳統的藍牙,BLE更顯著的特色是低功耗。本文主要講解Android低功耗藍牙的api使用以及藍牙掃描、鏈接、發送數據、接收數據等一系列操做,並主要介紹本人封裝的BleLib藍牙庫,很是適合藍牙初學者使用,只須要一行代碼注入就OK了,並且用法也極其簡單,下面會專門講解BleLib庫的使用。android
廢話不說,先來看下Demo中的效果圖:api
在BLE協議中,有兩個角色,周邊(Periphery)和中央(Central);周邊是數據提供者,中央是數據使用/處理者,一箇中央能夠同時鏈接多個周邊,可是一個周邊某一時刻只能鏈接一箇中央。
首先使用藍牙就不得不說BluetoothGatt和BluetoothGattCallback這兩個類,該類繼承自BluetoothProfile,BluetoothGatt做爲中央來使用和處理數據,經過BluetoothGatt能夠鏈接設備(connect),發現服務(discoverServices),並把相應地屬性返回到BluetoothGattCallback,BluetoothGattCallback返回中央的狀態和周邊提供的數據。服務器
咱們藍牙操做的主要目的就是爲了拿到中央BluetoothGatt這個對象,進而進行接下來的全部一系列操做,以下:app
1.先拿到BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 2.再拿到BluetoothAdapt btAdapter = bluetoothManager.getAdapter(); 3.開始掃描:btAdapter.startLeScan( BluetoothAdapter.LeScanCallback); 4.從LeScanCallback中獲得BluetoothDevice public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {…..} 5.用BluetoothDevice獲得BluetoothGatt:gatt = device.connectGatt(this, true, gattCallback);
這時總算拿到中央BluetoothGatt了,它有不少的方法,調用這些方法,你就能夠經過BluetoothGattCallback和周邊BluetoothGattServer交互了。ide
BluetoothProfile: 一個通用的規範,按照這個規範來收發數據。post
BluetoothManager:經過BluetoothManager來獲取BluetoothAdapter大數據
如:BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter:一個Android系統只有一個BluetoothAdapter ,經過BluetoothManager 獲取ui
BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
BluetoothGattDescriptor:能夠當作是描述符,對Characteristic的描述,包括範圍、計量單位等。this
BluetoothGattService:服務,Characteristic的集合。
BluetoothGattCallback:已經鏈接上設備,對設備的某些操做後返回的結果。這裏必須提醒下,已經鏈接上設備後的才能夠返回,沒有返回的認真看看有沒有鏈接上設備。
private BluetoothGattCallback GattCallback = new BluetoothGattCallback() { // 這裏有9個要實現的方法,看狀況要實現那些,用到那些就實現那些 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState){}; public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status){ }; }; BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); BluetoothGatt gatt = device.connectGatt(this, false, mGattCallback);
(1) notification對應onCharacteristicChanged;
gatt.setCharacteristicNotification(characteristic, true);
該方法通常是在發現服務後,進行設置的,設置該方法的目的是讓硬件在數據改變的時候,發送數據給app,app則經過onCharacteristicChanged方法回調給用戶,從參數中可獲取到回調回來的數據。
(2) readCharacteristic對應onCharacteristicRead;
gatt.readCharacteristic(characteristic);
(3) writeCharacteristic對應onCharacteristicWrite;
gatt.wirteCharacteristic(mCurrentcharacteristic);
(4) 鏈接藍牙或者斷開藍牙 對應 onConnectionStateChange;
bluetoothDevice.connectGatt(this, false, mGattCallback); 或 gatt.disconnect();(斷開鏈接後務必記得gatt.close();)
(5) readDescriptor對應onDescriptorRead;
gatt.readDescriptor(descriptor);
(6) writeDescriptor對應onDescriptorWrite;
gatt.writeDescriptor(descriptor);
(7) readRemoteRssi對應onReadRemoteRssi;
gatt.readRemoteRssi();
(8) executeReliableWrite對應onReliableWriteCompleted;
gatt.executeReliableWrite();
(9) discoverServices對應onServicesDiscovered
gatt.discoverServices();
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
若是 android.hardware.bluetooth_le設置爲false,能夠安裝在不支持的設備上使用,判斷是否支持藍牙4.0用如下代碼就能夠了,如:
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, 「設備不支持藍牙4.0」, Toast.LENGTH_SHORT).show(); finish(); }
對藍牙的啓動關閉操做:
一、利用系統默認開啓藍牙對話框
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
二、後臺打開藍牙,不作任何提示,這個也能夠用來自定義打開藍牙對話框啦
mBluetoothAdapter.enable();
三、後臺關閉藍牙
mBluetoothAdapter.disable();
private void initBle() {
mBle = Ble.options()
.setLogBleExceptions(true)//設置是否輸出打印藍牙日誌(非正式打包請設置爲true,以便於調試)
.setThrowBleException(true)//設置是否拋出藍牙異常
.setAutoConnect(true)//設置是否自動鏈接
.setConnectFailedRetryCount(3)
.setConnectTimeout(10 * 1000)//設置鏈接超時時長(默認101000 ms)
.setScanPeriod(12 1000)//設置掃描時長(默認10*1000 ms)
.setUuid_service(UUID.fromString("0000fee9-0000-1000-8000-00805f9b34fb"))//主服務的uuid
.setUuid_write_cha(UUID.fromString("d44bc439-abfd-45a2-b575-925416129600"))//可寫特徵的uuid
.create(getApplicationContext());
}
有對比才有傷害,那就來看下原生api調用藍牙流程和該庫之間的對比:
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); } ... } 而後在mLeScanCallback的回調中拿到掃描結果: // 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() { ... } }); }
mBle.startScan(scanCallback); 回調結果: BleScanCallback<BleDevice> scanCallback = new BleScanCallback<BleDevice>() { @Override public void onLeScan(final BleDevice device, int rssi, byte[] scanRecord) { ... } } };
這絕對是其餘藍牙庫所沒有的,具體API請看下面的庫使用步驟
private void initBle() { mBle = Ble.options() .setLogBleExceptions(true)//設置是否輸出打印藍牙日誌(非正式打包請設置爲true,以便於調試) .setThrowBleException(true)//設置是否拋出藍牙異常 .setAutoConnect(true)//設置是否自動鏈接 .setConnectFailedRetryCount(3)//設置鏈接失敗的重試次數 .setConnectTimeout(10 * 1000)//設置鏈接超時時長(默認10*1000 ms) .setScanPeriod(12 * 1000)//設置掃描時長(默認10*1000 ms) .setUuid_service(UUID.fromString("0000fee9-0000-1000-8000-00805f9b34fb"))//主服務的uuid .setUuid_write_cha(UUID.fromString("d44bc439-abfd-45a2-b575-925416129600"))//可寫特徵的uuid .create(getApplicationContext()); }
mBle.startScan(scanCallback); //掃描回調 BleScanCallback<BleDevice> scanCallback = new BleScanCallback<BleDevice>() { @Override public void onLeScan(final BleDevice device, int rssi, byte[] scanRecord) { ... //獲取到藍牙設備對象,根據自身需求進行操做(庫中已進行相同設備的過濾) } };
mBle.connect(device, connectCallback); //鏈接回調 private BleConnCallback<BleDevice> connectCallback = new BleConnCallback<BleDevice>() { @Override public void onConnectionChanged(BleDevice device) { if (device.isConnected()) { //鏈接成功以後設置通知(切記,很重要) setNotify(device); } Log.e(TAG, "onConnectionChanged: " + device.isConnected()); } @Override public void onConnectException(BleDevice device, int errorCode) { super.onConnectException(device, errorCode); Toast.makeText(BleActivity.this, "鏈接異常,異常狀態碼:" + errorCode, Toast.LENGTH_SHORT).show(); } };
鏈接異常狀態碼可參閱該項目的[wiki]
private void setNotify(BleDevice device) { /*鏈接成功後,設置通知*/ mBle.startNotify(device, new BleNotiftCallback<BleDevice>() { @Override public void onChanged(BleDevice device, BluetoothGattCharacteristic characteristic) { Log.e(TAG, "onChanged: 表示返回硬件MCU發來的數據"+Arrays.toString(characteristic.getValue())); } @Override public void onReady(BleDevice device) { Log.e(TAG, "onReady: 表示一切準備就緒,能夠進行讀寫(發送數據或者讀取數據)的標誌"); } @Override public void onServicesDiscovered(BluetoothGatt gatt) { Log.e(TAG, "onServicesDiscovered is success "); } @Override public void onNotifySuccess(BluetoothGatt gatt) { Log.e(TAG, "onNotifySuccess is success "); } }); }
當收到onChanged(BluetoothGattCharacteristic characteristic)回調時,則說明藍牙設備的數據發生改變了,通知程序做出改變。還有不少回調,他們對應的狀況不懂得能夠參考上面的原生API的詳細講解。
mBle.readRssi(mBle.getConnetedDevices().get(0), new BleReadRssiCallback<BleDevice>() { @Override public void onReadRssiSuccess(int rssi) { super.onReadRssiSuccess(rssi); Log.e(TAG, "onReadRssiSuccess: " + rssi); Toast.makeText(BleActivity.this, "onReadRssiSuccess:"+ rssi, Toast.LENGTH_SHORT).show(); } });
public void read(BleDevice device) { boolean result = mBle.read(device, new BleReadCallback<BleDevice>() { @Override public void onReadSuccess(BluetoothGattCharacteristic characteristic) { super.onReadSuccess(characteristic); byte[] data = characteristic.getValue(); Log.w(TAG, "onReadSuccess: " + Arrays.toString(data)); } }); if (!result) { Log.d(TAG, "讀取數據失敗!"); }
boolean result = mBle.write(device, changeLevelInner(), new BleWriteCallback<BleDevice>() { @Override public void onWriteSuccess(BluetoothGattCharacteristic characteristic) { Toast.makeText(BleActivity.this, "發送數據成功", Toast.LENGTH_SHORT).show(); } }); if (!result) { Log.e(TAG, "changeLevelInner: " + "發送數據失敗!"); }
try { //獲取整個文件的總字節 byte[]data = toByteArray(getAssets().open("WhiteChristmas.bin")); //發送大數據量的包(參數請查閱Demo Code) mBle.writeEntity(mBle.getConnetedDevices().get(0), data, 20, 50, new BleWriteEntityCallback<BleDevice>() { @Override public void onWriteSuccess() { L.e("writeEntity", "onWriteSuccess"); } @Override public void onWriteFailed() { L.e("writeEntity", "onWriteFailed"); } }); } catch (IOException e) { e.printStackTrace(); }
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ //此處第二個參數 不是特定的 好比你也能夠設置500 可是若是設備不支持500個字節則會返回最大支持數 mBle.setMTU(mBle.getConnetedDevices().get(0).getBleAddress(), 250, new BleMtuCallback<BleDevice>() { @Override public void onMtuChanged(BleDevice device, int mtu, int status) { super.onMtuChanged(device, mtu, status); ToastUtil.showToast("最大支持MTU:"+mtu); } }); }else { ToastUtil.showToast("設備不支持MTU"); }
//找到你須要升級文件的路徑(通常狀況都是保存再服務器上,一旦有更新會自動提示,而後APP下載並保存到本地,生成對應的file對象) File file = new File(...); //讀寫SD卡權限,此處略(6.0及以上需添加) OtaManager mOtaManager = new OtaManager(BleActivity.this); boolean result = mOtaManager.startOtaUpdate(file, (BleDevice) mBle.getConnetedDevices().get(0), mBle); Log.e("OTA升級結果:", result + "");
分析以前先來張BleLib庫API的結構圖供你們參考(下圖是1.x庫的結構,API名稱部分與當前有點不一樣):
該類提供了幾乎全部你須要用到的方法,包括藍牙掃描、鏈接、斷開、藍牙當前鏈接狀態等等,管理了藍牙操做的全部接口和方法。
該類的主要是來描述並記錄藍牙的屬性和狀態,如記錄藍牙名稱、藍牙MAC地址、藍牙別名(即修改以後的名稱)、藍牙鏈接狀態等。
該類是藍牙狀態類,定義了藍牙掃描、鏈接、通知使能、發送、接收等狀態的常量值(鏈接異常等狀態碼可參考該類)
該類是最重要的一個核心藍牙處理類,主要是藍牙操做中用到的各個方法的實現類,是整個藍牙的核心功能實現,Ble是對外提供全部藍牙操做接口的管理類。
在此要注意一些細節,好比大多數設備掃描的時候會重複掃描到相同藍牙設備,必需要進行過濾,開發應用時,必須還要進行產品過濾,好比經過設備的廣播包過濾,或者經過設備名過濾都是能夠的,以下(注意:要根據本身產品提供的廣播包進行過濾,下圖是咱們本身產品的):
/** * Verify the product broadcast parameters * @param data Parameter data * @return Whether the match */ public static boolean matchProduct(byte[] data) { if (data == null || data.length <= 0) { return false; } int i = 0; do { // Read packet size int len = data[i++] & 0xff; if (len > 0) { // Read packet data byte[] d = new byte[len]; int j = 0; do { d[j++] = data[i++]; } while (j < len); // Authentication Type and Length if (d.length > BROADCAST_SPECIFIC_PRODUCT.length && (d[0] & 0xFF) == BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA) { // Matching product parameters boolean passed = true; for (int x = 0; x < BROADCAST_SPECIFIC_PRODUCT.length; x++) { passed = passed && d[x + 1] == BROADCAST_SPECIFIC_PRODUCT[x]; } //Match successful if (passed) { return true; } } } } while (i < data.length); return false; }
OK,要注意的細節問題已經介紹的差很少了,若是感興趣的朋友能夠去應用該庫到本身的項目中。
Android BLE藍牙詳細解讀
代碼地址以下:
http://www.demodashi.com/demo/15062.html
注:本文著做權歸做者,由demo大師代發,拒絕轉載,轉載須要做者受權