Flutter音乐播放器加载专辑封面报错:无法获取文件长度求助
我正在开发一款简易音乐播放器应用,通过自定义Java方法获取用户设备上的所有歌曲:
private static List<Map<String, String>> getAllSongs(Context context) { List<Map<String, String>> songs = new ArrayList<>(); // 配置查询参数获取音频文件 String[] projection = new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ALBUM_ID }; String selection = MediaStore.Audio.Media.IS_MUSIC + " != 0"; String sortOrder = MediaStore.Audio.Media.TITLE + " ASC"; // 执行查询 Cursor cursor = context.getContentResolver().query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null, sortOrder ); // 从查询结果提取元数据 if (cursor != null && cursor.moveToFirst()) { int idColumnIndex = cursor.getColumnIndex(MediaStore.Audio.Media._ID); int titleColumnIndex = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE); int artistColumnIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST); int albumColumnIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM); int durationColumnIndex = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION); int pathColumnIndex = cursor.getColumnIndex(MediaStore.Audio.Media.DATA); int albumIdColumnIndex = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID); do { Map<String, String> song = new HashMap<>(); song.put("id", cursor.getString(idColumnIndex)); song.put("title", cursor.getString(titleColumnIndex)); song.put("artist", cursor.getString(artistColumnIndex)); song.put("album", cursor.getString(albumColumnIndex)); song.put("duration", cursor.getString(durationColumnIndex)); song.put("path", cursor.getString(pathColumnIndex)); long albumId = cursor.getLong(albumIdColumnIndex); Uri albumArtUri = Uri.withAppendedPath(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, Long.toString(albumId)); song.put("albumArt", albumArtUri.toString()); songs.add(song); } while (cursor.moveToNext()); cursor.close(); } return songs; }
随后通过Flutter处理数据:
class Song { String id; String title; String artist; String album; String path; String albumArt; Song( {required this.id, required this.title, required this.artist, required this.album, required this.path, required this.albumArt}); } class MusicService { static const MethodChannel _channel = const MethodChannel('com.mybea.ah'); static Future<List<Song>> getAllSongs() async { List<Song> songs = []; try { final List<dynamic> result = await _channel.invokeMethod('getAllSongs'); if (result.isNotEmpty) { for (int i = 0; i < result.length; i++) { songs.add(Song( id: result[i]['id'], title: result[i]['title'], artist: result[i]['artist'], album: result[i]['album'], path: result[i]['path'], albumArt: result[i]['albumArt'], )); } } } on PlatformException catch (e) { print("Failed to get all songs: '${e.message}'."); } return songs; } }
成功获取歌曲信息后,加载专辑封面时出现以下错误:
════════ Exception caught by image resource service ════════════════════════════
Cannot retrieve length of file, path = 'content://media/external/audio/albums/89' (OS Error: No such file or directory, errno = 2)
相关Flutter调用代码如下:
Future<void> _loadSongs() async { if (await Permission.storage.request().isGranted) { List<Song> songs = await MusicService.getAllSongs(); setState(() { _songs = songs; }); } else { return; } } // 界面渲染部分 _songs.length == 0 ? Center( child: Text("No Songs Found"), ) : ListView.builder( itemCount: _songs.length, itemBuilder: (context, index) { Song song = _songs[index]; return ListTile( onTap: () { debugPrint(song.path); }, leading: CircleAvatar( backgroundImage: FileImage(File(song.albumArt)), ), title: Text(song.title), subtitle: Text(song.artist), trailing: Icon(Icons.play_arrow), ); }, ),
我尝试使用Image.file()和Image.network()加载封面,均出现相同错误,请求协助解决该问题。
解决方案
问题根源
你传递的albumArt是Content URI(content://media/external/audio/albums/89),但FileImage/Image.file仅支持本地文件路径,无法解析Content URI;Image.network用于加载网络URL,同样不适用。
解决步骤
1. 修改Java层,直接获取专辑封面的本地文件路径
当前Java代码生成的是专辑的URI,而非封面的实际文件路径。调整代码查询专辑封面的本地路径:
// 替换原有的albumArtUri生成逻辑 String albumArtPath = ""; Uri albumUri = ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, albumId); String[] albumProjection = {MediaStore.Audio.Albums.ALBUM_ART}; Cursor albumCursor = context.getContentResolver().query(albumUri, albumProjection, null, null, null); if (albumCursor != null && albumCursor.moveToFirst()) { int albumArtColumn = albumCursor.getColumnIndex(MediaStore.Audio.Albums.ALBUM_ART); albumArtPath = albumCursor.getString(albumArtColumn); albumCursor.close(); } song.put("albumArt", albumArtPath);
2. Flutter层适配加载逻辑
使用获取到的本地文件路径直接加载,同时添加兜底默认图:
leading: CircleAvatar( backgroundImage: song.albumArt.isNotEmpty ? FileImage(File(song.albumArt)) : AssetImage("assets/default_album_art.png"), ),
3. 若坚持使用Content URI加载(无需修改Java层)
通过MethodChannel在原生层读取封面字节流,传递给Flutter后用MemoryImage加载:
- 原生Java新增方法:
public static byte[] getAlbumArt(Context context, long albumId) { Uri albumUri = ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, albumId); String[] projection = {MediaStore.Audio.Albums.ALBUM_ART}; Cursor cursor = context.getContentResolver().query(albumUri, projection, null, null, null); if (cursor != null && cursor.moveToFirst()) { String artPath = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Albums.ALBUM_ART)); cursor.close(); if (artPath != null) { try { return Files.readAllBytes(Paths.get(artPath)); } catch (IOException e) { e.printStackTrace(); } } } return null; }
- Flutter层调用并渲染:
// 新增MethodChannel调用方法 Future<Uint8List?> getAlbumArt(String albumId) async { try { return await _channel.invokeMethod('getAlbumArt', {"albumId": albumId}); } catch (e) { return null; } } // 列表项中使用FutureBuilder加载 leading: FutureBuilder<Uint8List?>( future: getAlbumArt(song.id), builder: (context, snapshot) { if (snapshot.hasData && snapshot.data != null) { return CircleAvatar( backgroundImage: MemoryImage(snapshot.data!), ); } else { return CircleAvatar( backgroundImage: AssetImage("assets/default_art.png"), ); } }, ),
4. 补充权限配置
确保AndroidManifest.xml中添加必要权限:
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <!-- Android 12及以下兼容 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
内容的提问来源于stack exchange,提问作者Archit Dandavate

