You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 22:19:50