android 從4.3系統開始可以連接BLE設備,這個大家都知道了。iOS是從7.0版本開始支持BLE。 android 進入5.0時代時,開放了一個新功能,手機可以模擬設備發出BLE廣播, 這個新功能其實是 對標於 iOS系統的手機模擬iBeacon設備。 先介紹一下BLE的廣播, BLE設備之所以能被手機掃描到,是因為 BLE設備一直在每隔 一段時間廣播一次,這個廣播里面包含很多數據。 手機掃描BLE設備代碼如下: public void startScan(){ bluetoothAdapter.startLeScan(leScanCallback); } public void stopScan(){ bluetoothAdapter.stopLeScan(leScanCallback); } private LeScanCallback leScanCallback=new LeScanCallback() { @Override public void onLeScan(BluetoothDevice bluetoothdeivce, int rssi, byte[] scandata) { //把byte數組轉成16進制字符串,方便查看 Log.e("TAG","scandata:"+ CYUtils.Bytes2HexString(scandata)); } }; ok,這段代碼大家在做連接BLE設備進行通訊的時候,已經很熟悉了。其中的 byte數組 scandata就是 BLE設備的廣播數據。 那么接下來,我們開始使用 手機1 模擬成BLE設備來發送廣播,然后用手機2 來進行掃描查看廣播數據 scandata 首先獲取 BluetoothAdapter, 熟悉的代碼: BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter(); 進行廣播的時候需要用到BluetoothLeAdvertiser,進行實例化: mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); 實例化好之后就可以進行廣播數據了,開啟廣播方法是: BluetoothLeAdvertiser: public void startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, final AdvertiseCallback callback) 其中, AdvertiseSettings 是廣播的一些設置,比如,廣播間隔,是否可以連接等等; AdvertiseData 就是廣播數據了, AdvertiseCallback是廣播回調,會告訴你廣播成功還是失敗。 先給一段完整廣播代碼如下: public void startAction(View v){ byte[] broadcastData ={0x34,0x56}; mBluetoothLeAdvertiser.startAdvertising(createAdvSettings(true, 0), createAdvertiseData(broadcastData), mAdvertiseCallback); } public void stopAction(View v) { mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback); } public AdvertiseSettings createAdvSettings(boolean connectable, int timeoutMillis) { AdvertiseSettings.Builder mSettingsbuilder = new AdvertiseSettings.Builder(); mSettingsbuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY); mSettingsbuilder.setConnectable(connectable); mSettingsbuilder.setTimeout(timeoutMillis); AdvertiseSettings mAdvertiseSettings = mSettingsbuilder.build(); return mAdvertiseSettings; } public AdvertiseData createAdvertiseData(byte[] data) { AdvertiseData.Builder mDataBuilder = new AdvertiseData.Builder(); mDataBuilder.addManufacturerData(0x01AC, data); AdvertiseData mAdvertiseData = mDataBuilder.build(); return mAdvertiseData; } private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() { @Override public void onStartSuccess(AdvertiseSettings settingsInEffect) { super.onStartSuccess(settingsInEffect); ToastUtils.showToast(MainActivity.this, "開啟廣播成功", 2000); } @Override public void onStartFailure(int errorCode) { super.onStartFailure(errorCode); ToastUtils.showToast(MainActivity.this, "開啟廣播失敗 errorCode:" + errorCode, 2000); } }; 其中,廣播數據broadcastData 我暫時直接先定死為2個字節 0x3456,同樣在createAdvertiseData里面 也有定死的數據 0x01AC . 開啟成功之后 我們使用手機2 來掃描看下廣播的數據是什么: E/TAG: scandata:02011A05FFAC0134560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 log打印出來的scandata 有效數據是 02011A05FFAC013456 . 給大家解釋一下這個數據的意思 為了看清楚,我分段如下: 02011A 05 FF AC01 3456 (注,這里的都是16進制數字) 02011A這3個字節,02表示后面一段數據長度為2字節,01表示數據類型是flag ,1A就是flag的數據了 05 表示后面的一段數據長度為 5個字節, FF一個字節,AC01 兩個字節,3456兩個字節,加起來一共5個字節,老鐵沒毛病 FF,是一個數據類型,這是我們通過代碼mDataBuilder.addManufacturerData(0x01AC, data); 添加廣播數據時候設置的 ManufacturerData 是指設備廠商自定義數據,FF 就是代表下面的數據實體是廠商數據. 第一個參數0x01AC,是廠商id,id長度為2個字節,不足2個字節系統會補0,可以看到log打印出來的是 AC01,順序是倒過來的,這點要注意! 如果我代碼是這樣寫的 : mDataBuilder.addManufacturerData(0xAC, data); //只寫了一個字節的id 那么使用手機2 掃描出的scandata是: E/TAG: scandata:02011A05FFAC0034560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 可以看到,AC后面系統自動補了00 廣播數據出除了可以添加ManufacturerData,還可以添加ServerUUID, 代碼如下: public AdvertiseData createAdvertiseData(byte[] data) { AdvertiseData.Builder mDataBuilder = new AdvertiseData.Builder(); mDataBuilder.addManufacturerData(0x01AC, data); mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb")); AdvertiseData mAdvertiseData = mDataBuilder.build(); return mAdvertiseData; } 代碼添加了一個 AE8F的 server uuid, 使用手機2 掃描的scandata 如下: E/TAG: scandata:02011A05FFAC01345603038FAE00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 直接看03038FAE 這一段,第一個03 表示后面的一段數據長度為3個字節 第二個03 表示這個數據類型是 server uuid類型,uuid的數據就是8FAE,順序是倒過來的! 有人會問: 如果 我把addServiceUuid代碼放在 addManufacturerData 前面,掃描的數據順序是什么樣的呢? 答案 還是: E/TAG: scandata:02011A05FFAC01345603038FAE00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 可以添加多個 server uuid嗎? 可以,代碼如下: public AdvertiseData createAdvertiseData(byte[] data) { AdvertiseData.Builder mDataBuilder = new AdvertiseData.Builder(); mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb")); mDataBuilder.addManufacturerData(0x01AC, data); AdvertiseData mAdvertiseData = mDataBuilder.build(); return mAdvertiseData; } 掃描的結果是: E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 直接看0503 8FAE E1FF 這一段, 05 表示后面的一段數據長度是5個字節,03表示數據類型是 server uuid, 8FAE是第一個uuid, E1FF是第二個uuid 這個ServerUUID 有什么用呢? 不知大家在掃描BLE設備的時候,有沒有注意到這個方法: BluetoothAdapter public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback) 這個方法也可以用來掃描BLE設備,但是多了一個參數, UUID數組, 這個掃描方法是用來過濾BLE設備用的,比如 你公司開發一個 藍牙防丟器APP,你使用 startLeScan(callback)這個方法掃描的話,你會發現你掃描到周圍的所有的BLE設備,同事戴的小米手環可能也被你掃描到,這樣讓用戶來選擇設備進行連接的話可能就比較迷糊,startLeScan(serviceUuids,callback) 這個方法在掃描的時候會過濾廣播里的數據,只有符合的BLE設備才會被掃描回調。 所以,你們公司的藍牙防丟器設備可以在廣播字段里加入特定的server uuid, app掃描的時候可以過濾其他設備。 我們來實現一下這個功能, 修改 手機2 的掃描代碼: public void startScan(){ UUID[] serviceUuids = new UUID[] { UUID .fromString("0000ae8f-0000-1000-8000-00805f9b34fb") }; bluetoothAdapter.startLeScan(serviceUuids, leScanCallback); } public void stopScan(){ bluetoothAdapter.stopLeScan(leScanCallback); } private LeScanCallback leScanCallback=new LeScanCallback() { @Override public void onLeScan(BluetoothDevice bluetoothdeivce, int rssi, byte[] scandata) { //把byte數組轉成16進制字符串,方便查看 Log.e("TAG","scandata:"+ CYUtils.Bytes2HexString(scandata)); } }; 掃描結果是這樣的: 05-23 16:13:30.522 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 05-23 16:13:30.625 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 05-23 16:13:30.735 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 05-23 16:13:30.847 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 05-23 16:13:30.955 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 05-23 16:13:31.061 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 05-23 16:13:31.192 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 05-23 16:13:31.283 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 05-23 16:13:31.369 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 05-23 16:13:31.480 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 可以發現,掃描結果里面只會出現擁有 AE8F 這個uuid的 BLE設備,搜索不到其他設備 注意:部分手機使用startLeScan(serviceUuids,callback)這個方法過濾設備 會掃描不到設備,即使這個設備UUID符合過濾條件,我歸結為手機/系統問題,如三星手機 這樣,我們知道,廣播數據可以添加ManufacturerData,還可以添加ServerUUID, 還有嗎? 有,代碼如下: public AdvertiseData createAdvertiseData(byte[] data) { AdvertiseData.Builder mDataBuilder = new AdvertiseData.Builder(); mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceData( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"),new byte[]{0x64,0x12}); mDataBuilder.addManufacturerData(0x01AC, data); AdvertiseData mAdvertiseData = mDataBuilder.build(); return mAdvertiseData; } 掃描結果如下: E/TAG: scandata:02011A05FFAC01345605038FAEE1FF05168FAE64120000000000000000000000000000000000000000000000000000000000000000000000000000000000 直接看05168FAE6412 這一段,05依然表示下面一段數據長度為5個字節,16表示數據類型為 server data, 8FAE表示這個數據的uuid是AE8F, 6412就是數據本體了. 那么這個 server data能做什么呢?比如有這樣一個 產品:溫度計,溫度計硬件在廣播字段里的server data里面加入它測量的溫度,這樣APP可以不連接溫度計設備 只通過掃描就知道溫度了,是不是很方便. 以下有幾個坑請大家注意一下: 情況1: public AdvertiseData createAdvertiseData(byte[] data) { AdvertiseData.Builder mDataBuilder = new AdvertiseData.Builder(); // mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceData( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"),new byte[]{0x64,0x12}); mDataBuilder.addManufacturerData(0x01AC, data); AdvertiseData mAdvertiseData = mDataBuilder.build(); return mAdvertiseData; } 我注釋了一句代碼,廣播字段里我沒有 添加 ae8f這個 uuid,而直接添加了 ae8f的data 為 0x6412,那么掃描結果如何? 使用startLeScan(serviceUuids,callback)過濾 ae8f這個uuid,沒有掃描結果; 使用startLeScan(callback),掃描結果如下: E/TAG: scandata:02011A05FFAC0134560303E1FF05168FAE641200000000000000000000000000000000000000000000000000000000000000000000000000000000000000 可以看到是有 ae8f對應的數據 6412,但是server uuid里面是沒有 ae8f的. 情況2: 代碼順序1: mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceData( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"),new byte[]{0x22,0x44}); mDataBuilder.addServiceData( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"),new byte[]{0x54,0x12}); //掃描結果 E/TAG: scandata:02011A05FFAC01345605038FAEE1FF05168FAE54120000000000000000000000000000000000000000000000000000000000000000000000000000000000 代碼順序2: mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceData( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"),new byte[]{0x54,0x11}); mDataBuilder.addServiceData( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"),new byte[]{0x22,0x43}); //掃描結果 E/TAG: scandata:02011A05FFAC01345605038FAEE1FF05168FAE54110000000000000000000000000000000000000000000000000000000000000000000000000000000000 代碼順序3: mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceData( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"),new byte[]{0x54,0x1A}); mDataBuilder.addServiceData( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"),new byte[]{0x22,0x47}); //掃描結果 E/TAG: scandata:02011A05FFAC0134560503E1FF8FAE05168FAE541A0000000000000000000000000000000000000000000000000000000000000000000000000000000000 代碼順序4: mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb")); mDataBuilder.addServiceData( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"),new byte[]{0x22,0x48}); mDataBuilder.addServiceData( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"),new byte[]{0x54,0x1B}); //掃描結果 E/TAG: scandata:02011A05FFAC0134560503E1FF8FAE05168FAE541B0000000000000000000000000000000000000000000000000000000000000000000000000000000000 情況2總結:從上面4個代碼順序的結果來看,總是掃描到 ae8f這個uuid對應的數據,沒有第二個 server data,但是為什么每次都是ae8f?我TM也不知道!! AdvertiseData介紹完畢,下面再稍微介紹一下 AdvertiseSettings AdvertiseSettings.Builder mSettingsbuilder = new AdvertiseSettings.Builder(); mSettingsbuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY); mSettingsbuilder.setConnectable(connectable); mSettingsbuilder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH); mSettingsbuilder.setTimeout(0); AdvertiseSettings mAdvertiseSettings = mSettingsbuilder.build(); setAdvertiseMode(int advertiseMode) 設置廣播的模式,低功耗,平衡和低延遲三種模式; 對應 AdvertiseSettings.ADVERTISE_MODE_LOW_POWER ,ADVERTISE_MODE_BALANCED ,ADVERTISE_MODE_LOW_LATENCY 從左右到右,廣播的間隔會越來越短 setConnectable(boolean connectable) 設置是否可以連接。 廣播分為可連接廣播和不可連接廣播,一般不可連接廣播應用在iBeacon設備上,這樣APP無法連接上iBeacon設備 setTimeout(int timeoutMillis) 設置廣播的最長時間,最大值為常量AdvertiseSettings.LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000; 180秒 設為0時,代表無時間限制會一直廣播 setTxPowerLevel(int txPowerLevel) 設置廣播的信號強度 常量有AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW,ADVERTISE_TX_POWER_LOW,ADVERTISE_TX_POWER_MEDIUM,ADVERTISE_TX_POWER_HIGH 從左到右分別表示強度越來越強. 舉例:當設置為ADVERTISE_TX_POWER_ULTRA_LOW時, 手機1和手機2放在一起,手機2掃描到的rssi信號強度為-56左右, 當設置為ADVERTISE_TX_POWER_HIGH 時, 掃描到的信號強度為-33左右, 信號強度越大,表示手機和設備靠的越近 好了,關於BluetoothLeAdvertiser 的用法介紹完畢!!!! 可能有人會說,bluetoothAdapter.startLeScan(leScanCallback); 這個方法過時了怎么辦,那可以看一下我的另一篇文章 《android BLE 掃描BLE設備 BluetoothLeScanner》 源碼附件: 模擬BLE廣播源碼:http://pan.baidu.com/s/1bptOQyb 手機2掃描打印的源碼就不放出了,很簡單。 android BLE Peripheral 手機模擬設備發出BLE廣播 BluetoothLeAdvertiser