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

Android 10设备contentResolver.query调用报错原因排查

问题根因

崩溃是两个问题叠加导致的,你测试的设备(三星Note9、官方模拟器)都做了兼容宽松处理,所以无法复现:

  1. Android 10 分区存储限制拦截查询
    Android 10是首个默认启用分区存储的版本,当应用targetSdkVersion >= 29且未配置 legacy 存储标志时,AOSP原生逻辑会禁止通过MediaStore.Audio.Media.DATA(文件真实路径字段)做查询筛选,直接抛出异常。你测试的三星Galaxy Note9在Android10版本对MediaStore做了厂商定制,放宽了DATA字段的访问限制,官方模拟器也未严格执行该拦截逻辑,而部分严格遵循原生规范的小众Android10机型会直接触发崩溃。
    错误日志截图如下:
    错误日志
  2. 查询参数不匹配的边界逻辑bug
    现有代码的selection语句拼接和参数数组赋值存在边界漏洞:
    • 初始化时默认给selectionPlaceHolders拼了1个DATA like ?占位符,但当carpetasMap.size() == 0且notClickedOnce == false时,carpetasArray始终为null,占位符数量(1个)和传入的查询参数(null)完全不匹配,部分机型的MediaProvider会直接抛出参数非法异常,不会返回null cursor走你写的错误处理分支。
    • 当rutas.length > 2时,你只给carpetasArray的0、1索引赋值,剩余索引位置为null,同样会触发参数异常。
修复方案

按优先级依次处理即可:

  • 补全分区存储兼容配置
    若当前应用targetSdkVersion <= 30,在AndroidManifest.xml的<application>标签下添加属性:
    android:requestLegacyExternalStorage="true"
    
    可临时绕过Android10的分区存储限制,避免DATA字段查询被系统拦截。
  • 修复查询参数拼接逻辑
    重构selection拼接流程,不要提前初始化占位符,改为遍历所有有效筛选路径时动态拼接,保证占位符数量和selectionArgs数组长度完全一致;补全carpetasMap.size() == 0 && notClickedOnce == false的分支逻辑,该场景下直接return终止查询,不要执行query调用。
    参考修正后的核心代码:
    // 权限判断逻辑保持不变
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
            ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        return;
    }
    
    List<String> queryArgs = new ArrayList<>();
    StringBuilder selectionBuilder = new StringBuilder();
    Map<String, ?> carpetasMap = carpetasPreferences.getAll();
    
    // 拼接用户配置的文件夹筛选条件
    for (Map.Entry<String, ?> entry : carpetasMap.entrySet()) {
        if (selectionBuilder.length() > 0) {
            selectionBuilder.append(" OR ");
        }
        // Android10+优先用RELATIVE_PATH,低版本用DATA
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            selectionBuilder.append(MediaStore.Audio.Media.RELATIVE_PATH).append(" like ?");
        } else {
            selectionBuilder.append(MediaStore.Audio.Media.DATA).append(" like ?");
        }
        queryArgs.add("%" + entry.getValue().toString() + "/%");
    }
    
    // 无用户配置时的默认逻辑
    if (carpetasMap.isEmpty() && notClickedOnce) {
        if (selectionBuilder.length() > 0) {
            selectionBuilder.append(" OR ");
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            selectionBuilder.append(MediaStore.Audio.Media.RELATIVE_PATH).append(" like ?");
            queryArgs.add("%Music/%");
            // 多路径场景依次追加即可,保证每加一个占位符就加一个对应参数
            for (int i = 1; i < rutas.length; i++) {
                selectionBuilder.append(" OR ").append(MediaStore.Audio.Media.RELATIVE_PATH).append(" like ?");
                queryArgs.add("%" + rutas[i] + "%");
            }
        } else {
            selectionBuilder.append(MediaStore.Audio.Media.DATA).append(" like ?");
            queryArgs.add("%" + Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music/%");
            for (int i = 1; i < rutas.length; i++) {
                selectionBuilder.append(" OR ").append(MediaStore.Audio.Media.DATA).append(" like ?");
                queryArgs.add("%" + rutas[i] + "%");
            }
        }
    }
    
    // 无任何有效筛选条件直接返回
    if (selectionBuilder.length() == 0) {
        return;
    }
    
    ContentResolver contentResolver = getContentResolver();
    Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
    Cursor cursor = null;
    try {
        cursor = contentResolver.query(uri, null, selectionBuilder.toString(), queryArgs.toArray(new String[0]), null);
    } catch (SecurityException | IllegalArgumentException e) {
        // 捕获系统层面的查询异常,避免崩溃
        e.printStackTrace();
    }
    
    if (cursor == null) {
        // 查询失败处理
    } else if (!cursor.moveToFirst()) {
        // 无媒体文件处理
        cursor.close();
    } else {
        int idColumn = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
        do {
            long thisId = cursor.getLong(idColumn);
            listaCanciones.add(thisId);
        } while (cursor.moveToNext());
        cursor.close();
        mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
    }
    
  • 加查询异常兜底
    给contentResolver.query调用加try-catch,捕获SecurityException、IllegalArgumentException两类系统可能抛出的异常,避免不同厂商ROM的定制逻辑导致崩溃。
  • 长期适配建议
    后续升级targetSdk到30+时,完全移除对DATA字段的依赖,Android10及以上版本统一使用RELATIVE_PATH、BUCKET_ID等MediaStore公开字段做文件夹筛选,不要依赖文件系统真实路径做查询。

内容的提问来源于stack exchange,提问作者Mike087

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 15:31:15