Android开发:如何使用MediaStore实现本地歌曲RecyclerView列表展示(附代码求指导)
Hey there! Let's walk through your code and fix the gaps to get your song list working properly. You're on the right track with MediaStore, but there are a few key things missing and some tweaks needed.
1. Missing Permissions to Access Media Files
First off, you need to request the necessary permissions to read audio files from the device.
- For Android 13 (API 33+): Use
READ_MEDIA_AUDIOpermission. - For Android 12 and below: Use
READ_EXTERNAL_STORAGEpermission.
Add these to your AndroidManifest.xml:
<!-- For Android 12 and below --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- For Android 13+ --> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
Then, request the permission at runtime before querying songs. Add this code to your MainActivity:
private fun checkPermissions() { val requiredPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { Manifest.permission.READ_MEDIA_AUDIO } else { Manifest.permission.READ_EXTERNAL_STORAGE } if (ContextCompat.checkSelfPermission(this, requiredPermission) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(requiredPermission), 100) } else { initSongs() } } // Handle permission grant/deny result override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == 100 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { initSongs() } else { Toast.makeText(this, "Permission required to access songs", Toast.LENGTH_SHORT).show() } }
Replace your initSongs() call in onCreate with checkPermissions().
2. Fix Data Type Mismatches
Your Song data class uses incorrect types for media attributes:
MediaStore.Audio.Media.DURATIONis a Long (milliseconds), not Float.MediaStore.Audio.Media.SIZEis a Long (file size in bytes), not String.
Update your Song data class:
data class Song( val uri: Uri, val name: String, val duration: Long, // Changed to Long val size: Long // Changed to Long )
Adjust the query code to fetch correct types:
val duration = cursor.getLong(durationColumn) // Use getLong instead of getFloat val size = cursor.getLong(sizeColum) // Use getLong instead of getString
Later, you can add helper functions to convert duration to mm:ss format and size to KB/MB for display.
3. Update RecyclerView Adapter After Loading Data
Right now, your adapter initializes with an empty list and never gets notified when mSongs updates. Also, avoid GlobalScope (it's not lifecycle-aware) — use lifecycleScope instead:
Modify initSongs():
private fun initSongs() { lifecycleScope.launch { mSongs = querySongs() // Notify adapter of new data mSongAdapter.updateSongs(mSongs) Log.i("Canciones:", mSongs.toString()) // Log here instead of onStop } }
Add an update method to your SongAdapter:
class SongAdapter(private var songs: List<Song>) : RecyclerView.Adapter<SongAdapter.SongViewHolder>() { // Implement your ViewHolder and bind logic here... fun updateSongs(newSongs: List<Song>) { songs = newSongs notifyDataSetChanged() // Use DiffUtil for more efficient updates later } }
4. Remove Unnecessary Android Version Check
Your query logic is wrapped in an Android O+ check, but MediaStore queries work the same on older versions. Remove the check to support all devices:
private suspend fun querySongs(): List<Song> { val songs = mutableListOf<Song>() withContext(Dispatchers.IO) { val projection = arrayOf( MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.SIZE ) // Optional: Filter to only music files (exclude ringtones/notifications) val selection = "${MediaStore.Audio.Media.IS_MUSIC} != 0" application.contentResolver.query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null, "${MediaStore.Audio.Media.DISPLAY_NAME} ASC" // Optional: Sort by name )?.use { cursor -> val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID) val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME) val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION) val sizeColum = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE) while (cursor.moveToNext()) { val id = cursor.getLong(idColumn) val name = cursor.getString(nameColumn) val duration = cursor.getLong(durationColumn) val size = cursor.getLong(sizeColum) val contentUri = ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id ) val song = Song(contentUri, name, duration, size) songs += song } } } return songs }
Final Tips
- Ensure your
SongAdapterproperly binds song data (like formatted duration/size) to your list item layout. - For better performance, replace
notifyDataSetChanged()withDiffUtilonce your app is stable.
With these changes, your RecyclerView should correctly display all music files on the device!
内容的提问来源于stack exchange,提问作者CuadCode

