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

Xamarin.Android偶现System.IO.IOException: '/sdcard'已存在异常求助

问题分析与解决方案

问题背景

生产环境Xamarin.Forms(C#)应用在数千台Android 8.1和10设备(内置存储,无真实SD卡)上偶现System.IO.IOException: The file '/sdcard' already exists崩溃,触发于App构造函数创建SQLite数据库连接的流程中。异常发生时数据库路径和目录均为有效值,重启应用后恢复正常。

堆栈跟踪:

FileSystem.CreateDirectory (System.String fullPath)
Directory.CreateDirectory (System.String path)
DatabaseConnectionProviderBase.CreateDatabaseConnection (System.String databaseFilePath)

相关代码:

protected SQLiteAsyncConnection CreateDatabaseConnection(string databaseFilePath)
{
    if (string.IsNullOrWhiteSpace(databaseFilePath))
        throw new ArgumentOutOfRangeException(nameof(databaseFilePath), "Argument cannot be null, empty or whitespace!");

    var directory = Path.GetDirectoryName(databaseFilePath);

    try
    {
        if (directory != null && !Directory.Exists(directory))
            Directory.CreateDirectory(directory);
    }
    catch (Exception ex)
    {
        var metaData = new Dictionary<string, string>
        {
            { "DatabaseFilePath", databaseFilePath },
            { "Directory", directory }
        };

        LoggingService.ReportException(ex, metaData);

        throw;
    }

    return CreateAsyncDbConnection(databaseFilePath);
}

核心原因

  1. 符号链接状态异常:Android中/sdcard是指向内置存储/storage/emulated/0的符号链接,当系统存储服务未完全就绪(如刚开机、应用被系统重启),Directory.Exists可能误判其为文件而非目录,导致后续尝试创建/sdcard时抛出“已存在”异常。
  2. 启动时机竞争:App构造函数初始化过早,此时系统存储挂载流程尚未完成,存储状态不稳定,引发IO操作的误判。

解决方案

1. 替换根路径为官方推荐路径

避免直接依赖/sdcard符号链接,改用Android官方提供的应用专属外部存储路径,该路径无需额外权限,且系统会自动维护:

// 在Android项目中获取合法路径
var externalFilesDir = Android.App.Application.Context.GetExternalFilesDir(null).AbsolutePath;
// 拼接数据库路径
var databaseFilePath = Path.Combine(externalFilesDir, "database", "mydatabasefile.db3");

此路径格式为/storage/emulated/0/Android/data/com.mycompany.myapp/files/database/mydatabasefile.db3,完全规避/sdcard符号链接带来的问题。

2. 优化目录创建容错逻辑

即使Directory.Exists判断错误,只要目录实际存在,后续数据库连接仍能正常建立。可捕获特定IOException并忽略,避免崩溃:

protected SQLiteAsyncConnection CreateDatabaseConnection(string databaseFilePath)
{
    if (string.IsNullOrWhiteSpace(databaseFilePath))
        throw new ArgumentOutOfRangeException(nameof(databaseFilePath), "Argument cannot be null, empty or whitespace!");

    var directory = Path.GetDirectoryName(databaseFilePath);

    if (directory != null)
    {
        try
        {
            Directory.CreateDirectory(directory);
        }
        catch (IOException ex) when (ex.Message.Contains("already exists"))
        {
            // 目录已存在,无需处理,直接继续
            LoggingService.LogInfo($"Directory {directory} already exists, skip creation");
        }
        catch (Exception ex)
        {
            var metaData = new Dictionary<string, string>
            {
                { "DatabaseFilePath", databaseFilePath },
                { "Directory", directory }
            };

            LoggingService.ReportException(ex, metaData);
            throw;
        }
    }

    return CreateAsyncDbConnection(databaseFilePath);
}

注:Directory.CreateDirectory本身是幂等的,但在符号链接状态异常时仍可能抛出异常,因此针对性捕获“已存在”的IOException即可。

3. 延迟数据库初始化

将数据库连接初始化从App构造函数推迟到首次使用数据库的时机(如首页加载完成后),给系统足够时间完成存储服务初始化:

// 在App类中延迟初始化
private SQLiteAsyncConnection _dbConnection;
public SQLiteAsyncConnection DbConnection => _dbConnection ??= CreateDatabaseConnection(GetDatabaseFilePath());

// 在需要使用数据库的页面或服务中调用App.Current.DbConnection

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 22:20:27