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
相关产品推荐
相关产品推荐

