Xamarin.Android适配Android API30:外部存储权限被拒及旧路径适配咨询
解决Xamarin.Android API30+的Scoped Storage权限问题
针对你遇到的System.UnauthorizedAccessException,本质是Android 10(API29)引入的Scoped Storage限制了直接访问外部存储根目录的权限。我给你分临时过渡和长期适配两种方案来解决,同时修改你的日志代码:
一、临时兼容现有应用(API30及以下可用)
如果想快速让现有安装的应用继续访问旧路径,可以先在AndroidManifest.xml的<application>标签里添加这个属性:
android:requestLegacyExternalStorage="true"
这个设置会让应用在Android 10/11上沿用旧的存储模型,你的原有代码就能正常运行。但注意API31(Android12)及以上会忽略这个属性,所以这只是短期过渡方案,最终还是要适配Scoped Storage。
二、长期适配Scoped Storage的正确姿势
1. 选对存储位置
根据你的文件类型(日志、数据库备份),推荐两种存储策略:
- 应用专属外部存储:适合日志这类不需要用户手动访问的文件,路径属于应用私有,无需额外权限,卸载应用时会自动删除。
- 公共Downloads目录:适合需要用户在文件管理器中找到的备份文件,无需权限即可写入。
2. 修改日志文件代码
方案A:用应用专属外部存储(推荐日志使用)
替换你原来的代码,改用Context.GetExternalFilesDir()获取合法路径:
// 获取应用专属外部存储的LogFolder目录 var logDir = Android.App.Application.Context.GetExternalFilesDir("LogFolder"); if (logDir == null) { // 处理存储不可用的情况,比如返回null或内部存储路径 return null; } string logFilePath = Path.Combine(logDir.AbsolutePath, "MyLogFile.txt"); // 检查目录是否存在,不存在则创建 if (!Directory.Exists(logDir.AbsolutePath)) { Directory.CreateDirectory(logDir.AbsolutePath); } try { System.IO.File.WriteAllText(logFilePath, "This is some testing log."); } catch (Exception ex) { string errorMsg = ex.Message; // 这里可以添加错误日志的处理逻辑,比如写入内部存储 } return logFilePath;
这个路径不需要申请READ_EXTERNAL_STORAGE或WRITE_EXTERNAL_STORAGE权限,完全符合Scoped Storage的要求。
方案B:用公共Downloads目录(适合备份文件)
如果需要用户能在文件管理器里找到日志/备份,用MediaStore写入Downloads目录:
public string CreateLogInDownloads() { var contentValues = new ContentValues(); // 设置文件名和类型 contentValues.Put(MediaStore.Downloads.Columns.DisplayName, "MyLogFile.txt"); contentValues.Put(MediaStore.Downloads.Columns.MimeType, "text/plain"); // 设置在Downloads下的子目录 contentValues.Put(MediaStore.Downloads.Columns.RelativePath, Environment.DirectoryDownloads + "/LogFolder"); // 获取Downloads的URI var downloadUri = Android.App.Application.Context.ContentResolver.Insert(MediaStore.Downloads.ExternalContentUri, contentValues); if (downloadUri == null) { return null; } try { // 写入文件内容 using (var outputStream = Android.App.Application.Context.ContentResolver.OpenOutputStream(downloadUri)) { using (var streamWriter = new StreamWriter(outputStream)) { streamWriter.Write("This is some testing log."); } } return downloadUri.ToString(); } catch (Exception ex) { string errorMsg = ex.Message; // 写入失败时删除无效的MediaStore条目 Android.App.Application.Context.ContentResolver.Delete(downloadUri, null, null); return null; } }
这种方式也不需要任何存储权限,完全合规。
3. 迁移现有用户的旧文件
如果要把用户之前存在/storage/emulated/0/SpecialDir/的文件迁移到新路径:
- 过渡期间:先开启
requestLegacyExternalStorage,用原有代码读取旧文件,复制到新的存储位置,之后再移除这个属性。 - 完全适配后:如果已经去掉了legacy权限,可以通过**存储访问框架(SAF)**让用户手动选择旧的
SpecialDir文件夹,然后读取文件完成迁移。
三、关键注意事项
- API31及以上,
requestLegacyExternalStorage不再生效,必须完成Scoped Storage适配。 - 除非你的应用是文件管理器这类需要全局访问存储的工具,否则不要申请
MANAGE_EXTERNAL_STORAGE权限——这个权限需要用户跳转到系统设置授权,而且Google Play的审核非常严格,大部分应用都无法通过。
内容的提问来源于stack exchange,提问作者user6159419
相关产品推荐
相关产品推荐

