通过 AltBeacon 库监视从我的应用程序发布广告的信标

Monitoring beacons that are advertising from my application through AltBeacon library

我正在开发一种解决方案,使用 AltBeacon 库以 iBeacon 格式进行广告和扫描。我担心的是库会扫描所有设备,这很好,但在解析扫描的设备后,它还会跟踪广告设备,这些设备不是我的应用程序的广告。有没有办法通过使用图书馆来解决这个问题?如果不是,那么替代解决方案可能是什么。 跟踪仅在我的应用程序中投放广告的广告信标对我来说非常重要。

这是通过 AltBeacon 库以 iBeacon 格式做广告时使用的代码:

BluetoothManager bluetoothManager =
        (BluetoothManager) applicationContext.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager != null) {
    BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
    BluetoothLeAdvertiser mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
    if (mBluetoothLeAdvertiser != null) {
        beacon = new Beacon.Builder()
                .setId1(userId)
                .setId2("1")
                .setId3("1")
                .setManufacturer(0x004C)
                .setTxPower(-75)
                .setDataFields(Arrays.asList(new Long[]{0l}))
                .build();
        beaconParser = new BeaconParser()
                .setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24");
        beaconTransmitter = new BeaconTransmitter(InventaSdk.getContext(), beaconParser);
        beaconTransmitter.setBeacon(beacon);
    }
}

编辑:

正在解析 Beacon 代码:

/**
 * Construct a Beacon from a Bluetooth LE packet collected by Android's Bluetooth APIs,
 * including the raw Bluetooth device info
 *
 * @param scanData The actual packet bytes
 * @param rssi The measured signal strength of the packet
 * @param device The Bluetooth device that was detected
 * @return An instance of a <code>Beacon</code>
 */
public Beacon fromScanData(byte[] scanData, int rssi, BluetoothDevice device) {
    return fromScanData(scanData, rssi, device, new Beacon());
}

