You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android平台Avalonia跨平台应用文件I/O操作出现间歇性不可预测异常

Android平台Avalonia跨平台应用文件I/O操作出现间歇性不可预测异常

我之前也碰到过类似的Android存储适配坑,结合你给出的详细测试信息,咱们来拆解这个问题的核心现象、可能的根因,以及对应的排查和解决思路:

问题核心表现

不管用DocumentFile(SAF存储访问框架)还是直接用System.IO访问本地路径,文件操作的结果完全不稳定:

  • 同一代码连续执行,每次返回的文件数量、属性(比如IsFile)、元数据(文件名、修改时间)随机变化
  • 偶尔出现文件凭空消失、文件被误判为目录、元数据异常(比如修改时间变成1970年、文件名缺失)
  • 极端情况下,某一轮次的查询直接返回空结果

重现测试代码

1. 使用SAF的DocumentFile测试代码

var testDir = DocumentFile.FromTreeUri(
    global::Android.App.Application.Context, 
    global::Android.Net.Uri.Parse("content://com.android.externalstorage.documents/tree/primary%3Adocuments/document/primary%3Adocuments%2FJob_0001")
);
Logger.LogInfo($"TargetDir: {testDir.Uri}");

for(int attempt = 0; attempt < 3; attempt++) 
{
    foreach (var docFile in testDir.ListFiles()) 
    {
        Logger.LogInfo($"Attempt{attempt}. IsFile: {docFile.IsFile} - {docFile.Name} - {DateTimeOffset.FromUnixTimeMilliseconds(docFile.LastModified()).LocalDateTime:yyyy-MM-dd HH:mm:ss}");
    }
    Task.Delay(100).Wait();
}

2. 使用System.IO直接访问的测试代码

Logger.LogInfo($"TargetDir: {localPath}");
for(int attempt = 0; attempt < 3; attempt++) 
{
    var fileNames = Directory.GetFiles(localPath, "*", SearchOption.TopDirectoryOnly);
    Logger.LogInfo($"Found {fileNames.Length} files:");
    foreach (var fileName in fileNames) 
    {
        Logger.LogInfo($"Attempt{attempt}. {fileName}");
    }
    Task.Delay(100).Wait();
}

关键异常日志节选

DocumentFile测试的异常细节

17:54:24 -- TargetDir: content://com.android.externalstorage.documents/tree/primary%3Adocuments/document/primary%3Adocuments%2FJob_0001
17:54:24 -- Attempt0. IsFile: False - Single-01.RA1 - 2025-09-14 21:37:27 <-- 错误:文件被识别为目录
17:54:24 -- Attempt0. IsFile: True - - 2025-09-14 21:37:27 <-- 错误:文件名缺失
17:54:24 -- Attempt0. IsFile: True - Single-03.RA1 - 1969-12-31 16:00:00 <-- 错误:时间戳异常
17:54:24 -- Attempt2. IsFile: True - Single-01.RA1 - 2025-09-14 21:37:27 <-- 之前的错误属性又自动修正了
17:54:25 -- Attempt2. IsFile: True - Single-03.gps - 2025-09-20 13:54:38 <-- Single-03.RA2完全消失

System.IO测试的异常细节

18:16:46 -- TargetDir: /storage/emulated/0/documents/Job_0001
18:16:46 -- Found 8 files:
18:16:46 -- Attempt0. /storage/emulated/0/documents/Job_0001/Single-02.RAD <-- 缺失所有Single-01.*文件
18:16:46 -- Found 0 files: <-- Attempt1直接返回空结果
18:16:47 -- Found 8 files:
18:16:47 -- Attempt2. /storage/emulated/0/documents/Job_0001/Single-01.RAD <-- 这次又缺失Single-03.*文件

可能的解决和排查方向

1. 先确认SAF权限的有效性和稳定性

虽然你说用户已经授权,但Android的SAF权限可能存在临时失效或缓存问题:

  • 每次操作前先验证DocumentFile的状态:调用Exists()IsDirectory()确认目录有效
  • 不要长期持有DocumentFile实例,每次操作都通过Uri重新获取,避免实例状态过期
  • 尝试重新发起SAF目录选择,确认权限是否真的持续有效(比如检查返回的DocumentFile是否为null)

2. 规避Avalonia的适配竞态问题

Avalonia在Android上的文件I/O依赖Xamarin.Android的绑定,可能存在异步或缓存冲突:

  • 把同步阻塞的代码(比如Task.Delay(100).Wait())改成await Task.Delay(100),避免线程阻塞导致的状态异常
  • 绕过Avalonia封装,直接用Android原生ContentResolver查询文件,对比结果是否稳定:
    var resolver = global::Android.App.Application.Context.ContentResolver;
    var cursor = resolver.Query(
        testDir.Uri, 
        new string[] { Android.Provider.OpenableColumns.DisplayName, "last_modified" }, 
        null, null, null
    );
    if (cursor != null)
    {
        while (cursor.MoveToNext())
        {
            var fileName = cursor.GetString(cursor.GetColumnIndex(Android.Provider.OpenableColumns.DisplayName));
            var lastModified = cursor.GetLong(cursor.GetColumnIndex("last_modified"));
            Logger.LogInfo($"File: {fileName} - {DateTimeOffset.FromUnixTimeMilliseconds(lastModified).LocalDateTime}");
        }
        cursor.Close();
    }
    

3. 处理Android媒体扫描的缓存问题

Android的媒体扫描服务可能会缓存文件元数据,导致查询结果不一致:

  • 如果是应用刚创建文件就查询,先发送媒体扫描广播刷新缓存:
    var fileUri = Android.Net.Uri.FromFile(new Java.IO.File(localFilePath));
    global::Android.App.Application.Context.SendBroadcast(
        new Intent(Intent.ActionMediaScannerScanFile, fileUri)
    );
    await Task.Delay(500); // 等待扫描完成再查询
    
  • 优先使用应用私有存储目录(Context.GetExternalFilesDir(null)),避开公共Documents目录的媒体扫描干扰

4. 排查线程安全问题

确保所有文件操作在同一线程或线程安全的环境下执行:

  • 避免在多个线程同时访问同一目录或文件
  • 把文件操作放在后台线程执行,不要和UI线程的操作混在一起

5. 验证Avalonia版本兼容性

检查你使用的Avalonia版本是否有已知的Android文件I/O bug:

  • 升级到最新稳定版Avalonia,查看官方Issue是否有类似问题的修复记录
  • 在纯Xamarin.Android项目中执行相同的文件操作,对比结果是否稳定,定位是Avalonia的适配问题还是Android本身的问题

火山引擎 最新活动