android BLE 扫描后收到错误结果

Recieving incorrect results after BLE scan in android

我正在使用以下代码扫描附近的所有 BLE 设备

这是代码。

    package com.btexample;

    import android.Manifest;
    import android.app.Activity;
    import android.bluetooth.BluetoothAdapter;
    import android.bluetooth.BluetoothManager;
    import android.bluetooth.le.BluetoothLeScanner;
    import android.bluetooth.le.ScanCallback;
    import android.bluetooth.le.ScanFilter;
    import android.bluetooth.le.ScanResult;
    import android.bluetooth.le.ScanSettings;
    import android.content.Context;
    import android.content.Intent;
    import android.content.pm.PackageManager;
    import android.os.Handler;
    import androidx.annotation.NonNull;
    import androidx.core.app.ActivityCompat;
    import androidx.core.content.ContextCompat;
    import com.facebook.react.bridge.ActivityEventListener;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContextBaseJavaModule;
    import com.facebook.react.bridge.ReactMethod;
    import com.facebook.react.bridge.Callback;
    import com.facebook.react.bridge.WritableMap;
    import com.facebook.react.bridge.WritableNativeMap;
    import java.util.ArrayList;
    import java.util.List;
    
    
    public class MyBluetooth extends ReactContextBaseJavaModule implements ActivityEventListener {


    private ReactApplicationContext context;
    private static final int PERMISSION_REQUEST_CODE = 200;
    private static final int REQUEST_ENABLE_BT = 10112;
    private String[] permissions = new String[]{Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN};
    private BluetoothManager bluetoothManager;
    private BluetoothAdapter bluetoothAdapter;
    private BluetoothLeScanner bluetoothLeScanner;
    private Callback scanSuccessCallBack,scanFailedCallBack;

    public MyBluetooth(ReactApplicationContext reactContext) {
        super(reactContext);
        context = reactContext;
        bluetoothManager =
                (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
        bluetoothAdapter = bluetoothManager.getAdapter();
        bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
    }

    @NonNull
    @Override
    public String getName() {
        return "MyBluetooth";
    }




    @ReactMethod
    public void turnOnBluetooth() {
        if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            getCurrentActivity().startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }
    }

    private WritableMap convertJsonToMap(String name) {
        WritableMap map = new WritableNativeMap();
        map.putString("device_name", name);
        return map;
    }

    @ReactMethod
    private void requestGPSPermissions() {
        boolean permissionCheck =
                ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
                        != PackageManager.PERMISSION_GRANTED
                        &&
                        ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
                                != PackageManager.PERMISSION_GRANTED;


        if (permissionCheck) {

            ActivityCompat.requestPermissions(getCurrentActivity(), new String[]{
                            Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION
                    },
                    PERMISSION_REQUEST_CODE);

        }
    }


    private boolean mScanning = false,isInvoked;
    private Handler handler;

    @ReactMethod
    public void scanLeDevice(int scanSeconds,Callback scanSuccessCallBack,Callback scanFailedCallBack) {
        if (!mScanning) {
            // Stops scanning after a pre-defined scan period.
            this.scanSuccessCallBack  = scanSuccessCallBack;
            this.scanFailedCallBack  = scanFailedCallBack;
            isInvoked = false;
            handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if(mScanning) {
                        bluetoothLeScanner.stopScan(leScanCallback);
                        mScanning = false;
                        if(!isInvoked){
                            scanFailedCallBack.invoke("No results found");
                        }
                    }
                }
            }, scanSeconds*1000);

            mScanning = true;
            List<ScanFilter> filters = new ArrayList<ScanFilter>();

            ScanSettings settings = new ScanSettings.Builder()
                    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                    .setReportDelay(1000)
                    .build();



            bluetoothLeScanner.startScan(filters, settings, leScanCallback);
        } else {
            mScanning = false;
            bluetoothLeScanner.stopScan(leScanCallback);
            scanFailedCallBack.invoke("Already scanning");
        }
    }


    private ScanCallback leScanCallback = new ScanCallback() {


        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            if(results.size() > 0 && mScanning) {
                mScanning = false;
                if(!isInvoked){
                    scanSuccessCallBack.invoke("results found");
                    isInvoked = true;
                    bluetoothLeScanner.stopScan(leScanCallback);
                    handler.removeCallbacksAndMessages(null);
                }
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            scanFailedCallBack.invoke("Scan failed");
        }
    };


    @Override
    public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {

    }

    @Override
    public void onNewIntent(Intent intent) {

    }
}