protected Beacon fromScanData(byte[] bytesToProcess, int rssi, BluetoothDevice device, Beacon beacon) {
    BleAdvertisement advert = new BleAdvertisement(bytesToProcess);
    boolean parseFailed = false;
    Pdu pduToParse = null;
    int startByte = 0;
    ArrayList<Identifier> identifiers = new ArrayList<Identifier>();
    ArrayList<Long> dataFields = new ArrayList<Long>();

    for (Pdu pdu: advert.getPdus()) {
        if (pdu.getType() == Pdu.GATT_SERVICE_UUID_PDU_TYPE ||
                pdu.getType() == Pdu.MANUFACTURER_DATA_PDU_TYPE) {
            pduToParse = pdu;
            LogHelper.d(TAG, "Processing pdu type: "+pdu.getType()+bytesToHex(bytesToProcess)+" with startIndex: "+pdu.getStartIndex()+" endIndex: "+pdu.getEndIndex());
            break;
        }
        else {
            LogHelper.d(TAG, "Ignoring pdu type %02X "+ pdu.getType());
        }
    }
    if (pduToParse == null) {
        LogHelper.d(TAG, "No PDUs to process in this packet.");
        parseFailed = true;
    }
    else {
        byte[] serviceUuidBytes = null;
        byte[] typeCodeBytes = longToByteArray(getMatchingBeaconTypeCode(), mMatchingBeaconTypeCodeEndOffset - mMatchingBeaconTypeCodeStartOffset + 1);
        if (getServiceUuid() != null) {
            serviceUuidBytes = longToByteArray(getServiceUuid(), mServiceUuidEndOffset - mServiceUuidStartOffset + 1, false);
        }
        startByte = pduToParse.getStartIndex();
        boolean patternFound = false;

        if (getServiceUuid() == null) {
            if (byteArraysMatch(bytesToProcess, startByte + mMatchingBeaconTypeCodeStartOffset, typeCodeBytes)) {
                patternFound = true;
            }
        } else {
            if (byteArraysMatch(bytesToProcess, startByte + mServiceUuidStartOffset, serviceUuidBytes) &&
                    byteArraysMatch(bytesToProcess, startByte + mMatchingBeaconTypeCodeStartOffset, typeCodeBytes)) {
                patternFound = true;
            }
        }



        if (patternFound == false) {
            // This is not a beacon
            if (getServiceUuid() == null) {
                LogHelper.d(TAG, "This is not a matching Beacon advertisement. (Was expecting   "+byteArrayToString(typeCodeBytes)
                                    + ".The bytes I see are: "+
                            bytesToHex(bytesToProcess));

            } else {
                LogHelper.d(TAG, "This is not a matching Beacon advertisement. Was expecting "+
                        byteArrayToString(serviceUuidBytes)+
                        " at offset "+startByte + mServiceUuidStartOffset+"and "+byteArrayToString(typeCodeBytes)+
                        " at offset "+ startByte + mMatchingBeaconTypeCodeStartOffset + "The bytes I see are: "
                        + bytesToHex(bytesToProcess));
            }
            parseFailed = true;
            beacon =  null;
        } else {
            LogHelper.d(TAG, "This is a recognized beacon advertisement -- "+
                        byteArrayToString(typeCodeBytes)+"seen");
            LogHelper.d(TAG, "Bytes are: "+ bytesToHex(bytesToProcess));
        }

        if (patternFound) {
            if (bytesToProcess.length <= startByte+mLayoutSize && mAllowPduOverflow) {
                // If the layout size is bigger than this PDU, and we allow overflow.  Make sure
                // the byte buffer is big enough by zero padding the end so we don't try to read
                // outside the byte array of the advertisement
                LogHelper.d(TAG, "Expanding buffer because it is too short to parse: "+bytesToProcess.length+", needed: "+(startByte+mLayoutSize));
                bytesToProcess = ensureMaxSize(bytesToProcess, startByte+mLayoutSize);
            }
            for (int i = 0; i < mIdentifierEndOffsets.size(); i++) {
                int endIndex = mIdentifierEndOffsets.get(i) + startByte;

                if (endIndex > pduToParse.getEndIndex() && mIdentifierVariableLengthFlags.get(i)) {
                    LogHelper.d(TAG, "Need to truncate identifier by "+(endIndex-pduToParse.getEndIndex()));
                    // If this is a variable length identifier, we truncate it to the size that
                    // is available in the packet
                    int start = mIdentifierStartOffsets.get(i) + startByte;
                    int end = pduToParse.getEndIndex()+1;
                    if (end <= start) {
                        LogHelper.d(TAG, "PDU is too short for identifer.  Packet is malformed");
                        return null;
                    }
                    Identifier identifier = Identifier.fromBytes(bytesToProcess, start, end, mIdentifierLittleEndianFlags.get(i));
                    identifiers.add(identifier);
                }
                else if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) {
                    parseFailed = true;
                    LogHelper.d(TAG, "Cannot parse identifier "+i+" because PDU is too short.  endIndex: " + endIndex + " PDU endIndex: " + pduToParse.getEndIndex());
                }
                else {
                    Identifier identifier = Identifier.fromBytes(bytesToProcess, mIdentifierStartOffsets.get(i) + startByte, endIndex+1, mIdentifierLittleEndianFlags.get(i));
                    identifiers.add(identifier);
                }
            }
            for (int i = 0; i < mDataEndOffsets.size(); i++) {
                int endIndex = mDataEndOffsets.get(i) + startByte;
                if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) {
                    LogHelper.d(TAG, "Cannot parse data field "+i+" because PDU is too short.  endIndex: " + endIndex + " PDU endIndex: " + pduToParse.getEndIndex()+".  Setting value to 0");
                    dataFields.add(new Long(0l));
                }
                else {
                    String dataString = byteArrayToFormattedString(bytesToProcess, mDataStartOffsets.get(i) + startByte, endIndex, mDataLittleEndianFlags.get(i));
                    dataFields.add(Long.decode(dataString));
                }
            }

            if (mPowerStartOffset != null) {
                int endIndex = mPowerEndOffset + startByte;
                int txPower = 0;
                try {
                    if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) {
                        parseFailed = true;
                        LogHelper.d(TAG, "Cannot parse power field because PDU is too short.  endIndex: " + endIndex + " PDU endIndex: " + pduToParse.getEndIndex());
                    }
                    else {
                        String powerString = byteArrayToFormattedString(bytesToProcess, mPowerStartOffset + startByte, mPowerEndOffset + startByte, false);
                        txPower = Integer.parseInt(powerString)+mDBmCorrection;
                        // make sure it is a signed integer
                        if (txPower > 127) {
                            txPower -= 256;
                        }
                        beacon.mTxPower = txPower;
                    }
                }
                catch (NumberFormatException e1) {
                    // keep default value
                }
                catch (NullPointerException e2) {
                    // keep default value
                }
            }
        }
    }

    if (parseFailed) {
        beacon = null;
    }
    else {
        int beaconTypeCode = 0;
        String beaconTypeString = byteArrayToFormattedString(bytesToProcess, mMatchingBeaconTypeCodeStartOffset+startByte, mMatchingBeaconTypeCodeEndOffset+startByte, false);
        beaconTypeCode = Integer.parseInt(beaconTypeString);
        // TODO: error handling needed on the parse

        int manufacturer = 0;
        String manufacturerString = byteArrayToFormattedString(bytesToProcess, startByte, startByte+1, true);
        manufacturer = Integer.parseInt(manufacturerString);

        String macAddress = null;
        String name = null;
        if (device != null) {
            macAddress = device.getAddress();
            name = device.getName();
        }

        beacon.mIdentifiers = identifiers;
        beacon.mDataFields = dataFields;
        beacon.mRssi = rssi;
        beacon.mBeaconTypeCode = beaconTypeCode;
        if (mServiceUuid != null) {
            beacon.mServiceUuid = (int) mServiceUuid.longValue();
        }
        else {
            beacon.mServiceUuid = -1;
        }

        beacon.mBluetoothAddress = macAddress;
        beacon.mBluetoothName= name;
        beacon.mManufacturer = manufacturer;
        beacon.mParserIdentifier = mIdentifier;
        beacon.mMultiFrameBeacon = extraParsers.size() > 0 || mExtraFrame;
    }
    return beacon;
}

