Android Studio中RecyclerView显示空音频条目 仅部分设备出现该问题
问题原因
- 核心原因是音频查询逻辑缺少必要过滤条件:查询
MediaStore时没有筛选有效音乐文件,系统内置的提示音、短音效等非音乐类音频会被查询到,这类文件大多没有标题、无法正常播放,就是你看到的顶部仅显示图标的空列表项,因为不同设备内置的系统音频数量不同,所以仅在部分设备复现。 Cursor遍历逻辑错误:调用cursor.moveToFirst()确认有数据后,直接使用while(cursor.moveToNext())遍历,会丢失查询结果的第一条数据。- 权限适配不兼容:Android 13(API 33)及以上版本废弃了
READ_EXTERNAL_STORAGE权限,需要申请READ_MEDIA_AUDIO权限,部分高版本设备使用旧权限查询MediaStore会返回无效的空数据。 - 排序规则写法不规范:音频排序错误使用了
MediaStore.Video.Media.TITLE,虽然多数设备上常量值相同不影响,但部分定制ROM可能返回异常值。
修复方案
1. 修复音频查询逻辑(修改Songs.java的getSongs方法)
@RequiresApi(api = Build.VERSION_CODES.Q) public static List<Songs> getSongs(Context context) { List<Songs> songsList = new ArrayList<Songs>(); ContentResolver contentResolver = context.getContentResolver(); Uri externalContentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; String[] projection = new String[]{ MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, }; // 新增过滤条件:仅查询有效音乐文件、文件大小大于100KB,排除短音效和空文件 String selection = MediaStore.Audio.Media.IS_MUSIC + " = ? AND " + MediaStore.Audio.Media.SIZE + " > ?"; String[] selectionArgs = new String[]{"1", "100000"}; // 修复排序规则为音频类的TITLE String sortOrder = MediaStore.Audio.Media.TITLE + " ASC"; Cursor cursor = contentResolver.query( externalContentUri, projection, selection, selectionArgs, sortOrder ); if(!cursor.moveToFirst()) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setMessage(R.string.dialog_message).setTitle(R.string.dialog_title); builder.setCancelable(false); builder.setPositiveButton("OK", (dialog, which) -> android.os.Process.killProcess(android.os.Process.myPid())); AlertDialog alert = builder.create(); alert.show(); } else { int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID); int nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE); // 改用do-while遍历,避免丢失第一条数据 do { long id = cursor.getLong(idColumn); String song_name = cursor.getString(nameColumn); // 额外过滤标题为空的无效数据 if (song_name == null || song_name.trim().isEmpty()) { continue; } Uri contentUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id); songsList.add(new Songs(contentUri, song_name)); } while(cursor.moveToNext()); } // 用完关闭Cursor,避免内存泄漏 if (cursor != null && !cursor.isClosed()) { cursor.close(); } return songsList; }
2. 适配高版本权限
首先修改AndroidManifest.xml的权限声明:
<!-- 兼容Android 12及以下版本 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- Android 13及以上版本音频权限 --> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
然后修改MainActivity.java的动态权限申请逻辑,根据系统版本申请对应权限,同时提前初始化RecyclerView避免多次初始化:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 提前初始化RecyclerView和Adapter RecyclerView recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); songs = new ArrayList<>(); SongsAdapter songsAdapter = new SongsAdapter(songs, this, this); recyclerView.setAdapter(songsAdapter); // 根据系统版本申请对应权限 String requestPermission = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ? Manifest.permission.READ_MEDIA_AUDIO : Manifest.permission.READ_EXTERNAL_STORAGE; Dexter.withContext(this) .withPermission(requestPermission) .withListener(new PermissionListener() { @RequiresApi(api = Build.VERSION_CODES.Q) @Override public void onPermissionGranted(PermissionGrantedResponse permissionGrantedResponse) { // 权限通过后更新数据集,通知Adapter刷新 List<Songs> newSongs = Songs.getSongs(getApplicationContext()); songs.clear(); songs.addAll(newSongs); songsAdapter.notifyDataSetChanged(); } @Override public void onPermissionDenied(PermissionDeniedResponse permissionDeniedResponse) { android.os.Process.killProcess(android.os.Process.myPid()); } @Override public void onPermissionRationaleShouldBeShown(PermissionRequest permissionRequest, PermissionToken permissionToken) { permissionToken.continuePermissionRequest(); } }) .check(); }
内容的提问来源于stack exchange,提问作者Kumar Sravan
相关产品推荐
相关产品推荐

