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
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掃描列印的原始碼就不放出了,很簡單。