适用于API>=21和API<21的Android蓝牙低功耗代码兼容。

6

我正在开发一款需要与蓝牙低功耗设备连接的应用程序,在我的代码中,我想使用从API 21(Android 5)开始实现的新的BLE扫描和扫描回调函数,但我必须保持与Android 4.3及以上版本的兼容性。

因此,我按照以下方式编写了代码:

        if (Build.VERSION.SDK_INT >= 21) {
            mLEScanner.startScan(filters, settings, mScanCallback);
        } else {
            btAdapter.startLeScan(leScanCallback);
        }

我已经定义了两个回调函数,一个针对Android 5.0及以上的API,另一个针对Android 4.3到4.4的API:

    //API 21
private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
              BluetoothDevice btDevice = result.getDevice();
              connectToDevice(btDevice);
         }
         public void connectToDevice(BluetoothDevice device) {
              if (mGatt == null) {
                   mGatt = device.connectGatt(context, false, btleGattCallback);
                   if (Build.VERSION.SDK_INT < 21) {
                        btAdapter.stopLeScan(leScanCallback);
                   } else {
                        mLEScanner.stopScan(mScanCallback);
                   }
               }
         }
    };


//API 18 to 20
        private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {

        btAdapter.stopLeScan(leScanCallback);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mBluetoothGatt = device.connectGatt(context, false, btleGattCallback);
            }
        });


    }
};

我还加上了注释。
@TargetApi(21)

但是当我在Android 4.x上启动应用程序时,它会立即崩溃,并报告错误,即找不到类ScanCallback(仅用于Android 5及以上版本)。

我该如何解决这个问题?

非常感谢。 Daniele。


你能展示一下完整的堆栈跟踪吗? - Niko Adrianus Yuwono
4个回答

7
阅读了几篇文章后,我做了以下事情。以防万一,这里是关于Android的BluetoothLe文档。 第一步 创建两个方法scanLeDevice21scanLeDevice18。在scanLeDevice21中添加注释@RequiresApi(21),该注释表示:

表明所注释的元素只应在指定的API级别或更高级别上调用。 这类似于旧版的@TargetApi注释,但更清楚地表达了这是对调用者的要求,而不是用于“抑制”超过minSdkVersion的方法内部警告。

第二步 实现每个方法,这是我的代码。
@RequiresApi(21)
private void scanLeDevice21(final boolean enable) {

    ScanCallback mLeScanCallback = new ScanCallback() {

        @Override
        public void onScanResult(int callbackType, ScanResult result) {

            super.onScanResult(callbackType, result);

            BluetoothDevice bluetoothDevice = result.getDevice();

            if (!bluetoothDeviceList.contains(bluetoothDevice)) {
                Log.d("DEVICE", bluetoothDevice.getName() + "[" + bluetoothDevice.getAddress() + "]");
                bluetoothDeviceArrayAdapter.add(bluetoothDevice);
                bluetoothDeviceArrayAdapter.notifyDataSetChanged();
            }
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            super.onBatchScanResults(results);

        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
        }
    };

    final BluetoothLeScanner bluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();

    if (enable) {
        // Stops scanning after a pre-defined scan period.
        mHandler.postDelayed(() -> {
            mScanning = false;
            swipeRefreshLayout.setRefreshing(false);
            bluetoothLeScanner.stopScan(mLeScanCallback);
        }, SCAN_PERIOD);

        mScanning = true;
        bluetoothLeScanner.startScan(mLeScanCallback);
    } else {
        mScanning = false;
        bluetoothLeScanner.stopScan(mLeScanCallback);
    }
}


 /**
 * Scan BLE devices on Android API 18 to 20
 *
 * @param enable Enable scan
 */
private void scanLeDevice18(boolean enable) {

    BluetoothAdapter.LeScanCallback mLeScanCallback =
            new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(final BluetoothDevice bluetoothDevice, int rssi,
                                     byte[] scanRecord) {
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            bluetoothDeviceArrayAdapter.add(bluetoothDevice);
                            bluetoothDeviceArrayAdapter.notifyDataSetChanged();
                        }
                    });
                }
            };
    if (enable) {
        // Stops scanning after a pre-defined scan period.
        mHandler.postDelayed(() -> {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }, SCAN_PERIOD);

        mScanning = true;
        mBluetoothAdapter.startLeScan(mLeScanCallback);
    } else {
        mScanning = false;
        mBluetoothAdapter.stopLeScan(mLeScanCallback);
    }

}

第三步

每次您需要扫描设备时,都会在代码周围询问其版本。例如,我有一个RefreshLayout用于显示设备列表。以下是结果:

  /**
 * Refresh listener
 */
private void refreshScan() {
    if (!hasFineLocationPermissions()) {
        swipeRefreshLayout.setRefreshing(false);


        //Up to marshmallow you need location permissions to scan bluetooth devices, this method is not here since is up to you to implement it and it is out of scope of this question. 
        requestFineLocationPermission();

    } else {
        swipeRefreshLayout.setRefreshing(true);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            scanLeDevice21(true);
        } else {
            scanLeDevice18(true);
        }
    }
}

就是这样了。

忘记去扩展、子类化那些你不需要的类,例如ulusoyca的回答。


