android解析藍芽廣播所攜帶的資訊
阿新 • • 發佈:2019-02-04
前言
在可穿戴行業工作了兩年,各種需求都碰到過,前段時間客戶提出了一個要求藍芽在廣播的時候要攜帶一個特定的服務(UUID),只有攜帶了此服務(UUID)的藍芽裝置才能被搜尋到。之前都是以裝置名稱進行過濾的,突然改了一種方式當然要進行新的技術瞭解啊。
解決歷程
因為是抓取藍芽在廣播的時候攜帶的資訊,所以判斷資訊應該是在裝置藍芽被掃描到時可以抓取到資訊,於是我們看掃描藍芽的回撥
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
}
};
這是android掃描到藍芽的回撥,其中包括三個引數:
BluetoothDevice device:是藍芽的一些基本資訊(名稱,mac地址等)。
int rssi:是藍芽當前的訊號值,絕對值越小訊號越好。
byte[] scanRecord:字面理解是藍芽的位元組記錄。
此時,藍芽在廣播時所攜帶的服務(UUID)最有可能在scanRecord裡面,有了初步的估計就開始著手解析這個scanRecord,可是解析也需要知道規則才好解析啊,但是我對藍芽的廣播格式並不太瞭解,只能從android的程式碼層面想辦法了,這時我發現android提供了一個ScanRecord的類,這一發現使我太開心了,然後我就取檢視這個類發現這個類裡面提供了一個方法
public static ScanRecord parseFromBytes(byte[] scanRecord){
}
得到解析廣播後的ScanRecord例項。然後就可以直接呼叫裡面的方法獲取需要的資訊了。希望總是美好的。
在實際的程式碼中發現不能ScanRecord.parseFromBytes(scanRecord)的方法得到例項,這個發現頓時把我給整蒙了,獲取不到例項後面的也不能做啊。
但是問題還是要解決的,既然它提供了類那我也建一個類移植它的主要程式碼,程式碼如下
public class ScanRecordUtil{
private static final String TAG = "ScanRecordUtil";
// The following data type values are assigned by Bluetooth SIG.
// For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18.
private static final int DATA_TYPE_FLAGS = 0x01;
private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
private static final int DATA_TYPE_SERVICE_DATA = 0x16;
private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
// Flags of the advertising data.
private final int mAdvertiseFlags;
@Nullable
private final List<ParcelUuid> mServiceUuids;
private final SparseArray<byte[]> mManufacturerSpecificData;
private final Map<ParcelUuid, byte[]> mServiceData;
// Transmission power level(in dB).
private final int mTxPowerLevel;
// Local name of the Bluetooth LE device.
private final String mDeviceName;
// Raw bytes of scan record.
private final byte[] mBytes;
/**
* Returns the advertising flags indicating the discoverable mode and capability of the device.
* Returns -1 if the flag field is not set.
*/
public int getAdvertiseFlags() {
return mAdvertiseFlags;
}
/**
* Returns a list of service UUIDs within the advertisement that are used to identify the
* bluetooth GATT services.
*/
public List<ParcelUuid> getServiceUuids() {
return mServiceUuids;
}
/**
* Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific
* data.
*/
public SparseArray<byte[]> getManufacturerSpecificData() {
return mManufacturerSpecificData;
}
/**
* Returns the manufacturer specific data associated with the manufacturer id. Returns
* {@code null} if the {@code manufacturerId} is not found.
*/
@Nullable
public byte[] getManufacturerSpecificData(int manufacturerId) {
return mManufacturerSpecificData.get(manufacturerId);
}
/**
* Returns a map of service UUID and its corresponding service data.
*/
public Map<ParcelUuid, byte[]> getServiceData() {
return mServiceData;
}
/**
* Returns the service data byte array associated with the {@code serviceUuid}. Returns
* {@code null} if the {@code serviceDataUuid} is not found.
*/
@Nullable
public byte[] getServiceData(ParcelUuid serviceDataUuid) {
if (serviceDataUuid == null) {
return null;
}
return mServiceData.get(serviceDataUuid);
}
/**
* Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}
* if the field is not set. This value can be used to calculate the path loss of a received
* packet using the following equation:
* <p>
* <code>pathloss = txPowerLevel - rssi</code>
*/
public int getTxPowerLevel() {
return mTxPowerLevel;
}
/**
* Returns the local name of the BLE device. The is a UTF-8 encoded string.
*/
@Nullable
public String getDeviceName() {
return mDeviceName;
}
/**
* Returns raw bytes of scan record.
*/
public byte[] getBytes() {
return mBytes;
}
private ScanRecordUtil(List<ParcelUuid> serviceUuids,
SparseArray<byte[]> manufacturerData,
Map<ParcelUuid, byte[]> serviceData,
int advertiseFlags, int txPowerLevel,
String localName, byte[] bytes) {
mServiceUuids = serviceUuids;
mManufacturerSpecificData = manufacturerData;
mServiceData = serviceData;
mDeviceName = localName;
mAdvertiseFlags = advertiseFlags;
mTxPowerLevel = txPowerLevel;
mBytes = bytes;
}
/**
* Parse scan record bytes to {@link ScanRecord}.
* <p>
* The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
* <p>
* All numerical multi-byte entities and values shall use little-endian <strong>byte</strong>
* order.
*
* @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
* @hide
*/
public static ScanRecordUtil parseFromBytes(byte[] scanRecord) {
if (scanRecord == null) {
return null;
}
Log.e(TAG + "MYX23P", "進入parseFromBytes");
int currentPos = 0;
int advertiseFlag = -1;
List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
String localName = null;
int txPowerLevel = Integer.MIN_VALUE;
SparseArray<byte[]> manufacturerData = new SparseArray<byte[]>();
Map<ParcelUuid, byte[]> serviceData = new ArrayMap<ParcelUuid, byte[]>();
try {
while (currentPos < scanRecord.length) {
// length is unsigned int.
int length = scanRecord[currentPos++] & 0xFF;
if (length == 0) {
break;
}
// Note the length includes the length of the field type itself.
int dataLength = length - 1;
// fieldType is unsigned int.
int fieldType = scanRecord[currentPos++] & 0xFF;
switch (fieldType) {
case DATA_TYPE_FLAGS:
advertiseFlag = scanRecord[currentPos] & 0xFF;
break;
case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos,
dataLength, MyBluetoothUuid.UUID_BYTES_16_BIT, serviceUuids);
break;
case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos, dataLength,
MyBluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
break;
case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos, dataLength,
MyBluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
break;
case DATA_TYPE_LOCAL_NAME_SHORT:
case DATA_TYPE_LOCAL_NAME_COMPLETE:
localName = new String(
extractBytes(scanRecord, currentPos, dataLength));
break;
case DATA_TYPE_TX_POWER_LEVEL:
txPowerLevel = scanRecord[currentPos];
break;
case DATA_TYPE_SERVICE_DATA:
// The first two bytes of the service data are service data UUID in little
// endian. The rest bytes are service data.
int serviceUuidLength = MyBluetoothUuid.UUID_BYTES_16_BIT;
byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
serviceUuidLength);
ParcelUuid serviceDataUuid = MyBluetoothUuid.parseUuidFrom(
serviceDataUuidBytes);
byte[] serviceDataArray = extractBytes(scanRecord,
currentPos + serviceUuidLength, dataLength - serviceUuidLength);
serviceData.put(serviceDataUuid, serviceDataArray);
break;
case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
// The first two bytes of the manufacturer specific data are
// manufacturer ids in little endian.
int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8) +
(scanRecord[currentPos] & 0xFF);
byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,
dataLength - 2);
manufacturerData.put(manufacturerId, manufacturerDataBytes);
break;
default:
// Just ignore, we don't handle such data type.
break;
}
currentPos += dataLength;
}
if (serviceUuids.isEmpty()) {
serviceUuids = null;
}
return new ScanRecordUtil(serviceUuids, manufacturerData, serviceData,
advertiseFlag, txPowerLevel, localName, scanRecord);
} catch (Exception e) {
Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
// As the record is invalid, ignore all the parsed results for this packet
// and return an empty record with raw scanRecord bytes in results
return new ScanRecordUtil(null, null, null, -1, Integer.MIN_VALUE, null, scanRecord);
}
}
@Override
public String toString() {
return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
+ ", mManufacturerSpecificData=" + MyBluetoothLeUtils.toString(mManufacturerSpecificData)
+ ", mServiceData=" + MyBluetoothLeUtils.toString(mServiceData)
+ ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
}
// Parse service UUIDs.
private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
int uuidLength, List<ParcelUuid> serviceUuids) {
while (dataLength > 0) {
byte[] uuidBytes = extractBytes(scanRecord, currentPos,
uuidLength);
serviceUuids.add(MyBluetoothUuid.parseUuidFrom(uuidBytes));
dataLength -= uuidLength;
currentPos += uuidLength;
}
return currentPos;
}
// Helper method to extract bytes from byte array.
private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
byte[] bytes = new byte[length];
System.arraycopy(scanRecord, start, bytes, 0, length);
return bytes;
}
}
這樣採用它的核心解析廣播的功能就得到了所有的資訊。
小結
碰到問題時,仔細去找解決方法一定會解決的