将文件存储在android应用程序(目标为API 31及以上版本)中,即使在卸载应用程序后,该应用程序也会保留



此问题专门针对API 31及以上版本的应用程序。

我参考了许多类似的StackOverflow问题、官方文档等。
这里提到的API 31有一些限制-文档。

用例

将JSON文件写入设备的外部存储,这样即使卸载了应用程序,数据也不会丢失

如何针对API 31及以上版本的应用程序实现这一目标

我当前的代码将文件保存到使用context.getExternalFilesDir(null)获得的应用程序特定存储目录中。

问题是在卸载应用程序时未保留该文件。

任何解释确切场景的博客或代码实验室也可以。

备注

  1. 意识到当我们将文件存储在外部存储目录中时,用户可以从其他应用程序中查看、修改或删除文件-这很好
  2. 设备根目录、Documents或任何其他位置都可以,因为所有设备都可以以相同的方式访问文件

您的应用程序可以在公共文档或下载目录和子目录中存储任意数量的文件,而无需任何用户交互。所有这些都具有经典的文件方式。

在重新安装时,您可以使用ACTION_OPEN_DOCUMENT_TREE让用户选择使用的子文件夹。

感谢@CommonsWare对我的问题的评论,我了解了如何实现它。

我的用例是创建、读取和编写一个JSON文件。

我使用的是Jetpack Compose,所以共享的代码是针对Compose的。

可组合代码,

val JSON_MIMETYPE = "application/json"
val createDocument = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument(
mimeType = JSON_MIMETYPE,
),
) { uri ->
uri?.let {
viewModel.backupDataToDocument(
uri = it,
)
}
}
val openDocument = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
) { uri ->
uri?.let {
viewModel.restoreDataFromDocument(
uri = it,
)
}
}

ViewModel代码,

fun backupDataToDocument(
uri: Uri,
) {
viewModelScope.launch(
context = Dispatchers.IO,
) {
// Create a "databaseBackupData" custom modal class to write data to the JSON file.
jsonUtil.writeDatabaseBackupDataToFile(
uri = uri,
databaseBackupData = it,
)
}
}
fun restoreDataFromDocument(
uri: Uri,
) {
viewModelScope.launch(
context = Dispatchers.IO,
) {
val databaseBackupData = jsonUtil.readDatabaseBackupDataFromFile(
uri = uri,
)
// Use the fetched data as required
}
}

JsonUtil

private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val databaseBackupDataJsonAdapter: JsonAdapter<DatabaseBackupData> = moshi.adapter(DatabaseBackupData::class.java)
class JsonUtil @Inject constructor(
@ApplicationContext private val context: Context,
) {
fun readDatabaseBackupDataFromFile(
uri: Uri,
): DatabaseBackupData? {
val contentResolver = context.contentResolver
val stringBuilder = StringBuilder()
contentResolver.openInputStream(uri)?.use { inputStream ->
BufferedReader(InputStreamReader(inputStream)).use { bufferedReader ->
var line: String? = bufferedReader.readLine()
while (line != null) {
stringBuilder.append(line)
line = bufferedReader.readLine()
}
}
}
return databaseBackupDataJsonAdapter.fromJson(stringBuilder.toString())
}
fun writeDatabaseBackupDataToFile(
uri: Uri,
databaseBackupData: DatabaseBackupData,
) {
val jsonString = databaseBackupDataJsonAdapter.toJson(databaseBackupData)
writeJsonToFile(
uri = uri,
jsonString = jsonString,
)
}
private fun writeJsonToFile(
uri: Uri,
jsonString: String,
) {
val contentResolver = context.contentResolver
try {
contentResolver.openFileDescriptor(uri, "w")?.use {
FileOutputStream(it.fileDescriptor).use { fileOutputStream ->
fileOutputStream.write(jsonString.toByteArray())
}
}
} catch (fileNotFoundException: FileNotFoundException) {
fileNotFoundException.printStackTrace()
} catch (ioException: IOException) {
ioException.printStackTrace()
}
}
}

首先,如果你问是否有方法跳过用户手动选择,答案是肯定的和否定的,如果你从用户那里获得了MANAGE_EXTERNAL_STORAGE权限,则是肯定的,但由于这是一个敏感而重要的权限,如果你的应用程序不是文件管理器、防病毒或其他真正需要此权限的应用程序,谷歌将拒绝将其发布在谷歌播放上。阅读此页了解更多信息如果不使用MANAGE_EXTERNAL_STORAGE权限,则答案为否。

你应该使用存储访问框架,它不需要任何许可,我会告诉你如何实现它:

假设您已经将JSON保存到一个文本文件中,以下是从中恢复数据的步骤:

1-我们需要一种向用户显示对话框的方法,这样他/她就可以选择保存的文件:

private void openFile() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/*");
someActivityResultLauncher.launch(intent);
}

2-我们应该实现onActivityResult来获取用户选择的文件的uri,所以将someActivityResultLauncher添加到活动类:

ActivityResultLauncher<Intent> someActivityResultLauncher;

3-将以下方法添加到活动类:

private void registerActivityResult()
{
someActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Intent data = result.getData();
Uri uri = data.getData();
// Perform operations on the document using its URI.
try {
String txt = readTextFromUri(Setting_Activity.this, uri);
dorestore(txt);
}catch (Exception e){}
}
}
});
}

4-在活动的onCreate((方法中调用上述方法:

@Override
protected void onCreate(Bundle savedInstanceState) {
//......   
registerActivityResult();
//.....
}

5-实现我们在步骤3:中使用的readTextFromUri((方法

public static String readTextFromUri(Context context, Uri uri) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
try (InputStream inputStream =
context.getContentResolver().openInputStream(uri);
BufferedReader reader = new BufferedReader(
new InputStreamReader(Objects.requireNonNull(inputStream)))) {
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
}
return stringBuilder.toString();
}

仅此而已,只需实现我们在步骤3中使用的dorestore((方法即可恢复json数据。在我的情况下,它看起来像这样:

private void dorestore(String data)
{
ArrayList<dbObject_user> messages = new ArrayList<>();
try {
JSONArray jsonArray = new JSONArray(data);
//....
//parsing json and saving it to app db....
//....
}
catch (Exception e)
{}
}

只需在步骤1中调用openFile((。仅此而已。

最新更新