Android 10(Api 29)新特性适配 – 分区存储
官方文档:https://developer.android.google.cn/preview/privacy/scoped-storage
问题描述
从Android 10开始应用将不可直接访问外部存储(/sdcard
)文件,否则抛异常。
在AndroidQ上运行:
targetSdkVersion<Q
,没影响;targetSdkVersion>=Q
,默认启用过滤视图,应用以外的文件需要通过存储访问框架(SAF
,StorageAccessFramework
)读写。
解决方法
停用过滤视图,使用旧版存储模式
<manifest ... >
<!-- This attribute is "false" by default on apps targeting Android Q. -->
<application android:requestLegacyExternalStorage="true" ... >
...
</application>
</manifest>
将文件存储到过滤视图中,官方推荐。
// /Android/data/com.example.androidq/files/Documents
File dir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
- 优点:不用申请读写权限
- 缺点:随应用卸载而删除
使用存储访问框架(SAF),由用户指定要读写的文件。
此功能Android 4.4(API: 19)就有,官方文档:https://developer.android.google.cn/guide/topics/providers/document-provider
获取用户指定的某个目录的读写权限
从Android 5.0(Api 21)开始就有,官方文档:https://developer.android.google.cn/about/versions/android-5.0#Storage
申请目录的访问权限
会打开系统的文件目录,由用户自己选择允许访问的目录,不用申请WRITE/READ_EXTERNAL_STORAGE
权限。
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
startActivityForResult(intent, REQ_CODE);
执行上述代码后会出现类似如下图界面,点击‘允许访问“DuoKan”’按钮
允许之后通过onActivityResult()
的intent.getData()
得到该目录的Uri,通过Uri可获取子目录和文件。这种方式的缺点是应用重装后权限失效,即使保存这个Uri也没用。
Uri dirUri = intent.getData();
// 持久化;应用重装后权限失效,即使知道这个uri也没用
SPUtil.setValue(this, SP_DOC_KEY, dirUri.toString());
//重要:少这行代码手机重启后会失去权限
getContentResolver().takePersistableUriPermission(dirUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
通过Uri读写文件
(1)创建文件
// 在mUri目录(‘DuoKan’目录)下创建'test.txt'文件
private void createFile() {
DocumentFile documentFile = DocumentFile.fromTreeUri(this, mUri);
DocumentFile file = documentFile.createFile("text/plain", "test.txt");
if (file != null && file.exists()) {
LogUtil.log(file.getName() + " created");
}
}
主要用到DocumentFile类,和File类的方法类似,有isFile
、isDirectory
、exists
、listFiles
等方法
(2)删除文件
//删除"test.txt"
private void deleteFile() {
DocumentFile documentFile = DocumentFile.fromTreeUri(this, mUri);
// listFiles(),列出所有的子文件和文件夹
for (DocumentFile file : documentFile.listFiles()) {
if (file.isFile() && "test.txt".equals(file.getName())) {
boolean delete = file.delete();
LogUtil.log("deleteFile: " + delete);
break;
}
}
}
(3)写入数据
private void writeFile(Uri uri) {
try {
ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "w");
//这种方法会覆盖原来文件内容
OutputStreamWriter output =
new OutputStreamWriter(new FileOutputStream(pfd.getFileDescriptor()));
// 不能传uri.toString(),否则FileNotFoundException
// OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(uri.toString(), true));
output.write("这是一段文件写入测试\n");
output.close();
LogUtil.log("写入成功。");
} catch (IOException e) {
LogUtil.log(e);
}
}
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/18/android-10-api-29-new-feature-adaptation-partitioned-storage/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论