Android BLE- 如何在 ScanCallback 中调用 onScanResult 方法?

Android BLE- How is onScanResult method being called in ScanCallback?

这是我第一次在Android项目中做低功耗蓝牙。我正在做的项目基本上是检测所有蓝牙 LE 设备并连接它们以发现它们的服务。

请问有谁知道在ScanCallback中是如何调用onScanResult()、onBatchScanResults()和onScanFailed()方法的?

首先,运行 scanLeDevice() 方法。

BluetoothLeScanner mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
ScanSettings settings = new ScanSettings.Builder()
           .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
           .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
           .build();
List<ScanFilter> filters = new ArrayList<ScanFilter>();

scanLeDevice(true);

在这个方法中,它会启动扫描。所以我假设扫描结果是使用这些回调传递的。

@TargetApi(21)
private void scanLeDevice(final boolean enable) {
    if (enable) {
        //stops scanning after a pre-defined scan period
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                    System.out.println("BLE// mLEScanner.stopScan(mScanCallback) ");
                    mLEScanner.stopScan(mScanCallback);
                }
            }
        }, SCAN_PERIOD);
            System.out.println("BLE// mLEScanner.startScan(filters, settings, mScanCallback)");
            mLEScanner.startScan(filters, settings, mScanCallback);

    } else {
            System.out.println("BLE// mLEScanner.stopScan(mScanCallback)");
            mLEScanner.stopScan(mScanCallback);
    }
}

但是,在ScanCallback中,我不知道它是如何触发onScanResult并使用回调传递扫描结果的。在我的测试中(如下所示),既没有调用 onScanResult() 也没有调用 onBatchScanResults() 和 onScanFailed() 。有人可以向我解释这个概念吗?对我有很大帮助!

 /* Scan result for SDK >= 21 */
 private ScanCallback mScanCallback = new ScanCallback() {

    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        System.out.println("BLE// onScanResult");
        Log.i("callbackType", String.valueOf(callbackType));
        Log.i("result", result.toString());
        BluetoothDevice btDevice = result.getDevice();
        connectToDevice(btDevice);
    }

    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        System.out.println("BLE// onBatchScanResults");
        for (ScanResult sr : results) {
            Log.i("ScanResult - Results", sr.toString());
        }
    }

    @Override
    public void onScanFailed(int errorCode) {
        System.out.println("BLE// onScanFailed");
        Log.e("Scan Failed", "Error Code: " + errorCode);
    }

};

02-17 10:38:38.513 878-895/? D/BluetoothManagerService: Added callback: android.bluetooth.IBluetoothManagerCallback$Stub$Proxy@8334cf4:true
02-17 10:38:38.520 782-782/? D/BluetoothAdapter: STATE_ON
02-17 10:38:38.529 21554-21590/? D/BtGatt.GattService: registerClient() - UUID=835342c6-81eb-4e09-9729-5bbe1c22bc86
02-17 10:38:38.529 21554-21570/? D/BtGatt.GattService: onClientRegistered() - UUID=835342c6-81eb-4e09-9729-5bbe1c22bc86, clientIf=5
02-17 10:38:38.530 782-793/? D/BluetoothLeScanner: onClientRegistered() - status=0 clientIf=5
02-17 10:38:38.530 21554-21599/? D/BtGatt.GattService: start scan with filters

02-17 10:38:38.532 782-782/? I/System.out: BLE// mLEScanner.startScan(filters, settings, mScanCallback)
02-17 10:38:38.532 21554-21573/? D/BtGatt.ScanManager: handling starting scan
02-17 10:38:38.534 21576-21577/? I/WCNSS_FILTER: ibs_msm_serial_clock_vote: vote UART CLK ON using UART driver's ioctl()
02-17 10:38:38.542 21554-21570/? D/BtGatt.GattService: onScanFilterEnableDisabled() - clientIf=5, status=0, action=1
02-17 10:38:38.543 21554-21570/? D/BtGatt.ScanManager: callback done for clientIf - 5 status - 0
02-17 10:38:38.543 21554-21573/? D/BtGatt.ScanManager: configureFilterParamter 500 10000 1 0
02-17 10:38:38.547 21554-21570/? D/BtGatt.GattService: onScanFilterParamsConfigured() - clientIf=5, status=0, action=0, availableSpace=15
02-17 10:38:38.547 21554-21570/? D/BtGatt.ScanManager: callback done for clientIf - 5 status - 0
02-17 10:38:38.548 21554-21573/? D/BtGatt.ScanManager: configureRegularScanParams() - queue=1
02-17 10:38:38.548 487-2827/? I/ACDB-LOADER: ACDB AFE returned = -19
02-17 10:38:38.549 21554-21573/? D/BtGatt.ScanManager: configureRegularScanParams() - ScanSetting Scan mode=0 mLastConfiguredScanSetting=-2147483648
02-17 10:38:38.549 21554-21573/? D/BtGatt.ScanManager: configureRegularScanParams - scanInterval = 8000configureRegularScanParams - scanWindow = 800
02-17 10:38:38.549 21554-21570/? D/BtGatt.GattService: onScanParamSetupCompleted : 0
02-17 10:38:38.568 21554-21574/? W/bt_hci: filter_incoming_event command complete event with no matching command. opcode: 0x0.
02-17 10:38:38.603 21554-21570/? D/bt_btif_gattc: btif_gattc_update_properties BLE device name=Polar HR Sensor len=15 dev_type=2
02-17 10:38:39.571 21576-21585/? I/WCNSS_FILTER: ibs_msm_serial_clock_vote: vote UART CLK OFF using UART driver's ioctl()

