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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 16:03:10