扫描回调:

private ScanCallback getNewLeScanCallback() {
    if (leScanCallback == null) {
        leScanCallback = new ScanCallback() {
            @MainThread
            @Override
            public void onScanResult(int callbackType, ScanResult scanResult) {
                    LogHelper.d(TAG, "got record");
                    List<ParcelUuid> uuids = scanResult.getScanRecord().getServiceUuids();
                    if (uuids != null) {
                        for (ParcelUuid uuid : uuids) {
                            LogHelper.d(TAG, "with service uuid: "+uuid);
                        }
                    }

                    try {
                        LogHelper.d("ScanRecord", "Raw Data: " + scanResult.toString());
                        LogHelper.d("ScanRecord", "Device Data Name: " + scanResult.getDevice().getName() + "Rssi: " + scanResult.getRssi() + "Address: " + scanResult.getDevice().getAddress() + "Service uuid: " + scanResult.getScanRecord().getServiceUuids());
                    }catch (Exception e){
                        LogHelper.d("ScanRecord",e.getMessage());
                        e.printStackTrace();
                    }
                mCycledLeScanCallback.onLeScan(scanResult.getDevice(),
                        scanResult.getRssi(), scanResult.getScanRecord().getBytes());
                if (mBackgroundLScanStartTime > 0) {
                    LogHelper.d(TAG, "got a filtered scan result in the background.");
                }
            }

            @MainThread
            @Override
            public void onBatchScanResults(List<ScanResult> results) {
                LogHelper.d(TAG, "got batch records");
                for (ScanResult scanResult : results) {
                    mCycledLeScanCallback.onLeScan(scanResult.getDevice(),
                            scanResult.getRssi(), scanResult.getScanRecord().getBytes());
                }
                if (mBackgroundLScanStartTime > 0) {
                    LogHelper.d(TAG, "got a filtered batch scan result in the background.");
                }
            }

            @MainThread
            @Override
            public void onScanFailed(int errorCode) {
                Intent intent = new Intent("onScanFailed");
                intent.putExtra("errorCode", errorCode);
                LocalBroadcastManager.getInstance(CycledLeScannerForLollipop.this.mContext).sendBroadcast(intent);
                switch (errorCode) {
                    case SCAN_FAILED_ALREADY_STARTED:
                        LogHelper.e(TAG, "Scan failed: a BLE scan with the same settings is already started by the app");
                        break;
                    case SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
                        LogHelper.e(TAG, "Scan failed: app cannot be registered");
                        break;
                    case SCAN_FAILED_FEATURE_UNSUPPORTED:
                        LogHelper.e(TAG, "Scan failed: power optimized scan feature is not supported");
                        break;
                    case SCAN_FAILED_INTERNAL_ERROR:
                        LogHelper.e(TAG, "Scan failed: internal error");
                        break;
                    default:
                        LogHelper.e(TAG, "Scan failed with unknown error (errorCode=" + errorCode + ")");
                        break;
                }
            }
        };
    }
    return leScanCallback;
}

