前言
最近一直在致力于AndroidQ的适配。截至今日,AndroidQ分区存储适配已完成。过程中遇到了很多坑。当前互联网上的大多数帖子都概述了这些变化。接下来的几篇文章是关于分区存储的。特此记录一下实际经验代码和填坑经验的总结,为大家提供帮助。
本文主要是AndroidQ(10)分区存储适配的具体实现
- 积分:
- Android Q 文件存储机制已修改为沙盒模式
- APP只能访问自己目录下的文件和公共媒体文件
- Android Q及以下版本,仍使用旧的文件存储方式
这里注意:适配Android Q时,还必须兼容Q系统版本及以下,使用SDK_VERSION来区分
背景
存储权限
Android Q 仍然使用 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 作为存储相关的运行时权限,但现在即使获得这些权限,对外部存储的访问也受到限制,只能访问自己目录下的文件和公共内部文件。
外部存储结构划分
公共目录:下载、文档、图片、DCIM、电影、音乐、铃声等
地址:/storage/emulated/0/下载(图片)等
公共目录中的文件在卸载APP时不会被删除。
APP私人目录
地址:/storage/emulated/0/Android/data/包名/文件
私有目录存放应用程序的私有文件,卸载应用程序时会被删除。
适应指导
在AndroidQ中使用ContentResolver来添加、删除、修改和检查文件
1。获取(创建)自己目录中的文件夹
获取并创建,如果手机中没有对应的文件夹,系统会自动生成
//在自己的目录下创建apk文件夹文件 apkFile = context.getExternalFilesDir("apk");
2。在自己的目录中创建文件
生成要下载的路径,通过输入输出流进行读写
String apkFilePath = context.getExternalFilesDir("apk").getAbsolutePath(); File newFile = new File(apkFilePath + File.separator + "temp.apk"); 输出流 os = null; 尝试 { os = new FileOutputStream(newFile); if (os != null) { os.write("文件已创建".getBytes(StandardCharsets.UTF_8)); os.flush(); } } catch (IOException e) { } 最后 { 尝试 { if (os != null) { os.close(); } } catch (IOException e1) { } }
3。在公共目录下创建文件夹
通过 MediaStore.insert 写入
如果(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){ 返回空值; } ContentResolver 解析器 = context.getContentResolver(); ContentValues值= new ContentValues(); value.put(MediaStore.Downloads.DISPLAY_NAME,文件名); value.put(MediaStore.Downloads.DESCRIPTION,文件名); //设置文件类型value.put(MediaStore.Downloads.MIME_TYPE,"application/vnd.android.package-archive"); //注意MediaStore.Downloads.RELATIVE_PATH需要targetVersion=29, //所以该方法只能在Android10手机上执行 value.put(MediaStore.Downloads.RELATIVE_PATH,"下载" + File.separator + "apk"); Uri 外部 = MediaStore.Downloads.EXTERNAL_CONTENT_URI; Uri insertUri =solver.insert(external,values); 返回插入Uri;
4。在公共目录下指定文件夹中创建文件
结合上面的代码,我们主要是在public目录下创建文件或者文件夹来获取本地路径uri。不同的Uri可以保存在不同的公共目录中。接下来就可以使用输入输出流来写入文件了
重要:AndroidQ不支持file://类型访问文件,只能通过uri访问
ContentResolver 解析器 = context.getContentResolver(); Uri insertUri =solver.insert(external,values); if(insertUri == null) { 返回; } String mFilePath = insertUri.toString(); 输入流 = null; 输出流 os = null; 尝试 { os =solver.openOutputStream(insertUri); 如果(操作系统==空){ 返回; } 整型读取; 文件sourceFile = new File(sourcePath); if (sourceFile.exists()) { // 当文件存在时是 = new FileInputStream(sourceFile); // 读取原始文件 字节[]缓冲区=新字节[1024]; while ((read = www.sxzhongrui.com(buffer)) != -1) { os.write(缓冲区,读取); } } } catch (异常 e) { e.printStackTrace(); }最后 { 尝试 { 如果(是!=空){ is.close(); } if (os != null) { os.close(); } } catch (IOException e) { e.printStackTrace(); } }
5。通过MediaStore读取public目录下的文件
ParcelFileDescriptor ParcelFileDescriptor = null; 文件描述符 文件描述符 = null; 位图 tagBitmap = null; 尝试 { ParcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri,"r"); if (parcelFileDescriptor!= null && ParcelFileDescriptor.getFileDescriptor() != null) { fileDescriptor = ParcelFileDescriptor.getFileDescriptor(); //将uri转换为bitmap类型 tagBitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor); } } catch (FileNotFoundException e) { e.printStackTrace();} catch (IOException e) { e.printStackTrace(); } 最后 { 尝试 { if (parcelFileDescriptor!= null) { ParcelFileDescriptor.close(); } } catch (IOException e) { } }
6。使用MediaStore删除文件
context.getContentResolver().delete(fileUri,null,null);
7。 APP通过MediaStore访问文件所需权限
标题 1 | 不允许 | READ_EXTERNAL |
---|---|---|
音频 | 可以读写APP本身创建的文件,但不能直接使用路径访问 | 您可以阅读其他APP创建的媒体文件。删除、修改操作需要用户授权 |
图片 | 可以读写APP本身创建的文件,但不能直接使用路径访问 | 您可以阅读其他APP创建的媒体文件。删除、修改操作需要用户授权 |
文件 | 可以读写APP本身创建的文件,但不能直接使用路径访问 | 无法读取或写入其他应用程序创建的非媒体文件 |
下载 | 可以读写APP本身创建的文件,但不能直接使用路径访问 | 无法读取或写入其他应用程序创建的非媒体文件 |
后续我们会介绍AndroidQ存储的具体功能,敬请关注~