Android Kotlin数据库导入失败(权限拒绝)问题求助
Android Kotlin数据库导入权限问题排查
问题背景
我是Android Kotlin编程新手,开发了一款简易应用,能正常将数据库导出到手机下载目录,但从下载目录导入数据库到应用数据库文件夹时失败。Logcat报错:
java.io.FileNotFoundException: /storage/emulated/0/Download/mymeds2025.db: open failed: EACCES (Permission denied)
已在Manifest中添加WRITE_EXTERNAL_STORAGE、READ_EXTERNAL_STORAGE权限,并设置android:requestLegacyExternalStorage="true",附上导出和导入代码,求解决思路。
Manifest配置
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <application android:requestLegacyExternalStorage="true"
导出代码(MainActivity.kt)
//Export to download folder (this works!!!!) private fun exportDbFile() { try { val dir = File(Environment.getExternalStorageDirectory().toString() + "/Download/" ) val success = true if (!dir.exists()) { dir.mkdirs() Toast.makeText(applicationContext, "Directory created", Toast.LENGTH_LONG).show() } if (success) { //do nothing } else { Toast.makeText(this, "Failed to create Directory", Toast.LENGTH_LONG).show() } val DATA_DIRECTORY = Environment.getDataDirectory().absolutePath + "/mymeds2025.db" val dbFile = File(getDatabasePath("mymeds2025.db").absolutePath) Log.v("MainActivity", "a-" + dbFile); Log.v("TestActivity", "a-" + DATA_DIRECTORY); Toast.makeText(this,"a- " +dbFile,Toast.LENGTH_LONG).show() File(dbFile,"") .copyTo(File(dir, "mymeds2025.db"), true) Toast.makeText(this,"Database Exported!",Toast.LENGTH_LONG).show() } catch (excep: Exception) { Toast.makeText(this,"$excep",Toast.LENGTH_LONG).show() Log.e("FILECOPYERROR>>>>",excep.toString()) excep.printStackTrace() } }
导入代码(MainActivity.kt)
//Import the db from the download folder private fun importDbFile() { try { //val dir = File(Environment.getExternalStorageDirectory().toString() + "/Download/mymeds2025.db" val dir = "${Environment.getExternalStorageDirectory()}/Download/mymeds2025.db" //) //"/data/data/com.rjenterprize.mymeds2025/databases/" val dbFile = File(getDatabasePath("mymeds2025.db").absolutePath) db.close() db.writableDatabase File(dir,"").copyTo(File(dbFile, ""), true) Toast.makeText(this,"Database Imported!",Toast.LENGTH_LONG).show() } catch (excep: Exception) { Toast.makeText(this,"$excep",Toast.LENGTH_LONG).show() Log.e("FILECOPYERROR>>>>",excep.toString()) excep.printStackTrace() } }
解决思路
1. 补充动态权限申请
Android 6.0(API 23)及以上版本,Manifest声明权限后还需运行时动态申请。导出正常可能是场景权限默认授予,但导入触发了严格检查,添加以下逻辑:
private val REQUEST_STORAGE_PERMISSIONS = 1001 fun checkStoragePermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_STORAGE_PERMISSIONS) } else { importDbFile() } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_STORAGE_PERMISSIONS) { if (grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { importDbFile() } else { Toast.makeText(this, "需要存储权限才能导入数据库", Toast.LENGTH_LONG).show() } } }
调用导入功能前先执行checkStoragePermissions(),而非直接调用importDbFile()。
2. 修正数据库关闭逻辑
导入代码中db.close()后又调用db.writableDatabase,会重新打开数据库导致目标文件被占用,无法覆盖。修改数据库操作部分:
// 替换成你的数据库Helper类实例 val dbHelper = YourDbHelper(this) dbHelper.close() // 或者直接操作SQLiteDatabase实例 if (db.isOpen) { db.close() } // 简化文件复制代码 val sourceFile = File(dir) sourceFile.copyTo(dbFile, true)
3. 适配Android 10+存储规则
即使设置requestLegacyExternalStorage="true",Android 11(API 30)及以上版本该属性已废弃。可两种方式适配:
- 限制权限仅在Android 10及以下生效:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
- 使用存储访问框架(SAF)获取文件,避免权限问题:
private val REQUEST_PICK_FILE = 1002 private fun pickDbFile() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "*/*" putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/octet-stream")) } startActivityForResult(intent, REQUEST_PICK_FILE) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_PICK_FILE && resultCode == RESULT_OK) { data?.data?.let { uri -> contentResolver.openInputStream(uri)?.use { input -> val output = FileOutputStream(dbFile) input.copyTo(output) output.close() Toast.makeText(this, "数据库导入成功", Toast.LENGTH_LONG).show() } } } }
4. 添加文件存在校验
导入前先检查源文件是否存在,避免无意义的异常:
val sourceFile = File(dir) if (!sourceFile.exists()) { Toast.makeText(this, "下载目录中未找到目标数据库文件", Toast.LENGTH_LONG).show() return }
内容的提问来源于stack exchange,提问作者Robert
相关产品推荐
相关产品推荐

