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

Xamarin Android如何获取本地/外部存储中选中文件的路径?

在Xamarin Android中获取选中文件的路径

嘿,我来帮你搞定这个问题!你已经写了部分Intent的代码,接下来我给你补全并讲解整个流程,重点适配不同Android版本的存储限制,毕竟Android 10之后的Scoped Storage对文件访问管得更严了。

第一步:完善文件选择器的Intent配置

先把你那部分代码补全,确保文件选择器能正确唤起并获取必要的访问权限:

// 创建文件选择Intent
var pickFileIntent = new Intent(Intent.ActionOpenDocument);

// 你定义的要支持的文件类型数组
string[] allowedMimeTypes = { 
    "application/pdf", 
    "image/png", 
    "image/jpeg", 
    "image/tiff", 
    "application/msword", 
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 
    "application/vnd.ms-excel", 
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" 
};

// 传入允许的MIME类型,实现精准筛选
pickFileIntent.PutExtra(Intent.ExtraMimeTypes, allowedMimeTypes);
// 设置类型为*/*,配合ExtraMimeTypes生效
pickFileIntent.SetType("*/*");

// 添加权限标记,确保应用能读取选中的文件,甚至长期访问
pickFileIntent.AddFlags(Intent.FlagGrantReadUriPermission);
pickFileIntent.AddFlags(Intent.FlagGrantPersistableUriPermission);
pickFileIntent.AddFlags(Intent.FlagGrantPrefixUriPermission);

接下来分两种方式启动选择器(根据你的Xamarin版本选择):

方式1:旧版Xamarin(使用OnActivityResult)

如果你的项目还在用传统的OnActivityResult,可以这样启动:

// 自定义请求码,后续用来识别返回结果
StartActivityForResult(pickFileIntent, 100);

然后重写OnActivityResult处理返回:

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
    base.OnActivityResult(requestCode, resultCode, data);
    
    if (requestCode == 100 && resultCode == Result.Ok && data != null)
    {
        Android.Net.Uri selectedFileUri = data.Data;
        if (selectedFileUri != null)
        {
            // 调用方法获取文件路径
            string filePath = GetFilePathFromUri(this, selectedFileUri);
            // 这里就可以用filePath做你需要的操作了
        }
    }
}

方式2:新版Xamarin(推荐用ActivityResultContracts)

现在Xamarin更推荐用ActivityResultContracts来处理返回结果,避免OnActivityResult的繁琐。先在OnCreate中注册回调:

protected override void OnCreate(Bundle savedInstanceState)
{
    base.OnCreate(savedInstanceState);
    SetContentView(Resource.Layout.Main);

    // 注册文件选择的回调
    var filePickerLauncher = RegisterForActivityResult(
        new ActivityResultContracts.OpenDocument(), 
        selectedUri =>
        {
            if (selectedUri != null)
            {
                // 获取路径并处理
                string filePath = GetFilePathFromUri(this, selectedUri);
                // 后续操作...
            }
        });

    // 假设你有一个按钮触发选择器
    var pickFileBtn = FindViewById<Button>(Resource.Id.btnPickFile);
    pickFileBtn.Click += (s, e) =>
    {
        // 传入允许的MIME类型启动选择器
        filePickerLauncher.Launch(allowedMimeTypes);
    };
}

第二步:实现获取路径的核心方法

下面这个方法会根据Android版本和Uri类型,正确获取文件路径——重点解决了Android 10+的Scoped Storage限制:

private string GetFilePathFromUri(Context context, Android.Net.Uri uri)
{
    string filePath = null;

    // 处理content类型的Uri(大部分选中的文件都是这个类型)
    if (ContentResolver.SchemeContent.Equals(uri.Scheme))
    {
        if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
        {
            // Android 10及以上,不能直接拿外部路径,复制文件到应用私有目录再获取路径
            using (var inputStream = context.ContentResolver.OpenInputStream(uri))
            {
                string fileName = GetFileNameFromUri(context, uri);
                // 用应用缓存目录存储复制后的文件,也可以用FilesDir
                var cacheDir = context.CacheDir;
                var tempFile = new File(cacheDir, fileName);

                // 复制文件到私有目录
                using (var outputStream = new FileStream(tempFile.Path, FileMode.Create))
                {
                    inputStream.CopyTo(outputStream);
                }

                filePath = tempFile.Path;
            }
        }
        else
        {
            // Android 10之前,通过ContentResolver查询获取路径
            string[] projection = { MediaStore.MediaColumns.Data };
            using (var cursor = context.ContentResolver.Query(uri, projection, null, null, null))
            {
                if (cursor != null && cursor.MoveToFirst())
                {
                    int columnIndex = cursor.GetColumnIndex(projection[0]);
                    filePath = cursor.GetString(columnIndex);
                    cursor.Close();
                }
            }
        }
    }
    // 处理file类型的Uri(这种情况比较少见)
    else if (ContentResolver.SchemeFile.Equals(uri.Scheme))
    {
        filePath = uri.Path;
    }

    return filePath;
}

// 辅助方法:从Uri中提取文件名
private string GetFileNameFromUri(Context context, Android.Net.Uri uri)
{
    string fileName = null;

    if (ContentResolver.SchemeContent.Equals(uri.Scheme))
    {
        var cursor = context.ContentResolver.Query(uri, null, null, null, null);
        if (cursor != null)
        {
            cursor.MoveToFirst();
            fileName = cursor.GetString(cursor.GetColumnIndex(OpenableColumns.DisplayName));
            cursor.Close();
        }
    }
    else
    {
        fileName = System.IO.Path.GetFileName(uri.Path);
    }

    return fileName;
}

几个重要的注意事项

  • Scoped Storage适配:Android 10+不允许直接访问外部存储的文件路径,所以代码里会把选中的文件复制到应用私有目录(CacheDir),这样你就能拿到合法的路径了。如果不需要路径,直接用ContentResolver.OpenInputStream操作流会更高效。
  • 权限问题:Android 10之前需要在Manifest中申请READ_EXTERNAL_STORAGE权限;Android 10+通过Intent选择文件时,已经通过FlagGrantReadUriPermission获得临时访问权限,如果需要长期访问,记得调用ContentResolver.TakePersistableUriPermission(uri, ActivityFlags.GrantReadUriPermission)保存权限。
  • 文件名重复:上面的代码没处理同名文件的覆盖问题,如果需要避免,可以在文件名后加时间戳或者随机字符串。

内容的提问来源于stack exchange,提问作者Thabang Motimele

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:46:02