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

NET 8 MAUI Android13平板SQLite备份路径存在性异常排查

Android 13下MAUI程序无法访问Survey123 SQLite数据库文件的解决方案

问题描述

我使用Visual Studio 2022开发了一款基于.NET 8 MAUI的程序,部署在Android 13系统的平板上用于备份数据库。当传入指定路径验证SQLite数据库文件是否存在时,系统提示文件不存在,但该文件实际位于该路径中。需要复制该路径下的Survey SQLite数据库文件,相关代码如下:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        //CopyAndCompressSQLiteFile();
        InitializeAsync();
    }

    private void CopyAndCompressSQLiteFile()
    {
        try
        {
            var sourcePath = @"/storage/emulated/0/Android/data/com.esri.survey123/files/ArcGIS/My Surveys/Databases/a084e65cd9403d813e8ec667ca329a63.sqlite";
            //var sourcePath = @"Este equipo\Galaxy Tab A9\Almacenamiento interno\Android\data\com.esri.survey123\files\ArcGIS\My Surveys\Databases\a084e65cd9403d813e8ec667ca329a63.sqlite";

            var targetFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "backup.sqlite");

            //CopyDatabaseContent(sourcePath, targetFilePath);

            // Verifica si el archivo existe en la ubicación de origen
            if (File.Exists(sourcePath))
            {
                Console.WriteLine("El archivo existe en la ubicación de origen.");

                // Copia el archivo
                File.Copy(sourcePath, targetFilePath, overwrite: true);

                // Comprime el archivo
                using (FileStream zipToOpen = new FileStream(targetFilePath + ".zip", FileMode.Create))
                {
                    using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Update))
                    {
                        archive.CreateEntryFromFile(targetFilePath, Path.GetFileName(targetFilePath));
                    }
                }

                Shell.Current.DisplayAlert("Operación exitosa", "Archivo copiado y comprimido exitosamente", "OK");
            }
            else
            {
                //Console.WriteLine("El archivo no existe en la ubicación de origen.");
                Shell.Current.DisplayAlert("Error", "El archivo no existe en la ubicación de origen.", "OK");
            }
        }
        catch (UnauthorizedAccessException ex)
        {
            Shell.Current.DisplayAlert("Error", $"Error de acceso no autorizado: {ex.Message}", "OK");
        }
        catch (Exception ex)
        {
            Shell.Current.DisplayAlert("Error", $"Archivo no copiado: {ex.Message}", "OK");
        }
    }


    private async void InitializeAsync()
    {
        bool hasPermissions = await RequestPermissionsAsync();
        if (hasPermissions)
        {
            CopyAndCompressSQLiteFile();
        }
        else
        {
            //Console.WriteLine("No se concedieron los permisos necesarios.");
            await Shell.Current.DisplayAlert("Verificar permisos de acceso", "No se concedieron los permisos necesarios.", "OK");
        }
    }

    private async Task<bool> RequestPermissionsAsync()
    {
        var storageReadStatus = await Permissions.CheckStatusAsync<Permissions.StorageRead>();
        if (storageReadStatus != PermissionStatus.Granted)
        {
            if (Permissions.ShouldShowRationale<Permissions.StorageRead>())
            {
                await DisplayAlert("Asignar Permiso lectura", "Se requieren permisos de lectura y escritura para continuar.", "OK");
            }

            // Solicitar el permiso
            storageReadStatus = await Permissions.RequestAsync<Permissions.StorageRead>();
        }

        var storageWriteStatus = await Permissions.CheckStatusAsync<Permissions.StorageWrite>();
        if (storageWriteStatus != PermissionStatus.Granted)
        {
            // Solicitar el permiso
            storageWriteStatus = await Permissions.RequestAsync<Permissions.StorageWrite>();
        }

        if (storageReadStatus != PermissionStatus.Granted || storageWriteStatus != PermissionStatus.Granted)
        {
            // Notificar al usuario que los permisos son necesarios
            await DisplayAlert("Permisos requeridos", "Se requieren permisos de lectura y escritura para continuar.", "OK");
        }

        return storageReadStatus == PermissionStatus.Granted && storageWriteStatus == PermissionStatus.Granted;
    }
}

问题原因

Android 13(API 33+)强化了应用沙箱机制,其他应用无法直接通过绝对路径访问另一个应用的Android/data/[包名]/私有目录,即使文件实际存在,File.Exists()也会返回false。传统的StorageRead/StorageWrite权限仅能访问公共存储目录,无法突破私有目录的访问限制。

解决方案

1. 使用存储访问框架(SAF)让用户手动选择文件

这是Android 13及以上版本访问其他应用私有文件的标准方式,通过调用系统文件选择器让用户指定要备份的SQLite文件,避免路径硬编码和权限问题:

private async Task PickAndBackupFileAsync()
{
    var result = await FilePicker.Default.PickAsync(new PickOptions
    {
        PickerTitle = "选择Survey123数据库文件",
        FileTypes = new FilePickerFileType(new Dictionary<DevicePlatform, IEnumerable<string>>
        {
            { DevicePlatform.Android, new[] { "application/x-sqlite3" } }
        })
    });

    if (result != null)
    {
        var targetFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "backup.sqlite");
        
        // 通过流复制文件,避免直接路径访问
        using var sourceStream = await result.OpenReadAsync();
        using var targetStream = File.Create(targetFilePath);
        await sourceStream.CopyToAsync(targetStream);

        // 执行压缩逻辑
        using (FileStream zipToOpen = new FileStream(targetFilePath + ".zip", FileMode.Create))
        {
            using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Update))
            {
                archive.CreateEntryFromFile(targetFilePath, Path.GetFileName(targetFilePath));
            }
        }

        await Shell.Current.DisplayAlert("操作成功", "文件已成功复制并压缩", "确定");
    }
}

修改InitializeAsync方法,替换原有的调用:

private async void InitializeAsync()
{
    bool hasPermissions = await RequestPermissionsAsync();
    if (hasPermissions)
    {
        await PickAndBackupFileAsync(); // 替换为新方法
    }
    else
    {
        await Shell.Current.DisplayAlert("权限验证", "未获取必要权限", "确定");
    }
}

2. 调整AndroidManifest权限配置

确保在Platforms/Android/AndroidManifest.xml中添加适配Android 13的权限:

<!-- 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" />

<!-- Android 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" />

3. 移除硬编码绝对路径

绝对路径依赖设备和应用版本,容易导致兼容性问题。通过SAF选择文件可以完全规避路径相关的错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 09:14:53