1
非常感谢,效果很好且实现起来非常简单。 - winne2
@Pedro 感谢您在解决方案中付出的努力。它对我有用。 - Prashant

4
  1. 创建AbstractBluetoothLe类和IBleScanCallback接口。IBleScanCallback接口是一个标记接口,也就是说,它没有任何方法。如果需要,您也可以向接口添加方法。这些方法将为所有类型的扫描回调执行相同的功能,例如getListOfFoundBleDevices()clearListOfFoundBleDevices()等。
  2. 创建BluetootLeLollipopBluetoothLeJellyBean类,它们都继承自AbstractBluetoothLe类。还要创建BluetootLeMarshmallow类,它继承自BluetootLeLollipop类。 AbstractBluetoothLe类有一个受保护的字段mIBleScanCallback,它是一个IBleScanCallback对象。
  3. 创建BleScanCallbackBase类,它实现了IBleScanCallback接口。
  4. 创建LollipopScanCallback类,它继承自ScanCallback类并实现IBleScanCallback接口。该类有一个受保护的字段scanCallback,它将被实例化为BleScanCallbackBase对象。还要创建MarshmallowScanCallback类,它继承自LollipopScanCallback类。
  5. 创建JellyBeanScanCallback类,它继承自BleScanCallbackBase并实现BluetoothAdapter.LeScanCallback接口。
  6. BleScanCallbackBase中重写方法:onScanCallback(...)
  7. LollipoScanCallback中重写onScanResult(int callbackType, ScanResult result)方法,并在此方法中调用scanCallback对象的onScanCallback(...)方法。
  8. JellyBeanScanCallback中重写onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord)方法,并在此方法中调用onScanCallback(...)方法。
  9. 最后,在BleScanCallbackBase类的onScanCallback(...)方法中执行需要执行的操作,当找到设备时。

简而言之,请了解组合优于继承-我知道这不是您问题的答案,但这是实现您最终想要达到的一种简洁的方式。以下是类图: enter image description here


为了扩展ScanCallback,API的最低版本必须是21,这意味着所有低于Jellybean的设备都将被排除在外。如果设备支持,是否可以更改Android Studio gradle构建以允许API级别21+的功能? - 0x6
@0x6 是的,你说得对。我已经相应更新了我的答案,请再次检查。 - ulusoyca
请回答问题,否则不要回复。 - GraSim

0

你的代码崩溃是因为它创建了匿名内部类。因此,在运行时它找不到sCanCallback类。

尝试以下方法并分享结果。在尝试之前,请确保您注释掉回调(ScanCallback)。

 if (Build.VERSION.SDK_INT >= 21) {
            mLEScanner.startScan(filters, settings, new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
              BluetoothDevice btDevice = result.getDevice();
              connectToDevice(btDevice);
         }
         public void connectToDevice(BluetoothDevice device) {
              if (mGatt == null) {
                   mGatt = device.connectGatt(context, false, btleGattCallback);
                   if (Build.VERSION.SDK_INT < 21) {
                        btAdapter.stopLeScan(leScanCallback);
                   } else {
                        mLEScanner.stopScan(mScanCallback);
                   }
               }
         }
    };
        } else {
            btAdapter.startLeScan(leScanCallback);
        }

0
我想分享一下我的想法,以克服在API 21以下的设备上崩溃的问题。ScanCallback类从API 21开始得到支持。因此,当您编写一个代码来实现ScanCallback时,在较低的API上会导致崩溃。我已经通过以下方式解决了这个问题:
我创建了一个新的抽象类,继承ScanCallback类,如下所示:
@TargetApi(Build.VERSION_CODES.LOLLIPOP)  
public abstract class AppScanCallback extends ScanCallback {}

现在我正在我的BluetoothService类中使用这个类实例,如下所示:

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private AppScanCallback mScanCallback;

并按照以下方式使用此变量:

public void startBleScan() {
    if (isEnabled()) {
        if (Build.VERSION.SDK_INT < 21) {
            _bluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mLEScanner = _bluetoothAdapter.getBluetoothLeScanner();
            settings = new ScanSettings.Builder()
                    .build();
            filters = new ArrayList<>();

            mScanCallback = new AppScanCallback() {
                @Override
                public void onScanResult(int callbackType, ScanResult result) {
                    if (Build.VERSION.SDK_INT >= 21) {
                        BluetoothDevice btDevice = result.getDevice();
                        onLeScanResult(btDevice, result.getScanRecord().getBytes());
                    }
                }

                @Override
                public void onBatchScanResults(List<ScanResult> results) {
                    for (ScanResult sr : results) {
                        Log.i("ScanResult - Results", sr.toString());
                    }
                }
                @Override
                public void onScanFailed(int errorCode) {
                    Log.e("Scan Failed", "Error Code: " + errorCode);
                }
            };
            mLEScanner.startScan(filters, settings, mScanCallback);
        }

    }
}

停止扫描

public void stopBLEScan() {;
        if (Build.VERSION.SDK_INT < 21) {
            _bluetoothAdapter.stopLeScan(mLeScanCallback);
        } else {
            mLEScanner.stopScan(mScanCallback);
        }
}

希望这能对你有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接