Android 13及以上版本蓝牙连接功能崩溃问题排查求助
Android 13及以上版本蓝牙应用崩溃问题排查
一、权限检查函数checkPermissions()的错误分析
用户提供的权限检查代码存在以下关键问题:
1. 版本判断逻辑错误
当前代码用Build.VERSION.SDK_INT > Build.VERSION_CODES.S判断高版本,但Build.VERSION_CODES.S对应API 31(Android 12),会导致Android 12设备也进入该分支。针对Android 13(API 33)及以上,应改用>= Build.VERSION_CODES.TIRAMISU,或至少修正为>= Build.VERSION_CODES.S来覆盖Android 12+,再细分权限处理。
2. 冗余/废弃权限申请
- Android 12+已废弃
BLUETOOTH_ADMIN权限,无需申请; - Android 13+已废弃
WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE,若无需访问媒体文件应直接移除;若需访问,需替换为READ_MEDIA_IMAGES等细分媒体权限。
3. 位置权限的必要性
Android 12+扫描BLE设备时,若仅扫描已配对设备或给BLUETOOTH_SCAN权限添加android:usesPermissionFlags="neverForLocation"属性,无需申请位置权限。若你的扫描不需要位置关联,应移除ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION,避免不必要的权限申请。
修正后的权限检查代码示例
private void checkPermissions() { List<String> permissions = new ArrayList<>(); // 基础相机权限 permissions.add(Manifest.permission.CAMERA); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // Android 13+:蓝牙核心权限 permissions.add(Manifest.permission.BLUETOOTH_SCAN); permissions.add(Manifest.permission.BLUETOOTH_CONNECT); // 如需访问图片,替换为新的媒体权限 // permissions.add(Manifest.permission.READ_MEDIA_IMAGES); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12:蓝牙权限+必要位置权限(若扫描需要位置) permissions.add(Manifest.permission.BLUETOOTH_SCAN); permissions.add(Manifest.permission.BLUETOOTH_CONNECT); permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); } else { // Android 11及以下:旧版蓝牙+位置+存储权限 permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION); permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); permissions.add(Manifest.permission.BLUETOOTH); permissions.add(Manifest.permission.BLUETOOTH_ADMIN); permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE); } List<String> listPermissionsNeeded = new ArrayList<>(); for (String p : permissions) { if (ContextCompat.checkSelfPermission(this, p) != PackageManager.PERMISSION_GRANTED) { listPermissionsNeeded.add(p); } } if (!listPermissionsNeeded.isEmpty()) { ActivityCompat.requestPermissions(this, listPermissionsNeeded.toArray(new String[0]), 10); } else { permissionOK(); } }
二、LeDeviceListAdapter与蓝牙扫描回调的问题分析
1. LeDeviceListAdapter的关键问题
- 未继承Adapter基类:该类没有继承
BaseAdapter或RecyclerView.Adapter,无法作为列表适配器绑定UI组件,直接导致崩溃; - 方法权限与注释错误:
getItem方法为包级私有,外部无法调用;被注释的@Override说明原本应重写Adapter的方法,但因未继承基类,这些方法无法被系统识别; - 线程安全问题:
mLeDevices使用非线程安全的ArrayList,若存在多线程操作集合,会引发ConcurrentModificationException。
修正后的LeDeviceListAdapter示例(继承BaseAdapter)
public class LeDeviceListAdapter extends BaseAdapter { private final ArrayList<BluetoothDevice> mLeDevices; public LeDeviceListAdapter() { mLeDevices = new ArrayList<>(); } public void addDevice(BluetoothDevice device) { if (!mLeDevices.contains(device)) { synchronized (mLeDevices) { mLeDevices.add(device); } notifyDataSetChanged(); } } public BluetoothDevice getDevice(int position) { synchronized (mLeDevices) { return mLeDevices.get(position); } } void clear() { synchronized (mLeDevices) { mLeDevices.clear(); } notifyDataSetChanged(); } @Override public int getCount() { synchronized (mLeDevices) { return mLeDevices.size(); } } @Override public Object getItem(int position) { synchronized (mLeDevices) { return mLeDevices.get(position); } } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { // 根据你的UI需求实现列表项渲染逻辑 return convertView; } }
2. 蓝牙扫描回调mLeScanCallback的问题
- 字符串索引越界:
tmp_str.substring(target_name.length()+3, target_name.length()+3+target_name2.length())未判断字符串长度是否足够,长度不足时会抛出StringIndexOutOfBoundsException,是直接崩溃诱因; - 使用废弃API:
BluetoothAdapter.startLeScan在API 21后已被废弃,Android 13+对旧API兼容性差,应改用BluetoothLeScanner的startScan方法; - 扫描逻辑漏洞:未处理设备名长度刚好等于截取长度的情况,且重复添加设备的判断可优化。
修正后的扫描逻辑示例
private BluetoothLeScanner mBluetoothLeScanner; private ScanCallback mScanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { super.onScanResult(callbackType, result); BluetoothDevice device = result.getDevice(); runOnUiThread(() -> { String deviceName = device.getName(); String targetName = "BCR"; String targetName2 = "BT"; if (deviceName != null) { int requiredLength = targetName.length() + 3 + targetName2.length(); if (deviceName.length() >= requiredLength) { String prefix = deviceName.substring(0, targetName.length()); String suffix = deviceName.substring(targetName.length() + 3, requiredLength); if (targetName.equals(prefix) && targetName2.equals(suffix)) { mLeDeviceListAdapter.addDevice(device); } } } }); } }; private void scanLeDevice(final boolean enable) { if (mBluetoothLeScanner == null) { mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner(); } if (enable) { if (mHandler == null) { mHandler = new Handler(Looper.getMainLooper()); } // 延迟停止扫描 mHandler.postDelayed(() -> { if (mBluetoothLeScanner != null) { mBluetoothLeScanner.stopScan(mScanCallback); } }, SCAN_PERIOD); // 启动扫描 ScanSettings settings = new ScanSettings.Builder().build(); List<ScanFilter> filters = new ArrayList<>(); mBluetoothLeScanner.startScan(filters, settings, mScanCallback); } else { if (mBluetoothLeScanner != null) { mBluetoothLeScanner.stopScan(mScanCallback); } if (mHandler != null) { mHandler.removeCallbacksAndMessages(null); } } }
内容的提问来源于stack exchange,提问作者Aqilah Mokhtarudin
相关产品推荐
相关产品推荐