过滤“您的”信标的一般方法是查看所有信标共有的标识符前缀。然后,您可以通过过滤与此标识符前缀匹配的信标来判断它是否是您的信标。

两种过滤方式:

A) 扫描结果进来后进行软件过滤。

使用这种方法,您可以等到解析信标,然后使用 if 语句查看信标标识符是否与您的前缀匹配。如果不是,请不要处理它。 Android 信标库通过使用区域对象为“您的”信标提供匹配模式,将此作为 built-in 功能。

    // replace uuid with your own
    beaconManager.startRangingBeaconsInRegion(new Region("matchOnlyMyBeacons", Identifier.parse(“2F234454-CF6D-4A0F-ADF2-F4911BA9”)), null, null));

    beaconManager.addRangeNotifier(new RangeNotifier() {
        @Override
        public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
           // only beacons matching the identifiers in the Region are included here
        }
    });

由于您不是将库作为一个整体使用,而是复制其中的一些代码,因此您可能必须自己构建类似的逻辑,如下所示:

  // replace the uuid with yours below
  if (beacon.getID1().equals(Identifier.parse(“2F234454-CF6D-4A0F-ADF2-F4911BA9”)){
    // only process matching beacons here
  }

这是一种非常灵活的简单方法。它适用于您的应用程序仅在前台或后台运行的情况,而通常周围几乎没有不感兴趣的 BLE 设备。

缺点是如果周围有很多不感兴趣的信标,它会燃烧 cpu 和电池。

B) 使用硬件扫描过滤器

Android 6+ API 允许您将类似的匹配功能放入蓝牙芯片本身,因此您获得的所有扫描回调都已经匹配标识符前缀。这使我们对 CPU 和电池的负担减轻了,但也有缺点:

  1. 并非所有设备都支持此功能,但自 2018 年以来制造的大多数设备都支持。

  2. 硬件过滤器是一种有限的资源。如果其他应用程序占用了所有这些,您将无法获得扫描结果。

  3. 过滤器不灵活。如果即使广告前缀的一个字节不匹配(通常是由于不同的制造商代码),您也不会得到扫描结果。

     ScanFilter.Builder builder = new ScanFilter.Builder();
     builder.setServiceUuid(null);
     byte[] filterBytes = new byte[]{
             /* 0215 are the start of iBeacon.  Use beac for AltBeacon */
             (byte) 0x02, (byte) 0x15,
             // These bytes are your 16 byte proximityUUID (ID1)
             (byte) 0x2F, (byte) 0x23, (byte) 0x44, (byte) 0x54, (byte) 0xCF, (byte) 0x6D, (byte) 0x4A, (byte) 0x0F, (byte) 0xAD, (byte) 0xF2, (byte) 0xF4, (byte) 0x91, (byte) 0x1B, (byte) 0xA9, (byte) 0xFF, (byte) 0xA6
     };
    
     byte[] maskBytes = new byte[]{
             /* Make this the same length as your filter bytes, and set every value to 0xff to match all bytes */
             (byte) 0xff, (byte) 0xff,
             (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff
     };
     builder.setManufacturerData((int) 0x004c /* apple for iBeacon, use 0x0118 for AltBeacon */, filterBytes, maskBytes);
     ScanFilter[] scanFilters = new ScanFilter[] { builder.build() };
     scanner.startScan(scanFilters, scanSettings, scanCallback);