02-17 10:38:43.526 782-782/? I/System.out: BLE// mLEScanner.stopScan(mScanCallback) 
02-17 10:38:43.599 21576-21576/? I/WCNSS_FILTER: ibs_msm_serial_clock_vote: vote UART CLK ON using UART driver's ioctl()
02-17 10:38:43.967 21576-21576/? I/WCNSS_FILTER: ibs_msm_serial_clock_vote: vote UART CLK OFF using UART driver's ioctl()

使用 Android Phone 与 API 23

我这里写的代码参考:http://www.truiton.com/2015/04/android-bluetooth-low-energy-ble-example/



[更新代码 V1]- 不工作

这是我所有的代码 我创建了一个虚拟外设,它处于广告模式。虚拟外设是通过名为 LightBlue 的应用程序创建的:https://itunes.apple.com/us/app/lightblue-explorer-bluetooth/id557428110?mt=8 请帮我检查一下我的代码:)

@TargetApi(21)
public class BluetoothLE extends Fragment {

View view;

private BluetoothAdapter mBluetoothAdapter;
private int REQUEST_ENABLE_BT = 1;
private Handler mHandler;
private static final long SCAN_PERIOD = 5000;  // Stops scanning after 5 seconds
private BluetoothLeScanner mLEScanner;
private BluetoothGatt mGatt; //To provide bluetooth communication
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;
private int permissionCheck;

public BluetoothLE(){
    //empty constructor
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment

    view = inflater.inflate(R.layout.fragment_bluetooth, container, false);
    mHandler = new Handler();

    /* check if BLE is supported in this phone */
    if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
        Toast.makeText(getActivity(), "BLE Not Supported", Toast.LENGTH_SHORT).show();
        getActivity().finish();
    }

    /* Enable bluetooth without leaving app */
    final BluetoothManager bluetoothManager =
            (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
    mBluetoothAdapter = bluetoothManager.getAdapter();

    /* Build ScanSetting */
    ScanSettings.Builder scanSetting = new ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
            .setReportDelay(5000);

    settings = scanSetting.build();


    return view;
}

@TargetApi(Build.VERSION_CODES.M)
@Override
public void onResume() {
    super.onResume();

    /* Ensures Bluetooth is available on the device and it is enabled. If not, displays a dialog requesting user permission to enable Bluetooth. */
    if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {  //Unable to obtain a BluetoothAdapter
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); //trigger onActivityResult

    } else {
        if (Build.VERSION.SDK_INT >= 21) {
            mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
            settings = new ScanSettings.Builder()
                    .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
                    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
                    .build();
            filters = new ArrayList<ScanFilter>();
        }

        if(Build.VERSION.SDK_INT >= 23){
            checkLocationPermission();
        }

        scanLeDevice(true);
    }
}

@Override
public void onPause() {
    super.onPause();
    if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
        scanLeDevice(false);
    }
}

@Override
public void onDestroy() {
    if (mGatt == null) {
        return;
    }
    mGatt.close();
    mGatt = null;
    super.onDestroy();
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    System.out.println("BLE// onActivityResult");
    if (requestCode == REQUEST_ENABLE_BT) {
        if (resultCode == Activity.RESULT_CANCELED) {
            //Bluetooth not enabled.
            getActivity().finish();
            return;
        }
    }
    super.onActivityResult(requestCode, resultCode, data);
}

private void scanLeDevice(final boolean enable) {
    if (enable) {
        //stops scanning after a pre-defined scan period
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (Build.VERSION.SDK_INT < 21) {
                    System.out.println("BLE// mBluetoothAdapter.stopLeScan(mLeScanCallback) ");
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                } else {
                    mLEScanner.stopScan(mScanCallback);
                    System.out.println("BLE// mLEScanner.stopScan(mScanCallback) ");
                }
            }
        }, SCAN_PERIOD);

        if (Build.VERSION.SDK_INT < 21) {
            System.out.println("BLE// mBluetoothAdapter.startLeScan(mLeScanCallback)");
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {

            mLEScanner.startScan(mScanCallback);
            //mLEScanner.startScan(filters, settings, mScanCallback);
            System.out.println("BLE// mLEScanner.startScan(mScanCallback) ");
        }
    } else {
        if (Build.VERSION.SDK_INT < 21) {
            System.out.println("BLE// mBluetoothAdapter.stopLeScan(mLeScanCallback)");
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        } else {
            System.out.println("BLE// mLEScanner.stopScan(mScanCallback)");
            mLEScanner.stopScan(mScanCallback);
        }
    }
}

