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

卸载重装Flutter应用后无法访问Download目录SQLite数据库的解决方法

问题描述

SQLite数据库存储路径为/storage/emulated/0/Download/Database,初始使用正常,但卸载重装应用后访问数据库时触发权限拒绝错误:

/storage/emulated/0/DATABASE/tablet_pos.db (OS Error: Permission denied, errno = 13)

已确认文件存在且路径、文件名无误,目录包含tablet_pos.db和tablet_pos.journal两个文件。附数据库初始化代码:

Future<void> initDatabase() async {
if (Platform.isAndroid) {
  databasesPath = '/storage/emulated/0/Download/DATABASE';

  // storageDirectory = await getExternalStorageDirectory();
  // databasesPath = p.join(storageDirectory.path, FolderNames.database);
}

if (!await Directory(databasesPath).exists()) {
  await Directory(databasesPath).create(recursive: true);
}
var path = p.join(databasesPath, 'tablet_pos.db');
print('databasesPath $databasesPath $path');

var databaseFile = File(path);

if (!await databaseFile.exists()) {
  try {
    print('await $database $path');
    await databaseFile.create(recursive: true);
  } catch (e) {
    print('Error creating database file: $e');
    return;
  }
}

// Check if the database file exists
if (!await databaseFile.exists()) {
  print('Database file not found');
  return;
}

print('try $database $path');
// Attempt to read the file to verify accessibility
try {
  await databaseFile.readAsBytes();
} catch (e) {
  print('Database file is not readable: $e'); // error here on reinstall
  return;
}

database = await openDatabase(path, version: 1,
    onCreate: (Database db, int version) async {
  await db.execute(CustomerDB.createTable);

});
print('database $database $path');
}

解决方法

1. 重新申请存储权限

卸载应用后,之前授予的权限会被清空,必须重新请求公共存储访问权限:

  • 在AndroidManifest.xml中添加权限声明:
    <!-- Android 10及以下所需权限 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- Android 11+ 访问Download目录专属权限 -->
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
    
  • 用permission_handler包动态申请权限,在初始化数据库前校验:
    import 'package:permission_handler/permission_handler.dart';
    
    Future<bool> checkStoragePermission() async {
      PermissionStatus status;
      if (Platform.isAndroid) {
        status = await (await AndroidInfo.version >= 30 
            ? Permission.manageExternalStorage.request() 
            : Permission.storage.request());
        return status.isGranted;
      }
      return true;
    }
    
    调用后若权限未授予,可引导用户前往设置开启。

2. 适配Android分区存储(Android 10+)

Android 10起的分区存储限制了绝对路径访问公共目录,建议用**存储访问框架(SAF)**获取持久化目录权限:

  • Flutter侧调用原生方法打开目录选择器:
    import 'package:flutter/services.dart';
    
    Future<String?> getPersistentDirAccess() async {
      const MethodChannel channel = MethodChannel('storage_access');
      try {
        return await channel.invokeMethod('openDirPicker');
      } on PlatformException catch (e) {
        print('选目录出错: $e');
        return null;
      }
    }
    
  • Android原生侧实现目录选择与权限持久化(MainActivity中):
    private val CHANNEL = "storage_access"
    private val REQUEST_DIR = 1001
    
    override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
        when (call.method) {
          "openDirPicker" -> {
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
            startActivityForResult(intent, REQUEST_DIR)
            result.success(null)
          }
          else -> result.notImplemented()
        }
      }
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
      super.onActivityResult(requestCode, resultCode, data)
      if (requestCode == REQUEST_DIR && resultCode == RESULT_OK) {
        data?.data?.let { uri ->
          // 持久化权限,重启、重装后仍有效
          contentResolver.takePersistableUriPermission(uri, 
              Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
          // 将URI传回Flutter
          MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CHANNEL)
              .invokeMethod("dirSelected", uri.toString())
        }
      }
    }
    
    获取URI后,通过ContentResolver操作文件,替代直接使用File类。

3. 避免硬编码路径

不要直接写/storage/emulated/0/Download/DATABASE,改用Flutter路径API获取公共目录:

import 'package:path_provider/path_provider.dart';

Future<String> getDownloadDir() async {
  if (Platform.isAndroid) {
    // Android 10+ 推荐用SAF,旧版本可兼容以下写法
    return (await getExternalStorageDirectory())?.parent?.parent?.parent?.parent?.path ?? '';
  }
  return (await getApplicationDocumentsDirectory()).path;
}

4. 尝试修改文件权限

若数据库文件由旧应用创建,新应用UID不同可能无读写权限,可尝试修改文件权限:

await databaseFile.chmod(0666); // 赋予所有用户读写权限

注:Android 10+分区存储下该方法可能受限,仅作补充方案。

总结

优先选择申请MANAGE_EXTERNAL_STORAGE权限(Android 11+)或动态存储权限(Android 10及以下);若需严格适配分区存储,就用**存储访问框架(SAF)**获取持久化目录权限,确保卸载重装后仍能访问目标数据库。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 20:05:34