Android加载外部存储SQLite数据库遇文件不可读错误求助
这个问题我遇见过,核心是Android的存储权限和Scoped Storage限制在搞鬼,咱们一步步解决:
解决方案
1. 先搞定存储权限
Android从API 23开始就要求动态申请存储权限,不同版本的系统处理方式还有点不一样:
针对Android 6.0 - 9.0(API 23-28)
首先在AndroidManifest.xml里加权限声明:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
然后在Activity里动态申请权限:
// 在onCreate里检查权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1001); } // 处理权限申请结果 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 1001) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 权限到手,加载数据库 loadDatabase(); } else { Toast.makeText(this, "得开存储权限才能读数据库哦", Toast.LENGTH_SHORT).show(); } } }
针对Android 10+(API 29及以上)
Android 10引入了Scoped Storage,默认不让直接碰公共目录的文件,有两种办法:
方式一:临时兼容(过渡方案,Android 11+有限制)
在AndroidManifest.xml的<application>标签里加一行:
android:requestLegacyExternalStorage="true"
这样能暂时绕过Scoped Storage,但这只是权宜之计,长期不推荐。
方式二:用存储访问框架(SAF,合规推荐)
让用户自己选数据库文件,通过系统文件选择器拿到权限,不用全局存储权限:
// 打开文件选择器,指定只选SQLite文件 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("application/x-sqlite3"); startActivityForResult(intent, 1002); // 处理选择结果 @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 1002 && resultCode == RESULT_OK && data != null) { Uri dbUri = data.getData(); // 拿持久化权限,下次打开不用再选 getContentResolver().takePersistableUriPermission(dbUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); // 推荐把文件复制到app私有目录再打开,避免权限问题 try { InputStream inputStream = getContentResolver().openInputStream(dbUri); File privateDb = new File(getFilesDir(), "temp_db.sqlite"); FileOutputStream outputStream = new FileOutputStream(privateDb); byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); } inputStream.close(); outputStream.close(); // 现在打开私有目录的数据库就没问题了 SQLiteDatabase db = SQLiteDatabase.openDatabase(privateDb.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY); // 接下来就可以操作数据库啦 } catch (IOException | SQLiteException e) { e.printStackTrace(); } } }
2. 先检查文件能不能读
调用openDatabase之前,先确认文件存在且可读:
// 别硬编码路径,用系统API拿Download目录更稳妥 File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); File dbFile = new File(downloadDir, "my_database"); if (dbFile.exists() && dbFile.canRead()) { SQLiteDatabase db = SQLiteDatabase.openDatabase(dbFile.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY); // 操作数据库 } else { Toast.makeText(this, "数据库文件不存在或者没权限读", Toast.LENGTH_SHORT).show(); }
3. 额外注意点
- 永远别硬编码
/storage/emulated/0/这个路径,用Environment.getExternalStoragePublicDirectory()能适配不同设备; - Android 11+里
requestLegacyExternalStorage会失效,必须用SAF或者MediaStore访问公共目录文件,这是合规的做法。
内容的提问来源于stack exchange,提问作者sagar
相关产品推荐
相关产品推荐

