Xamarin Forms导出ApplicationData下SQLite库到SD卡报空引用异常
问题排查结论
异常出在WriteAllBytes代码行,实际触发原因按出现概率从高到低排列如下:
- 硬编码
/sdcard/路径无效:Android系统没有统一固定的外部存储根路径,不同厂商、不同系统版本的外部存储路径可能为/storage/emulated/0/、/mnt/sdcard/等,硬编码路径会导致程序找不到目标目录,写入时抛出异常。同时Android 4.4之后普通应用无权直接访问系统根级目录,直接写/sdcard/属于非法路径访问。 - 缺少存储权限:Android 6.0以上版本写入外部存储需要动态申请危险权限,仅在清单文件声明权限不做动态申请的话,应用没有对应路径的写入权限,调用文件写入方法会触发异常。
- 入参为空:如果源数据库路径拼接错误(sqlite-net默认创建的数据库一般带
.db3/.db后缀,原代码拼接的文件名是Mydatabase没有后缀,大概率读不到真实文件),ReadAllBytes返回null,传入WriteAllBytes时就会触发空引用异常。
修复步骤
- 补全权限配置
在Android项目的AndroidManifest.xml中添加必要权限:
针对Android 10及以上版本,在<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><application>节点添加分区存储兼容配置:<application android:requestLegacyExternalStorage="true" ...其余配置> - 替换硬编码路径,增加权限申请、文件校验逻辑
不要手动写/sdcard/,通过系统官方API获取合法的外部存储路径;执行复制前先校验权限、校验源文件是否存在,避免空值传入写入方法。 - 修复后的可运行代码:
private const int StorageRequestCode = 1001; private void CopyDBButton_Clicked(object sender, EventArgs e) { // 未获取存储权限时先发起权限申请 if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != Permission.Granted) { ActivityCompat.RequestPermissions(this, new[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage }, StorageRequestCode); return; } // 注意核对sqlite数据库实际文件名,补充对应后缀,避免找不到源文件 var basePath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); var finalPath = Path.Combine(basePath, "Mydatabase.db3"); if (!File.Exists(finalPath)) { Toast.MakeText(this, "数据库文件不存在,导出失败", ToastLength.Short).Show(); return; } CopyDatabase(finalPath); } public static void CopyDatabase(string databasePath) { var bytes = File.ReadAllBytes(databasePath); if (bytes == null || bytes.Length == 0) { Toast.MakeText(Application.Context, "读取数据库内容为空", ToastLength.Short).Show(); return; } // 通过系统API获取外部存储根路径,禁止硬编码 var externalPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath; var fileCopyName = Path.Combine(externalPath, string.Format("Database_{0:dd-MM-yyyy_HH-mm-ss-tt}.db", DateTime.Now)); File.WriteAllBytes(fileCopyName, bytes); Toast.MakeText(Application.Context, $"数据库已导出至:{fileCopyName}", ToastLength.Long).Show(); }
额外注意
- 执行复制操作前,先关闭所有打开的SqliteConnection连接,避免读取到处于写入中间态的损坏文件。
- 权限申请的回调逻辑需要在对应Activity中实现,用户拒绝授权时要做提示,不要直接执行复制逻辑。
内容的提问来源于stack exchange,提问作者Adrian Hurst
相关产品推荐
相关产品推荐

