Google Drive应用文件夹SQLite数据库恢复文件不可写入问题排查
Problem Context
I'm trying to restore an SQLite database backup from Google Drive in my Android app. I've declared the WRITE_EXTERNAL_STORAGE permission in the Manifest and obtained runtime permissions, but my restore code always hits the Log.d("RESTORE: ", "File not writable") branch. No error logs are showing up. Here's my restore code (and backup code for reference):
Restore Code
public void startRestore(View view) { int EXTERNAL_WRITE_PERMISSION = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.M) { if(EXTERNAL_WRITE_PERMISSION != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { Snackbar.make(mLayout, "Write permission is required", Snackbar.LENGTH_INDEFINITE) .setAction("OK", new View.OnClickListener() { @Override public void onClick(View view) { ActivityCompat.requestPermissions(BackupActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_STORAGE); } }).show(); } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_STORAGE); } } } if (backupExists()) { Log.d("RESTORE: ", "Started restore"); final String driveFileID = sharedPreferences.getString("dbBackupDriveFileID", ""); final DriveFile driveFile = DriveId.decodeFromString(driveFileID).asDriveFile(); Log.d("RESTORE_FileID: ", driveFileID); final Task<DriveContents> openFileTask = mDriveResourceClient.openFile(driveFile, DriveFile.MODE_READ_ONLY); openFileTask.continueWithTask(new Continuation<DriveContents, Task<Void>>() { @Override public Task<Void> then(@NonNull Task<DriveContents> task) throws Exception { Log.d("RESTORE: ", "open File task"); DriveContents driveContents = task.getResult(); InputStream inputStream = driveContents.getInputStream(); byte[] buf = new byte[8192]; int c = 0; String baseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); String fileName = DatabaseHelper.DATABASE_NAME; Log.d("RESTORE: ", baseDir + "/" +fileName); // Here's the problematic line File f = new File(baseDir+File.pathSeparator+fileName); if(f.canWrite()) { Log.d("RESTORE: ", "File writable"); OutputStream outputStream = new FileOutputStream(f); while ((c = inputStream.read(buf, 0, buf.length)) > 0) { outputStream.write(buf, 0, c); outputStream.flush(); } outputStream.close(); } else { Log.d("RESTORE: ", "File not writable"); } return mDriveResourceClient.discardContents(driveContents); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { } }); } else { Toast.makeText(this, "Backup does not exists", Toast.LENGTH_SHORT).show(); } }
Backup Code
public void startBackup(View view) { final ProgressDialog progressDialog = new ProgressDialog(this); final File currentDB = this.getDatabasePath(DatabaseHelper.DATABASE_NAME); Log.d("DATABASE: ", currentDB.getAbsolutePath()); Log.d("DATABASE: ", currentDB.getName()); progressDialog.setMessage("Backing Up!!!!"); progressDialog.show(); final Task<DriveFolder> appFolderTask = mDriveResourceClient.getAppFolder(); final Task<DriveContents> createContentsTask = mDriveResourceClient.createContents(); Tasks.whenAll(appFolderTask, createContentsTask) .continueWithTask(new Continuation<Void, Task<DriveFile>>() { @Override public Task<DriveFile> then(@NonNull Task<Void> task) throws Exception { DriveFolder parent = appFolderTask.getResult(); DriveContents contents = createContentsTask.getResult(); InputStream inputStream = null; try { inputStream = new FileInputStream(currentDB); } catch (FileNotFoundException e) { e.printStackTrace(); } OutputStream outputStream = contents.getOutputStream(); int c = 0; byte[] buf = new byte[8192]; if (inputStream != null) { while ((c = inputStream.read(buf, 0, buf.length)) > 0) { outputStream.write(buf, 0, c); outputStream.flush(); } outputStream.close(); } else { Toast.makeText(BackupActivity.this, "Some error occurred", Toast.LENGTH_SHORT).show(); } MetadataChangeSet changeSet = new MetadataChangeSet.Builder() .setMimeType("application/x-sqlite3") .setTitle(currentDB.getName()) .build(); return mDriveResourceClient.createFile(parent, changeSet, contents); } }) .addOnSuccessListener(this, new OnSuccessListener<DriveFile>() { @Override public void onSuccess(DriveFile driveFile) { progressDialog.dismiss(); String driveFileID = driveFile.getDriveId().encodeToString(); String dateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date()); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString("dbBackupDriveFileID", driveFileID); editor.putString("lastDbBackupTime", dateTime); editor.apply(); Log.d("DRIVE_FILE", driveFileID); String d = getString(R.string.last_backup) + dateTime; textView.setText(d); Toast.makeText(BackupActivity.this, "Backup Successful. File "+driveFile.getDriveId() .encodeToString(), Toast.LENGTH_LONG).show(); } }) .addOnFailureListener(this, new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { progressDialog.dismiss(); Log.e("DRIVE ", "Unable to create file", e); Toast.makeText(BackupActivity.this, "Unable to backup", Toast.LENGTH_SHORT).show(); } }); }
Root Cause & Fixes
1. Critical Path Separator Error
The biggest issue is you're using the wrong separator to build the file path:
File.pathSeparatoris used to separate multiple paths in a list (e.g.,PATH=/usr/bin:/bin), not to join directory and file names.- You should use
File.separator(which is/on Android) or, even better, use theFileconstructor that handles paths safely.
Fix:
Replace this line:
File f = new File(baseDir+File.pathSeparator+fileName);
With either:
// Option 1: Use File.separator File f = new File(baseDir + File.separator + fileName);
Or (preferred, avoids manual separator handling):
// Option 2: Let File handle path joining File f = new File(baseDir, fileName);
Additionally, make sure the parent directory exists before writing:
// Ensure the Downloads directory exists (it should, but better safe than sorry) if (!f.getParentFile().exists()) { f.getParentFile().mkdirs(); }
2. Android 10+ Scoped Storage Restrictions
Even with the path fixed, you might still face issues on Android 10 (API 29) or higher due to scoped storage:
- Android 10 (API 29): Add
android:requestLegacyExternalStorage="true"to your<application>tag inAndroidManifest.xmlto opt out of scoped storage temporarily. - Android 11+ (API 30): The
WRITE_EXTERNAL_STORAGEpermission no longer grants access to public directories like Downloads. Instead, you have two options:- Use
MediaStoreto write to the Downloads directory (requires handling content URIs instead of file paths). - Switch to app-specific external storage (no extra permissions needed):
Files stored here are tied to your app and will be deleted when the app is uninstalled.String baseDir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
- Use
3. Replace canWrite() with Exception Handling
Checking f.canWrite() isn't reliable—it can return false even if you can create the file (e.g., if the file doesn't exist yet). Instead, try writing directly and catch any IOException to get meaningful error details:
try { OutputStream outputStream = new FileOutputStream(f); while ((c = inputStream.read(buf, 0, buf.length)) > 0) { outputStream.write(buf, 0, c); outputStream.flush(); } outputStream.close(); Log.d("RESTORE: ", "File written successfully"); } catch (IOException e) { Log.e("RESTORE_ERROR", "Failed to write database file", e); Toast.makeText(this, "Restore failed: " + e.getMessage(), Toast.LENGTH_SHORT).show(); }
Final Notes
Start with fixing the path separator issue first—it's almost certainly the immediate cause of your "File not writable" log. After that, adjust your storage approach based on the Android versions you support. Also, don't forget to check that inputStream isn't null before trying to read from it!
内容的提问来源于stack exchange,提问作者Pushkar

