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的基本开发过程。当然,本文可能会有一些错误的地方,欢迎各位大神指正。另外,如果大家有什么疑问,可以在下面进行评论,在我的能力范围之内,一定会给你回答的。