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

Flutter音乐播放器加载专辑封面报错:无法获取文件长度求助

问题: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 08:56:08