现在,

  1. 到目前为止我没有任何外围设备所以我正在使用一个应用程序作为模拟器 link: https://play.google.com/store/apps/details?id=com.ble.peripheral.sim&hl=en_IN&gl=US

  2. 现在正在另一台设备上使用另一应用程序扫描 https://play.google.com/store/apps/details?id=com.macdom.ble.blescanner&hl=en_IN&gl=US 我收到了正确的结果

  3. 现在再次使用我的应用程序扫描并接收结果。

现在主要问题是

  1. 当我关闭模拟器应用程序并卸载它时,我仍然在我的应用程序中得到它,但在那个扫描仪应用程序中却没有。

  2. 此外,我的应用程序正在接收 deviceName=null 的随机设备(还有很多东西是空的)并且这些随机设备不会出现在该扫描仪应用程序中。

  3. 当我从 starScan() 中删除过滤器和设置时;

    例如

    bluetoothLeScanner.startScan(过滤器、设置、leScanCallback);

从未收到 onBatchScanResults() 的回调。

  1. 从设备中删除模拟器应用程序,在第三台设备上全新安装我的应用程序仍然得到 1 或 2 个设备名称为空的随机结果

注意:我正在 android studio

的调试器中检查结果

请帮助我提前谢谢

1-2。一旦您的扫描过滤器为空,您将获得所有扫描结果,因此可能在此应用程序中有不同的扫描过滤器。因此,您可以获得不同的扫描结果。

  1. 我认为您在回调方法中开始接收扫描结果 onScanResult,这在您的代码中没有实现。

  2. 可能是您周围的一些 BLE 设备 - 如笔记本电脑、鼠标、电视等

我不确定什么对我有用,但这些是我已经改变的东西。

  1. 在两个清单中又添加了一项权限,并在 运行 时询问 ACCESS_BACKGROUND_LOCATION

  2. 停止使用android.bluetooth.le,现在我正在使用这个库来这样做 https://github.com/NordicSemiconductor/Android-Scanner-Compat-Library 两者的代码相同,我们只需要从这个包中导入 类。

  3. 在问题中我忘了提到我没有得到 onScanResult 的回调,即使在这样做之后我也无法获得扫描结果

  4. 所以我只是从 startScan 中删除了扫描过滤器和扫描设置,然后使用 onScanResult 获取设备。

    这是最终代码

import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.Handler;
import android.provider.Settings;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;

import java.util.ArrayList;
import java.util.List;

import no.nordicsemi.android.support.v18.scanner.BluetoothLeScannerCompat;
import no.nordicsemi.android.support.v18.scanner.ScanCallback;
import no.nordicsemi.android.support.v18.scanner.ScanFilter;
import no.nordicsemi.android.support.v18.scanner.ScanResult;
import no.nordicsemi.android.support.v18.scanner.ScanSettings;


public class BleModule extends ReactContextBaseJavaModule {


    private ReactApplicationContext context;
    private static final int PERMISSION_REQUEST_CODE = 200;
    private static final int REQUEST_ENABLE_BT = 10112;
    private BluetoothManager bluetoothManager;
    private BluetoothAdapter bluetoothAdapter;
    private BluetoothLeScannerCompat scanner;
    private Callback  scanFailedCallBack;
    private LocationManager lm;

