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
相关产品推荐
相关产品推荐

