You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动