    /**
     * constructor to initialize other important instances with the received context
     *
     * @param reactContext
     */
    public BleModule(ReactApplicationContext reactContext) {
        super(reactContext);
        context = reactContext;
        bluetoothManager =
                (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
        bluetoothAdapter = bluetoothManager.getAdapter();
        scanner = BluetoothLeScannerCompat.getScanner();
        lm = (LocationManager) context.getSystemService(context.LOCATION_SERVICE);

    }

    /**
     * overriding the method to return a name of module as string to be used inside react native codes to use this module.
     * this name should be same in both android and iOS
     *
     * @return
     */
    @NonNull
    @Override
    public String getName() {
        return "BleModule";
    }


    /**
     * this method is to check if the GPS is enabled and
     * invoking the react native callback with true or false
     *
     * @param callback
     */
    @ReactMethod
    public void checkGPS(Callback callback) {
        if (!lm.isProviderEnabled(LocationManager.GPS_PROVIDER))
            callback.invoke(false);

        else
            callback.invoke(true);
    }

    /**
     * this method is to launch GPS settings to enable
     */
    @ReactMethod
    public void enableGPS() {
        Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
        getCurrentActivity().startActivity(intent);
    }


    /**
     * this method is launch the bluetooth enable dialog.
     */
    @ReactMethod
    public void turnOnBluetooth() {
        if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            getCurrentActivity().startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }
    }


    /**
     * this method is to ask the Location permission from the user
     */
    @ReactMethod
    private void requestGPSPermissions() {
        boolean permissionCheck =
                ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
                        != PackageManager.PERMISSION_GRANTED
                        &&
                        ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
                                != PackageManager.PERMISSION_GRANTED
                        &&
                        ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
                                != PackageManager.PERMISSION_GRANTED;


        if (permissionCheck) {

            ActivityCompat.requestPermissions(getCurrentActivity(), new String[]{
                            Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION
                    },
                    PERMISSION_REQUEST_CODE);

        }
    }


    private boolean mScanning = false, isInvoked;
    private Handler handler;
    private WritableArray reactResults;
    private ArrayList<String> duplicateChecker = new ArrayList<>();

    /**
     * this method is to scan the nearby ble devices
     *
     * @param scanSeconds         to run the scan till.
     * @param scanSuccessCallBack to be invoked with the result of the detected devices.
     * @param scanFailedCallBack  to be invoked when any failure occurs with the reason as a string.
     */
    @ReactMethod
    public void scanLeDevice(int scanSeconds, Callback scanSuccessCallBack, Callback scanFailedCallBack) {
        if (!mScanning) {
            reactResults = new WritableNativeArray();
            duplicateChecker.clear();
            // Stops scanning after a pre-defined scan period.
            this.scanFailedCallBack = scanFailedCallBack;
            isInvoked = false;
            handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (mScanning) {
                        scanner.stopScan(leScanCallback);
                        mScanning = false;
                        if (reactResults.size() > 0 )
                            scanSuccessCallBack.invoke(reactResults);
                        else scanFailedCallBack.invoke("No results found");
                    }
                }
            }, scanSeconds * 1000);

            mScanning = true;

            scanner.startScan(leScanCallback);
        } else {
            mScanning = false;
            scanner.stopScan(leScanCallback);
            scanFailedCallBack.invoke("Already scanning");
        }
    }


    /**
     * this ScanCallback instance overriding two methods
     * <p>
     * this object is passed in startScan method inside the scanLeDevice method.
     */
    private ScanCallback leScanCallback = new ScanCallback() {

        @Override
        public void onScanResult(int callbackType, @NonNull ScanResult device) {
            String address = device.getDevice().getAddress();
            if(duplicateChecker.contains(address))
                return;
            else duplicateChecker.add(address);
            WritableMap map = new WritableNativeMap();
            if (device.getDevice() != null)
                map.putString("device_name", device.getDevice().getName());
            else map.putString("device_name", device.getScanRecord().getDeviceName());
            map.putString("device_address", address);

            reactResults.pushMap(map);
        }


        /**
         * this will be triggered if the scan failed. then trigger react native's scanFailedCallBack.
         * @param errorCode
         */
        @Override
        public void onScanFailed(int errorCode) {
            scanFailedCallBack.invoke("Scan failed");
        }
    };


}