Xamarin Android 13无法在下载目录读写文件求助
在Xamarin.Android中解决Android 13下载目录读写权限问题
Android 10引入的分区存储机制,到Android 13又进一步细化了权限控制,直接导致旧的外部存储读写逻辑失效。以下是针对Xamarin环境的完整解决方案:
一、权限配置与动态申请
1. 清单文件配置
首先在AndroidManifest.xml中添加对应权限,区分不同版本适配:
<!-- Android 10-12 兼容用读写权限 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- Android 13+ 媒体文件权限(若配置文件属于图片/音频/视频类型) --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <!-- 访问所有外部文件的特殊权限(针对非媒体配置文件,需用户手动授权) --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <!-- Android 10-12 过渡用旧存储模型(可选,仅用于兼容旧逻辑) --> <application android:requestLegacyExternalStorage="true" ...>
2. 动态权限申请代码
在Activity或Fragment中实现权限检查与申请逻辑:
private const int RequestStoragePermissions = 100; public async Task VerifyStoragePermissions() { if (Build.VERSION.SdkInt >= BuildVersionCodes.Tiramisu) { // 检查媒体权限(针对媒体类配置文件) var hasMediaPermissions = ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadMediaImages) == Permission.Granted && ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadMediaVideo) == Permission.Granted && ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadMediaAudio) == Permission.Granted; if (!hasMediaPermissions) { ActivityCompat.RequestPermissions(this, new[] { Manifest.Permission.ReadMediaImages, Manifest.Permission.ReadMediaVideo, Manifest.Permission.ReadMediaAudio }, RequestStoragePermissions); } // 非媒体文件需申请全文件访问权限 else if (!Environment.IsExternalStorageManager) { var intent = new Intent(Settings.ActionManageAppAllFilesAccessPermission); intent.SetData(Android.Net.Uri.Parse($"package:{PackageName}")); StartActivityForResult(intent, RequestStoragePermissions); } else { PerformFileOperations(); } } else if (Build.VERSION.SdkInt >= BuildVersionCodes.Q) { // Android 10-12 申请传统读写权限 var hasReadPerm = ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage) == Permission.Granted; var hasWritePerm = ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) == Permission.Granted; if (!hasReadPerm || !hasWritePerm) { ActivityCompat.RequestPermissions(this, new[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage }, RequestStoragePermissions); } else { PerformFileOperations(); } } } // 处理权限申请结果 public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults) { base.OnRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == RequestStoragePermissions) { if (grantResults.All(grant => grant == Permission.Granted)) { PerformFileOperations(); } else { Toast.MakeText(this, "缺少存储权限,无法操作配置文件", ToastLength.Long).Show(); } } } // 处理全文件访问权限的授权结果 protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); if (requestCode == RequestStoragePermissions) { if (Environment.IsExternalStorageManager) { PerformFileOperations(); } else { Toast.MakeText(this, "未授予全文件访问权限,无法操作配置文件", ToastLength.Long).Show(); } } }
二、下载目录的文件读写实现
1. 获取下载目录路径
var downloadDirectory = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads); var configFileFullPath = Path.Combine(downloadDirectory.AbsolutePath, "app_config.ini");
2. 写入配置文件
public void SaveConfigFile(string configContent) { try { using var writer = new StreamWriter(configFileFullPath, false); writer.Write(configContent); Toast.MakeText(this, "配置文件已保存至下载目录", ToastLength.Short).Show(); } catch (Exception ex) { Toast.MakeText(this, $"保存失败:{ex.Message}", ToastLength.Long).Show(); } }
3. 读取配置文件
public string LoadConfigFile() { try { if (!File.Exists(configFileFullPath)) { return string.Empty; } using var reader = new StreamReader(configFileFullPath); return reader.ReadToEnd(); } catch (Exception ex) { Toast.MakeText(this, $"读取失败:{ex.Message}", ToastLength.Long).Show(); return string.Empty; } }
三、更友好的替代方案:存储访问框架(SAF)
如果不想申请MANAGE_EXTERNAL_STORAGE(该权限需要用户跳转到系统设置,体验较差),可以使用SAF让用户主动选择下载目录,通过Uri进行文件操作:
private const int RequestPickDirectory = 101; public void PromptUserSelectDownloadDir() { var intent = new Intent(Intent.ActionOpenDocumentTree); // 初始定位到下载目录 var downloadUri = Android.Net.Uri.Parse($"content://{Android.OS.Environment.DirectoryDownloads}"); intent.PutExtra(DocumentsContract.ExtraInitialUri, downloadUri); StartActivityForResult(intent, RequestPickDirectory); } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); if (requestCode == RequestPickDirectory && resultCode == Result.Ok && data != null) { var selectedDirUri = data.Data; // 持久化Uri权限,确保重启后仍可访问 ContentResolver.TakePersistableUriPermission(selectedDirUri, Intent.FlagsGrantReadUriPermission | Intent.FlagsGrantWriteUriPermission); // 构建配置文件的Uri var configFileUri = DocumentsContract.BuildDocumentUriUsingTree(selectedDirUri, DocumentsContract.GetTreeDocumentId(selectedDirUri)) .BuildUpon().AppendPath("app_config.ini").Build(); // 写入示例 try { using var outputStream = ContentResolver.OpenOutputStream(configFileUri); using var writer = new StreamWriter(outputStream); writer.Write("{\"theme\":\"dark\",\"autoSync\":true}"); Toast.MakeText(this, "配置文件保存成功", ToastLength.Short).Show(); } catch (Exception ex) { Toast.MakeText(this, $"保存失败:{ex.Message}", ToastLength.Long).Show(); } } }
内容的提问来源于stack exchange,提问作者Anubhav
相关产品推荐
相关产品推荐

