这段时间微信周边摇一摇带动的ibeacon新兴市场火得一塌糊涂。
iBeacon 是苹果公司2013年9月发布的移动设备用OS(iOS7)上配备的新功能。其工作方式是,配备有 低功耗蓝牙(BLE)通信功能的设 备使用BLE技术向周围发送自己特有的ID,接收到该ID的应用软件会根据该ID采取一些行动。比如,在店铺里设置iBeacon通信模块的话,便可让iPhone和iPad上运行一资讯告知服务器,或者由服务器向顾客发送折扣券及进店积分。此外,还可以在家电发生故障或停止工作时使用iBeacon向应用软件发送资讯。
iOS这边官方已经有相关较为成熟的开发文档和封装类,但是安卓方面还没有官方的支持,只能自行开发或借助第三方sdk开发。安卓开发ibeacon是基于BLE开发的,所以大家需要先学习好安卓官方的BLE开发文档,其实iBeacon是BLE衍生版,所以iBeacon具有属性和特点BLE几乎都有,百度上的第三方包其实就是基于BLE开发的封装类。那么今天我来带大家一起来学习开发android
版的iBeacon吧:
安卓iBeacon开发前得先判断当前设备(手机)是否支持BLE功能:
//判断手机等设备是否支持BLE,即是否可以扫描iBeacon设备
bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
扫描蓝牙设备,将符合IBacon特征的设备对象转成IBeacon设备对象(由IBeaconProductor类转换 ):
// iBeacon设备扫描回调结果
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
final IBeacon ibeacon = IBeaconProductor.fromScanData(device, rssi, scanRecord);
final Commodity commodity = getCommodity(ibeacon);
runOnUiThread(new Runnable() {
@Override
public void run() {
mLeDeviceListAdapter.addCommodity(commodity);
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
};
IBeacon生产包装类,IBeaconProductor :
/**
* IBeacon生产包装类
* 帮助android扫描到的蓝牙设备对象转成IBeacon对象
*
* @author www.icheny.cn
* @date 2019.04.16
*/
public class IBeaconProductor {
/**
* 从蓝牙设备对象中解析出IBeacon对象
*
* @param device
* @param rssi
* @param scanData
* @return
*/
public static IBeacon fromScanData(BluetoothDevice device, int rssi, byte[] scanData) {
int startByte = 2;
boolean patternFound = false;
while (startByte <= 5) {
if (((int) scanData[startByte + 2] & 0xff) == 0x02 && ((int) scanData[startByte + 3] & 0xff) == 0x15) {
// 这是 iBeacon
patternFound = true;
break;
} else if (((int) scanData[startByte] & 0xff) == 0x2d && ((int) scanData[startByte + 1] & 0xff) == 0x24
&& ((int) scanData[startByte + 2] & 0xff) == 0xbf
&& ((int) scanData[startByte + 3] & 0xff) == 0x16) {
IBeacon beacon = new IBeacon();
beacon.major = 0;
beacon.minor = 0;
beacon.uuid = "00000000-0000-0000-0000-000000000000";
beacon.txPower = -55;
beacon.distance = String.format("%.2f", calculateAccuracy(beacon.txPower, rssi));
return beacon;
} else if (((int) scanData[startByte] & 0xff) == 0xad && ((int) scanData[startByte + 1] & 0xff) == 0x77
&& ((int) scanData[startByte + 2] & 0xff) == 0x00
&& ((int) scanData[startByte + 3] & 0xff) == 0xc6) {
IBeacon beacon = new IBeacon();
beacon.major = 0;
beacon.minor = 0;
beacon.uuid = "00000000-0000-0000-0000-000000000000";
beacon.txPower = -55;
beacon.distance = String.format("%.2f", calculateAccuracy(beacon.txPower, rssi));
return beacon;
}
startByte++;
}
if (patternFound == false) {
// 这不是iBeacon
return null;
}
IBeacon beacon = new IBeacon();
beacon.major = (scanData[startByte + 20] & 0xff) * 0x100 + (scanData[startByte + 21] & 0xff);
beacon.minor = (scanData[startByte + 22] & 0xff) * 0x100 + (scanData[startByte + 23] & 0xff);
beacon.txPower = (int) scanData[startByte + 24];
beacon.rssi = rssi;
// 格式化UUID
byte[] proximityUuidBytes = new byte[16];
System.arraycopy(scanData, startByte + 4, proximityUuidBytes, 0, 16);
String hexString = bytesToHexString(proximityUuidBytes);
StringBuilder sb = new StringBuilder();
sb.append(hexString.substring(0, 8));
sb.append("-");
sb.append(hexString.substring(8, 12));
sb.append("-");
sb.append(hexString.substring(12, 16));
sb.append("-");
sb.append(hexString.substring(16, 20));
sb.append("-");
sb.append(hexString.substring(20, 32));
beacon.uuid = sb.toString();
if (device != null) {
beacon.address = device.getAddress();
beacon.name = device.getName();
}
beacon.distance = String.format("%.2f", calculateAccuracy(beacon.txPower, rssi));
return beacon;
}
/**
* 转换十进制
*
* @param src
* @return
*/
private static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
/**
* 估算用户设备到IBeacon的距离
*
* @param txPower
* @param rssi
* @return
*/
private static double calculateAccuracy(int txPower, double rssi) {
if (rssi == 0) {
return -1.0; // if we cannot determine accuracy, return -1.
}
double ratio = rssi * 1.0 / txPower;
if (ratio < 1.0) {
return Math.pow(ratio, 10);
} else {
double accuracy = (0.89976) * Math.pow(ratio, 7.7095) + 0.111;
return accuracy;
}
}
}
模拟从后台服务器获取商品信息:
/**
* 模拟根据ibeacon信息从后台取对应商品据信息
* 这里面的uuid,major,minor 都是我这边的iBeacon设备
* 你可以填你自己的iBeacon设备相关信息,进行iBeacon设备过滤
*
* @param ibeacon
* @return
*/
private Commodity getCommodity(IBeacon ibeacon) {
if ("fda50693-a4e2-4fb1-afcf-c6eb07647825".equalsIgnoreCase(ibeacon.uuid) && 10001 == ibeacon.major// 这里是对照UUID,major,minor作为模拟唯一的识别id
&& 64120 == ibeacon.minor) {
return new Commodity("1122", R.drawable.a, 288.00, "老诚一锅6-8人餐\n6-8人餐,免费wifi,美味营养,回味无穷!", ibeacon.distance);
} else if ("fda50693-a4e2-4fb1-afcf-c6eb07647825".equalsIgnoreCase(ibeacon.uuid) && 10 == ibeacon.major
&& 7 == ibeacon.minor) {
return new Commodity("4455", R.drawable.b, 258.00, "净味真烤羊腿套餐\n烤羊腿套餐,可使用包间", ibeacon.distance);
} else if ("fda50693-a4e2-4fb1-afcf-c6eb07647825".equalsIgnoreCase(ibeacon.uuid)
&& 10111 == ibeacon.major && 7 == ibeacon.minor) {
return new Commodity("7788", R.drawable.c, 258.00, "新疆纸皮核桃\n【全国免费配送】新疆纸皮核桃2袋共1000g,仅55元,享价值116元(原价值每袋68元)",
ibeacon.distance);
}
return null;
}
OK,核心代码如上,这是模拟扫描周边iBeacon设备,过滤掉没有跟你公司合作的设备,并向用户推送这些有效设备相关是的商品信息,当然过滤规则可以自定义的,我这个过滤规则是应付教程的哈,代码很简单。下面我也会传代码供大家下载学习,注释也写得很详细。
下面是效果图:
下载DEMO源码 (文章,代码有小浮更新,更新时间:2016.03.29)
2019.04.16更新Android Studio的demo源码:IBeaconDemo
文笔不好,也没时间优化这篇文章,等有时间再修改细述。