Bluetooth BLE Beacon初探
iBeacon是蘋果公司2013年9月發佈的移動設備用OS(ios7)上配備的新功能。其主要的工作方式就是:配備有低功耗藍牙(BLE)通信功能的設備使用BLE技術向周圍發送自己特有的ID。
這個網址對iBeacon進行了基本介紹,建議大家去閱讀一下:http://www.beaconsandwich.com/what-is-ibeacon.html
在2015年,谷歌發佈Eddystone,它其實類似於iBeacon。
對於這兩者的主要區別,大家可以瀏覽這個網址的內容:https://www.zhihu.com/question/32708729
本文的demo開發是基於github上的一個開源項目Altbeacon:https://github.com/AltBeacon/android-beacon-library
下面進入主題:
- 創建工程之後,在buid.gradle中導入AltBeacon Library:
dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.2.1' compile 'com.android.support:design:23.2.1' compile 'org.altbeacon:android-beacon-library:2.9' }
- 創建一個Application,這個Application需要實現一個接口:BootstrapNotifier,關鍵代碼如下:
在以上代碼中,有幾個關鍵的點:調用getBeaconParsers()獲取BeaconParsers列表,然後添加我們自己定義的BeaconParsers。調用方法setBeaconLayout(String)來設置Beacon格式。private RegionBootstrap regionBootstrap; private BackgroundPowerSaver backgroundPowerSaver; @Override public void onCreate() { super.onCreate(); BeaconManager beaconManager = org.altbeacon.beacon.BeaconManager.getInstanceForApplication(this); beaconManager.getBeaconParsers().clear(); beaconManager.getBeaconParsers().add(new BeaconParser() .setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25")); Region region = new Region("all-region-beacon",null,null,null); regionBootstrap = new RegionBootstrap(this,region); backgroundPowerSaver = new BackgroundPowerSaver(this); }
上述方法的傳入參數格式說明如下:setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25")
m-matching byte sequence for this beacon type to parse(exactly one required)s - ServiceUuid for this beacon type to parse(option,only for Gatt-based beacons)i - identifier(at least one required,multiple allowed)p - power calibration field(exactly one required)d - data field(option,multiple allowed)如果不進行重新設置,那麼它默認的BeaconLayout是:"m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25",它只會去匹配字節序列爲beac的Beacon設備,筆者開發使用的Beacon設備的匹配字節序列是0215,所以要修改成0215,下面會詳細介紹如何獲取Beacon的匹配字節序列。在進行BLE設備掃描時,會有一個回調方法被調用到--onLeScan(final BluetoothDevice device,int rssi,byte[]scanRecord),而以上所述的Beacon格式的所有內容都包含在scanRecord這個參數中,下面根據我所獲取到的數據來具體分析參數scanRecord的內容:
將以上數據中的一些無關數據去除掉,剩下了:02 01 1e 1a ff 4c 00 02 15 12 23 34 45 56 67 78 89 90 01 12 23 34 45 56 67 00 12 00 21 c5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
以上數據共有30字節,包含了匹配字節序列、UUID、MajorID、MinorID、TxPower等信息。經過整理,我們得到:02 01 1e 1a ff 4c 00 02 15 12 23 34 45 56 67 78 89 90 01 12 23 34 45 56 67 00 12 00 21 c5
以上代碼段第一行中的0215就是我們程序中需要設置的匹配字節序列。那麼經過修改之後,我們就能掃描到自己的Beacon設備了。在以上的關鍵代碼中,創建了一個BackgroundPowerSaver實例,只要在這個Application中持有這個類對象,那麼就能實現後臺節省電量的功能。Region region = new Region("all-region-becon",null,null,null),實例化該對象時可以傳入一些參數,他們分別是String uniqueId,Identifier id1,Identifier id2,Identifier id3,利用這些傳入參數可以選擇性的獲取指定的Beacon信號。筆者此處沒有傳入相關參數,表示獲取所有的Beacon信號。02 01 1e 1a ff 4c 00 02 15 //beacon的前綴,包含匹配字節序列 12 23 34 45 56 67 78 89 90 01 12 23 34 45 56 67 //UUID 00 12 //MajorID 00 21 //MinorID c5 //TxPower
- 創建一個Service來進行後臺監聽Beacon信號,關鍵代碼如下:
public class BeaconService extends Service implements BeaconConsumer, RangeNotifier { private static final long DEFAULT_BACKGROUND_SCAN_PERIOD = 1000L; private static final long DEFAULT_BACKGROUND_BETWEEN_SCAN_PERIOD = 1000L; private BeaconManager beaconManager = BeaconManager.getInstanceForApplication(this); public BeaconService() { } @Override public void onCreate() { super.onCreate(); initBeacon(); beaconManager.bind(this); } private void initBeacon() { beaconManager.setBackgroundScanPeriod(DEFAULT_BACKGROUND_SCAN_PERIOD); beaconManager.setBackgroundBetweenScanPeriod(DEFAULT_BACKGROUND_BETWEEN_SCAN_PERIOD); } @Override public void onDestroy() { super.onDestroy(); if (beaconManager != null) beaconManager.removeRangeNotifier(this); } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onBeaconServiceConnect() { Region region = new Region("myRangingUniqueId", null, null, null); beaconManager.addRangeNotifier(this); try { beaconManager.startRangingBeaconsInRegion(region); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void didRangeBeaconsInRegion(Collection<Beacon> collections, Region region) { List<Beacon> beacons = new ArrayList<>(); for (Beacon beacon : collections) { beacons.add(beacon); } Intent intent = new Intent(MainActivity.BEACON_ACTION); intent.putParcelableArrayListExtra("beacon", (ArrayList<? extends Parcelable>) beacons);//因爲Beacon繼承了Parcelable, sendBroadcast(intent); // 所以能通過這個方式來傳遞數據 } }
在這裏,大家可以自定義設置一下後臺掃描間隔,不然,它默認的後臺掃描間隔是300000毫秒,也就是5分鐘setBackgroundScanPeriod()和setBackgroundBetweenScanPeriod(),設置後臺掃描的時間間隔,我沒有去仔細閱讀源碼,根據我的實驗,發現如下圖所示的結論(僅供參考): - 在MainActivity中利用startService()方式開啓後臺服務:
public class MainActivity extends AppCompatActivity { private BeaconBroadcastReceiver beaconBroadcastReceiver; private static final String TAG = "MainActivity"; public static final String BEACON_ACTION = "com.juju.beacontest.beacon.action"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); beaconBroadcastReceiver = new BeaconBroadcastReceiver(); Intent intent = new Intent(MainActivity.this, BeaconService.class); startService(intent); } @Override protected void onResume() { super.onResume(); registerReceiver(beaconBroadcastReceiver, getBeaconIntentFilter()); } @Override protected void onPause() { super.onPause(); if (beaconBroadcastReceiver != null) unregisterReceiver(beaconBroadcastReceiver); } class BeaconBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BEACON_ACTION.equals(action)) { List<Beacon> beacons = intent.getParcelableArrayListExtra("beacon"); for (Beacon beacon : beacons){ Log.i(TAG, "onReceive: "+beacon.getServiceUuid()); } } } } IntentFilter getBeaconIntentFilter() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BEACON_ACTION); return intentFilter; } }
本文的Demo代碼已經上傳,大家可以自行下載參考。第一次進行Beacon開發,所以想記錄一下beacon的基本開發過程。當然,本文可能會有一些錯誤的地方,歡迎各位大神指正。另外,如果大家有什麼疑問,可以在下面進行評論,在我的能力範圍之內,一定會給你回答的。