Android外部数据库恢复:URI格式差异及实现合理性咨询
Hey there, let's break down your questions one by one based on Android's file access rules and practical development best practices:
The variation comes from Android's evolving file access security model and vendor customizations:
- Older Android versions (pre-7.0, non-custom ROMs): Before Android 7.0 (API 24), the system was more lenient with file access.
ACTION_GET_CONTENTmight return afile://URI pointing directly to the file's path on external storage, since apps could generally access external storage with the right permissions. - Custom ROMs (like Xiaomi's Android 5.1): Some vendors implemented stricter file access controls earlier than Google's official requirements. They use
content://URIs (backed by a ContentProvider) to mediate file access, even on older Android versions, to prevent apps from directly accessing sensitive files. - Android 7.0 and above: Google introduced StrictMode which blocks cross-app sharing of
file://URIs (to avoid permission leaks). Instead,ACTION_GET_CONTENTwill always return acontent://URI, typically served by a system FileProvider, ensuring apps only access files they're explicitly allowed to.
The key rule here is: never rely on Uri.getPath()—it returns inconsistent values across URI types (for content:// URIs, it's often just an internal ID, not a real filesystem path). Instead, use ContentResolver to interact with the file directly, regardless of the URI format:
Step 1: Update your onActivityResult logic
Pass the full URI string instead of a parsed path, and validate the file using the content resolver:
@Override public void onActivityResult(int requestCode, int resultCode, Intent resultData) { super.onActivityResult(requestCode, resultCode, resultData); if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { if (resultData == null) { MessageDialogView.showErrorResultMessage(getResources().getString(R.string.Message_Error_Open_File)); return; } Uri selectedUri = resultData.getData(); // Validate the file is a SQLite database (check file header via input stream) if (!isValidSqliteFile(selectedUri)) { MessageDialogView.showErrorResultMessage(getResources().getString(R.string.Message_Error_Open_File)); return; } // Pass the URI string to your restore activity Map<String, String> parameters = new HashMap<>(); parameters.put(NavigationManagerKeyParameters.RESTORE_DATA_BASE_ACTIVITY_DATA, selectedUri.toString()); NavigationManager.getInstance().navigateTo(NavigationManager.Pages.RestoreDataBaseActivity, parameters); } } // Helper to check if the selected file is a valid SQLite database private boolean isValidSqliteFile(Uri uri) { try (InputStream inputStream = getContentResolver().openInputStream(uri)) { if (inputStream == null) return false; byte[] fileHeader = new byte[16]; int bytesRead = inputStream.read(fileHeader); if (bytesRead < 16) return false; // SQLite databases start with the header "SQLite format 3" return "SQLite format 3".equals(new String(fileHeader, StandardCharsets.UTF_8).trim()); } catch (IOException e) { e.printStackTrace(); return false; } }
Step 2: Handle the URI in your RestoreDataBaseActivity
Convert the URI string back to a Uri object, then use ContentResolver.openInputStream() to read the file:
// Inside RestoreDataBaseActivity Uri selectedUri = Uri.parse(getIntent().getStringExtra(NavigationManagerKeyParameters.RESTORE_DATA_BASE_ACTIVITY_DATA)); // Copy the file to your app's private storage (see question 3 for details) File tempDb = copyToPrivateStorage(selectedUri);
The core idea is correct, but your current copyDataBase() code is targeting the wrong input source (it reads from assets instead of the user-selected file). Here's why this approach makes sense and how to fix the implementation:
Why this approach is good:
- App private storage (like
getFilesDir()orgetCacheDir()) is secure—other apps can't access files here, so you avoid permission issues later. content://URIs often have temporary access permissions; copying the file to your private storage ensures you can access it as long as you need to, without worrying about the URI expiring.
Fixed copy method:
private File copyToPrivateStorage(Uri sourceUri) throws IOException { // Create a temporary file in your app's private files directory File tempDbFile = new File(getFilesDir(), "temp_restore.db"); // Use ContentResolver to get input stream from the URI try (InputStream inputStream = getContentResolver().openInputStream(sourceUri); OutputStream outputStream = new FileOutputStream(tempDbFile)) { byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); } outputStream.flush(); } return tempDbFile; }
After copying, you can open this temporary file with SQLiteOpenHelper or SQLiteDatabase to perform your restore operations. Don't forget to delete the temporary file once you're done with it to free up space:
// Delete temp file after restore tempDbFile.delete();
内容的提问来源于stack exchange,提问作者Alexander Ananev

