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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 06:24:56