Avalonia跨平台应用Android端content:// URI转文件路径问题
在Avalonia Android端将content:// URI转换为真实文件路径用于M3U播放列表
你的思路是对的——要生成VLC能识别的M3U,确实需要把Avalonia获取到的content://格式URI转成Android的真实文件路径(如/storage/emulated/0/...)。由于Avalonia的跨平台API抽象了底层细节,直接用IStorageFile拿不到真实路径,必须调用Android原生API来实现转换,以下是具体方案:
核心实现逻辑(Android平台专属)
通过Android的ContentResolver查询媒体库的DATA字段,获取文件的真实路径。需要注意权限适配和不同Android版本的差异:
1. 添加权限声明
在Android项目的AndroidManifest.xml中添加必要权限:
<!-- Android 12及以下 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Android 13+ 针对音频文件的权限 --> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
Android 6.0+需要动态申请这些权限,代码中要先检查权限是否已授予,未授予则发起请求。
2. 编写转换代码
使用条件编译确保代码只在Android平台运行,避免跨平台冲突:
#if ANDROID using Android.App; using Android.Content; using Android.Net; using Android.Provider; using Android.Content.PM; using Java.IO; #endif // 从文件选择器获取IStorageFile实例后执行以下逻辑 var selectedFile = files[0]; string realFilePath = null; #if ANDROID var androidUri = Uri.Parse(selectedFile.Uri.ToString()); var context = Application.Context; // 根据URI的Authority判断文件类型,选择对应的DATA字段 string dataColumn = null; var authority = androidUri.Authority; if (authority == MediaStore.Audio.Media.Authority) { dataColumn = MediaStore.Audio.Media.Data; } else if (authority == MediaStore.Downloads.Authority) { dataColumn = MediaStore.Downloads.Data; } else if (authority == MediaStore.Files.FileColumns.Authority) { dataColumn = MediaStore.Files.FileColumns.Data; } // 查询真实路径 if (!string.IsNullOrEmpty(dataColumn)) { using (var cursor = context.ContentResolver.Query(androidUri, new string[] { dataColumn }, null, null, null)) { if (cursor != null && cursor.MoveToFirst()) { var columnIndex = cursor.GetColumnIndex(dataColumn); if (columnIndex != -1) { realFilePath = cursor.GetString(columnIndex); } cursor.Close(); } } } // 备选方案:如果查询不到路径,尝试复制文件到应用可访问的公共目录 // 注意:Android 10+需要MANAGE_EXTERNAL_STORAGE权限,该权限审核严格,谨慎使用 if (string.IsNullOrEmpty(realFilePath)) { var publicMusicDir = Environment.GetExternalStoragePublicDirectory(Environment.DirectoryMusic); if (publicMusicDir != null && publicMusicDir.Exists()) { var destFile = new Java.IO.File(publicMusicDir, selectedFile.Name); using (var inputStream = context.ContentResolver.OpenInputStream(androidUri)) using (var outputStream = new FileStream(destFile.AbsolutePath, FileMode.Create)) { await inputStream.CopyToAsync(outputStream); } realFilePath = destFile.AbsolutePath; } } #else // 桌面平台直接获取文件路径 realFilePath = selectedFile.Path; #endif // 将真实路径写入M3U文件 if (!string.IsNullOrEmpty(realFilePath)) { var m3uContent = $"#EXTM3U\n#EXTINF:-1,{selectedFile.Name}\n{realFilePath}"; await File.WriteAllTextAsync(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "playlist.m3u"), m3uContent); }
3. 动态申请权限
在调用文件选择器或转换逻辑前,需要检查并申请权限:
#if ANDROID string requiredPermission; if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Tiramisu) { requiredPermission = Manifest.Permission.ReadMediaAudio; } else { requiredPermission = Manifest.Permission.ReadExternalStorage; } if (ContextCompat.CheckSelfPermission(Application.Context, requiredPermission) != Permission.Granted) { // 假设当前页面是Activity,若在ViewModel中需通过Messenger等方式通知页面发起请求 ActivityCompat.RequestPermissions((Activity)Application.Context, new[] { requiredPermission }, 1001); } #endif
注意事项
- Android 10+的Scoped Storage限制:部分私有文件(如其他应用的内部文件)无法获取真实路径,此时只能建议用户将文件移动到公共目录(如Music文件夹)后再选择。
- 复制文件的备选方案仅适用于用户同意的情况,且会占用额外存储空间,需谨慎使用。
内容的提问来源于stack exchange,提问作者Daap