/* Scan result for SDK >= 21 */
private ScanCallback mScanCallback = new ScanCallback() {

    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        System.out.println("BLE// onScanResult");
        super.onScanResult(callbackType, result);

        Log.i("callbackType", String.valueOf(callbackType));
        Log.i("result", result.toString());
        Log.i("Device Name: ", result.getDevice().getName());
        System.out.println("Signal: " + result.getRssi());

        BluetoothDevice btDevice = result.getDevice();
        connectToDevice(btDevice);
    }

    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        System.out.println("BLE// onBatchScanResults");
        for (ScanResult sr : results) {
            Log.i("ScanResult - Results", sr.toString());
        }
    }

    @Override
    public void onScanFailed(int errorCode) {
        System.out.println("BLE// onScanFailed");
        Log.e("Scan Failed", "Error Code: " + errorCode);
    }

};

// scan results are returned here SDK < 21
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
                         byte[] scanRecord) {
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                System.out.println("BLE// DEVICDE FOUND");

                Log.i("onLeScan", device.toString());

                connectToDevice(device);
            }
        });
    }
};

public void connectToDevice(BluetoothDevice device) {
    System.out.println("BLE// connectToDevice()");
    if (mGatt == null) {
        mGatt = device.connectGatt(getActivity(), false, gattCallback); //Connect to a GATT Server
        //scanLeDevice(false);// will stop after first device detection
    }
}

private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        System.out.println("BLE// BluetoothGattCallback");
        Log.i("onConnectionStateChange", "Status: " + status);
        switch (newState) {
            case BluetoothProfile.STATE_CONNECTED:
                Log.i("gattCallback", "STATE_CONNECTED");
                gatt.discoverServices();
                break;
            case BluetoothProfile.STATE_CONNECTING:
                Log.i("gattCallback", "STATE_CONNECTING");
                break;
            case BluetoothProfile.STATE_DISCONNECTED:
                Log.e("gattCallback", "STATE_DISCONNECTED");
                break;
            default:
                Log.e("gattCallback", "STATE_OTHER");
        }
    }

    @Override
    //New services discovered
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        List<BluetoothGattService> services = gatt.getServices();
        Log.i("onServicesDiscovered", services.toString());
        gatt.readCharacteristic(services.get(1).getCharacteristics().get
                (0));
    }

    @Override
    //Result of a characteristic read operation
    public void onCharacteristicRead(BluetoothGatt gatt,
                                     BluetoothGattCharacteristic
                                             characteristic, int status) {
        Log.i("onCharacteristicRead", characteristic.toString());
        gatt.disconnect();
    }
};

public void checkLocationPermission(){
    permissionCheck = ContextCompat.checkSelfPermission(getActivity(), android.Manifest.permission.ACCESS_COARSE_LOCATION);

    switch(permissionCheck){
        case PackageManager.PERMISSION_GRANTED:
            break;

        case PackageManager.PERMISSION_DENIED:

            if(ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), android.Manifest.permission.ACCESS_COARSE_LOCATION)){
                //Show an explanation to user *asynchronouselly* -- don't block
                //this thread waiting for the user's response! After user sees the explanation, try again to request the permission

                Snackbar.make(view, "Location access is required to show Bluetooth devices nearby.",
                        Snackbar.LENGTH_LONG).setAction("Action", null).show();

            }
            else{
                //No explanation needed, we can request the permission
                ActivityCompat.requestPermissions(getActivity(), new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
            }
            break;
    }
}
}

我看到您正在使用 startScan()public void startScan (List<ScanFilter> filters, ScanSettings settings, ScanCallback callback) 方法,但您从未定义任何过滤器。相反,您传递一个空的 ScanFilters ArrayList。所以你永远不会收到任何回调,因为你没有提供任何过滤器匹配的标准。

既然你说要扫描所有 BLE设备,根本不需要使用任何过滤器。相反,请使用更简单的 public void startScan (ScanCallback callback) 方法,该方法不使用任何过滤器或特殊设置。

关于您要求了解它是如何工作的 - 我认为您已经根据您的代码和您对回调应该被触发的期望理解了这个概念。您开始扫描,系统关闭并进行扫描而不会阻止您的代码执行(即它异步执行)。在进行扫描时,它会在适当的时候调用回调对象中的三种方法之一(如 API 文档中所述)。差不多就这些了。

更新: 确保您申请了蓝牙、BLUETOOTH_ADMIN 权限,以及 ACCESS_COARSE_LOCATION ACCESS_FINE_LOCATION 权限。这些是接收来自 startScan() 方法的回调所必需的。不幸的是,如果您不请求这些权限,扫描就会默默地失败。我希望系统在日志中提供警告消息或触发对 onScanFailed() 方法的回调,并使用指示问题的错误代码。

所以..我终于找到了答案。 对于 Android 6.0 或更高版本的 Android 设备(比如我的 phone 是 Nexus 5x),GPSBluetooth 在你的 phone 设置中必须打开,另外在你的清单中你必须添加 BLUETOOTH BLUETOOTH_ADMIN ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION 权限。

现在对我来说一切正常:)