Android 12下外部存储文件卸载留存及免弹窗读写方案咨询
Android 12下留存型外部存储文件读写方案(卸载不删除、无重复弹窗)
针对你的需求,以下是两种可行的实现方案,均满足文件在应用卸载后留存、无重复读写弹窗的要求:
方案一:使用MANAGE_EXTERNAL_STORAGE权限(直接操作公共存储目录)
核心逻辑
Android 11+引入的MANAGE_EXTERNAL_STORAGE权限允许应用直接读写公共外部存储的任意目录,权限只需一次性授权(引导用户到设置开启),后续读写无弹窗,且文件会留存至应用卸载后。
实现步骤
- 权限声明:在
AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" /> <!-- 兼容低版本的传统读写权限(Android 10+已失效,可选保留) --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
- 权限检查与引导:判断是否已获取权限,未获取则跳转至设置页面:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (!Environment.isExternalStorageManager()) { Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent); } }
- 文件读写操作:权限获取后,直接在公共存储的自定义目录下创建并读写文件:
// 创建自定义公共存储目录(卸载后不会被删除) File appPublicDir = new File(Environment.getExternalStorageDirectory(), "MyAppPairFiles"); if (!appPublicDir.exists()) { appPublicDir.mkdirs(); } // 写入文本文件 File txtFile = new File(appPublicDir, "f1.txt"); try (FileWriter writer = new FileWriter(txtFile)) { writer.write("测试文本内容"); } catch (IOException e) { e.printStackTrace(); } // 写入关联图片(假设已有Bitmap对象) File imgFile = new File(appPublicDir, "img1.jpg"); try (FileOutputStream fos = new FileOutputStream(imgFile)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos); } catch (IOException e) { e.printStackTrace(); } // 读取文本文件示例 try (BufferedReader reader = new BufferedReader(new FileReader(txtFile))) { String line; while ((line = reader.readLine()) != null) { // 处理读取内容 } } catch (IOException e) { e.printStackTrace(); }
优缺点
- 优点:无需用户手动选择目录,代码风格与传统文件IO一致,开发成本低
- 缺点:属于Google严格管控的特殊权限,非必要场景提交Google Play可能被拒;部分国产ROM可能存在额外权限限制
方案二:使用SAF存储访问框架(持久化目录权限)
核心逻辑
通过SAF让用户手动选择一个存储目录,获取该目录的持久化URI权限,后续读写该目录下的文件无需再次弹窗,文件卸载后留存,符合Google分区存储的推荐规范。
实现步骤
- 启动目录选择器:引导用户选择目标目录,并申请持久化权限:
private static final int REQUEST_CODE_PICK_DIR = 1001; // 触发目录选择 private void pickStorageDir() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); startActivityForResult(intent, REQUEST_CODE_PICK_DIR); }
- 处理选择结果并持久化权限:将选中目录的URI保存到SharedPreferences,方便后续复用:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_PICK_DIR && resultCode == RESULT_OK) { Uri treeUri = data.getData(); // 持久化权限 getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // 保存URI到SharedPreferences SharedPreferences sp = getSharedPreferences("storage_config", MODE_PRIVATE); sp.edit().putString("selected_dir_uri", treeUri.toString()).apply(); } }
- 使用持久化URI读写文件:从SharedPreferences读取URI后,通过
DocumentFile操作文件:
// 读取保存的目录URI SharedPreferences sp = getSharedPreferences("storage_config", MODE_PRIVATE); String uriStr = sp.getString("selected_dir_uri", null); if (uriStr == null) { // 未选择目录,重新触发选择 pickStorageDir(); return; } Uri treeUri = Uri.parse(uriStr); DocumentFile rootDir = DocumentFile.fromTreeUri(this, treeUri); // 创建并写入文本文件 DocumentFile txtFile = rootDir.createFile("text/plain", "f1.txt"); try (OutputStream os = getContentResolver().openOutputStream(txtFile.getUri())) { os.write("测试文本内容".getBytes()); } catch (IOException e) { e.printStackTrace(); } // 创建并写入关联图片 DocumentFile imgFile = rootDir.createFile("image/jpeg", "img1.jpg"); try (OutputStream os = getContentResolver().openOutputStream(imgFile.getUri())) { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, os); } catch (IOException e) { e.printStackTrace(); } // 读取文本文件示例 try (InputStream is = getContentResolver().openInputStream(txtFile.getUri()); BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { String line; while ((line = reader.readLine()) != null) { // 处理读取内容 } } catch (IOException e) { e.printStackTrace(); }
优缺点
- 优点:符合Google分区存储规范,权限审核更容易通过;用户可自主选择存储位置
- 缺点:首次操作需要用户手动选择目录(仅一次弹窗);文件操作需通过ContentResolver,代码相对繁琐
注意事项
- 两种方案创建的文件均会在应用卸载后留存,且不会触发重复读写弹窗
- Android 12中,即使拥有
MANAGE_EXTERNAL_STORAGE权限,也无法访问其他应用的专属外部存储目录 - 若仅需存储媒体文件(如图片),也可通过
MediaStore写入公共媒体目录(如Pictures),但无法与文本文件放在同一自定义目录下
内容的提问来源于stack exchange,提问作者xralf
相关产品推荐
相关产品推荐

