Android跨应用写入外部存储问题:如何向其他应用数据目录写入二进制文件?
兄弟,这个问题我太熟了!本质是Android从API 29(Android 10)开始推行的**分区存储(Scoped Storage)**机制搞的鬼,加上部分厂商(比如三星、一加)提前适配了类似限制,才导致你的旧代码在新设备上失效。我给你拆解下原因,再给你对应不同版本的靠谱解决方案:
一、为啥旧方法不管用了?
- Android 10及以后,系统把每个应用的
Android/data/<<包名>>目录划为私有外部存储区,哪怕你申请了WRITE_EXTERNAL_STORAGE权限,默认也不允许跨应用直接读写这个区域——这是为了提升用户数据安全性。 - 你的小米Pocophone F1是Android 9(API 28),还没强制分区存储,所以旧代码能正常跑;但三星S9、一加5T这类设备要么升级到了Android 10+,要么厂商提前锁了这个权限,就会抛出
Permission denied的异常。
二、分版本适配的正确写法
针对不同Android版本,得用不同的方案来实现写入:
1. Android 11+(API 30及以上)
这时候分区存储是强制开启的,而且requestLegacyExternalStorage属性也失效了,最合规的方式是用**存储访问框架(SAF)**让用户手动选择目标目录,然后通过ContentResolver写入:
步骤1:引导用户选择目标目录
先启动SAF的目录选择界面:
private static final int REQUEST_CODE_PICK_DIR = 1001; // 触发目录选择 private void pickTargetDirectory() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); // 可以设置提示语,引导用户找到目标应用的Android/data/<<包名>>/files目录 intent.putExtra(Intent.EXTRA_TITLE, "请选择目标应用的files目录"); startActivityForResult(intent, REQUEST_CODE_PICK_DIR); }
步骤2:处理选择结果并写入文件
在onActivityResult(或者用Jetpack的Activity Result API)中获取目录Uri,然后写入数据:
@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); // 创建要写入的文件 String targetFileName = "your_binary_file.bin"; DocumentFile parentDir = DocumentFile.fromTreeUri(this, treeUri); DocumentFile targetFile = parentDir.createFile("application/octet-stream", targetFileName); // 写入二进制数据 try (OutputStream os = getContentResolver().openOutputStream(targetFile.getUri())) { os.write(myData); os.flush(); // 这里可以加个成功提示 } catch (IOException e) { e.printStackTrace(); // 处理写入失败的情况 } } }
这种方式虽然需要用户手动选一次目录,但权限可以持久化,后续操作就不用重复选择了,而且完全符合Google的存储规范,不会被Play商店拒审。
2. Android 10(API 29)
这个版本可以用过渡方案,在AndroidManifest.xml的application标签里加个属性,暂时兼容旧的存储逻辑:
<application ... android:requestLegacyExternalStorage="true"> ... </application>
加上这个之后,你的旧代码(FileOutputStream那套)就能在Android 10上正常工作,但这只是临时方案,Android 12之后这个属性就失效了,所以还是建议尽快迁移到SAF方案。
3. Android 9及以下(API 28及以下)
你的旧代码没问题,但要记得动态申请存储权限(Android 6之后危险权限需要动态申请,光在清单里加没用):
private static final int REQUEST_CODE_STORAGE_PERM = 1002; // 检查并申请存储权限 private void checkStoragePermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_STORAGE_PERM); } else { // 权限已授予,执行旧的写入逻辑 writeFileWithOldMethod(); } } // 你的旧写入方法 private void writeFileWithOldMethod() { BufferedOutputStream bos; try { bos = new BufferedOutputStream(new FileOutputStream(filePath + fileName)); bos.write(myData); bos.flush(); bos.close(); } catch (IOException e) { e.printStackTrace(); } }
三、避坑提醒
- 别碰
MANAGE_EXTERNAL_STORAGE权限:这个权限是给系统级文件管理器用的,普通应用申请了也过不了Google Play的审核,完全没必要碰。 - 先检查目标目录是否存在:如果目标应用已经被卸载,它的
Android/data/<<包名>>目录会被系统自动删除,写入前要先判断,避免报错。 - 多设备测试:一定要在Android 10、11、12+的设备上都测一遍,确保兼容性。
内容的提问来源于stack exchange,提问作者ffonz




