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

Google Drive应用文件夹SQLite数据库恢复文件不可写入问题排查

SQLite Restore Fails with "File not writable" Even After Granting Permissions

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.pathSeparator is 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 the File constructor 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 in AndroidManifest.xml to opt out of scoped storage temporarily.
  • Android 11+ (API 30): The WRITE_EXTERNAL_STORAGE permission no longer grants access to public directories like Downloads. Instead, you have two options:
    • Use MediaStore to write to the Downloads directory (requires handling content URIs instead of file paths).
    • Switch to app-specific external storage (no extra permissions needed):
      String baseDir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
      
      Files stored here are tied to your app and will be deleted when the app is uninstalled.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:53:12