AndroidQ沙盒机制之分区存储适配
为了让用户更好地控制自己的文件,Android Q更改了应用访问设备外部存储空间中文件的方式。Android Q用更精细的媒体特定权限来替换READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限,并且无需特定权限,应用即可访问自己在外部存储设备的文件。
1、针对应用私有文件的隔离存储沙盒
对于每个应用,Android Q 都会创建一个“隔离存储沙盒”,以限制其他应用访问本应用在外部存储设备的文件。常见的外部存储设备是/sdcard。此定义具有两个优点:
①、需要的权限更少。 应用沙盒中的文件是您应用的私有文件。因此,您不再需要任何权限即可在外部存储设备中访问和保存自己的文件;
②、相对于设备上的其他应用,隐私性更强。 任何其他应用都无法直接访问您应用的隔离存储沙盒中的文件。借助此访问权限限制,您的应用可以更轻松地维护沙盒文件的隐私性;
在外部存储设备存储文件的最佳位置是Context.getExternalFilesDir()返回文件所在的位置,因此此位置的行为方式在所有Android版本中都保持一致。使用此方法时,需要在媒体环境中传递我们要创建或打开的文件类型对应的文件。例如,要保存或访问应用私有图片,请调用Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)。
2、媒体文件的共享集合
如果我们的应用创建了属于相应用户的文件,并希望卸载该应用时保留此用户,则将这些文件保存在某个通用媒体集合(共享集合)中。共享集合包括:照片、音频、视频和下载内容。
3、查看其它应用的文件所需权限
我们的应用无需请求任何权限,即可在这些共享集合中创建和修改自己的文件。但是,我们的应用要创建或修改其他应用已创建的文件,则必须先请求相应权限:
①、访问照片和视频共享集合中其他应用的文件时,需要 READ_MEDIA_IMAGES 或 READ_MEDIA_VIDEO 权限(具体取决于您的应用需要访问的文件类型);
②、访问音乐共享集合中其他应用的文件时,需要 READ_MEDIA_AUDIO 权限;
4、访问共享集合
在请求必要的权限后,我们的应用可以使用MediaStore API访问这些集合:
①、对于照片和视频共享集合,请使用 MediaStore.Images 或 MediaStore.Video;
②、对于音乐共享集合,请使用 MediaStore.Audio;
③、对于下载内容共享集合,请使用 MediaStore.Downloads;
要在原生代码中访问媒体文件,请使用基于Java或kotlin代码的MediaStore来检索相应文件,然后对相应文件描述符传递到原生代码。详情请参考从原生代码访问媒体文件部分。
5、保留应用在共享集合的文件
默认情况下,在用户卸载应用时,Android Q会清理保存在沙盒的文件。要在卸载应用时保留这些文件,请使用存储访问框架存储访问框架,或将文件保存在共享集合中。要保留共享集合的文件,请在相关的MediaStore集合中新插一行,并使用以下方法:
①、至少应为 DISPLAY_NAME 和 MIME_TYPE 列提供值;
②、(可选)您可以使用 PRIMARY_DIRECTORY 和 SECONDARY_DIRECTORY 列来影响文件在磁盘上的存储位置;
③、保留 DATA 列不定义。这样一来,平台便可以灵活地将文件保留在沙盒之外;
插入此行后,我们可以使用ContentResolver.openFileDescriptor() 这个API向文件读取或写入数据。
6、访问照片的位置信息
一些照片在Exif元数据中包含位置信息,以便用户查看照片的拍摄地点。由于此位置信息非常敏感,因此默认情况下,Android Q会对此位置信息进行隐藏。如果我们的应用需要访问照片的位置信息,需要调用以下方法:
①、将新的 ACCESS_MEDIA_LOCATION 权限添加到您应用的清单中;
②、在 MediaStore 对象中,调用 setRequireOriginal() 并传入照片的 URI;
Java示例代码如下:
Uri photoUri = Uri.withAppendedPath( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getString(idColumnIndex)); final double[] latLong; if (BuildCompat.isAtLeastQ()) { // When running Android Q, get location data from `ExifInterface`. photoUri = MediaStore.setRequireOriginal(photoUri); InputStream stream = getContentResolver().openInputStream(photoUri); if (stream != null) { ExifInterface exifInterface = new ExifInterface(stream); double[] returnedLatLong = exifInterface.getLatLong(); // If lat/long is null, fall back to the coordinates (0, 0). latLong = returnedLatLong != null ? returnedLatLong : new double[2]; // Don't reuse the stream associated with {@code ExifInterface}. stream.close(); } else { // Failed to load the stream, so return the coordinates (0, 0). latLong = new double[2]; } } else { // On devices running Android 9 (API level 28) and lower, use the // media store columns. latLong = new double[]{ cursor.getFloat(latitudeColumnIndex), cursor.getFloat(longitudeColumnIndex) }; }
kotlin示例代码如下:
val latLong = if (BuildCompat.isAtLeastQ()) { // When running Android Q, get location data from `ExifInterface`. photoUri = MediaStore.setRequireOriginal(photoUri) contentResolver.openInputStream(photoUri).use { stream -> ExifInterface(stream).run { // If lat/long is null, fall back to the coordinates (0, 0). latLong ?: doubleArrayOf(0.0, 0.0) } } } else { // On devices running Android 9 (API level 28) and lower, use the // media store columns. doubleArrayOf( cursor.getFloat(latitudeColumnIndex).toDouble(), cursor.getFloat(longitudeColumnIndex).toDouble() ) }
7、访问其他应用创建的文件
要访问其他应用已保存在外部存储设备的媒体文件,需要以下步骤:
①、根据包含您要访问的文件的共享集合请求必要的权限;
②、使用 ContentResolver 对象查找并打开该文件;
8、向其他应用创建的文件写入数据
通过将文件保存在共享集合,我们的应用成为该文件的所有者。通常情况下,只有是共享集合的某个文件所有者时,我们的应用才可以向文件写入数据。不过,如果我们的应用是用户默认的应用,我们可以向其他应用的文件写入数据:
①、如果您的应用是用户的默认照片管理器应用,则可以修改其他应用保存到照片和视频共享集合中的图片文件;
②、如果您的应用是用户的默认音乐应用,则可以修改其他应用保存到音乐共享集合中的音频文件;
要修改其他应用保存在存储设备的媒体文件,需要使用ContentResolver找到相应文件来修改。
9、标识特定的外部存储设备
在Android 9及以下版本,所有存储设备上的所有文件都会显示单个"external"卷名称。而Android Q为每个外部存储设备提供唯一的卷名称。此命名系统可以帮助我们高效整理内容并且加入索引,还可以控制存储内容的位置。要唯一标识外部存储设备的特定文件,我们需要使用卷名称和ID。例如,主存储设备的文件是content://media/external/images/media/12,而命名为FA23-3E92辅助存储设备对应文件是content://media/FA23-3E92/images/media/12。
10、获取外部存储列表
要获取所有当前可用卷的名称列表,请调用 MediaStore.getAllVolumeNames(),如以下代码段所示:
Set<String> volumeNames = MediaStore.getAllVolumeNames(context);
上一篇:springboot打包部署到linux服务器的方法
栏 目:JAVA代码
下一篇:java中利用List的subList方法实现对List分页(简单易学)
本文标题:AndroidQ沙盒机制之分区存储适配
本文地址:http://www.codeinn.net/misctech/45327.html
阅读排行
- 1Java Swing组件BoxLayout布局用法示例
- 2java中-jar 与nohup的对比
- 3Java邮件发送程序(可以同时发给多个地址、可以带附件)
- 4Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.Type异常
- 5Java中自定义异常详解及实例代码
- 6深入理解Java中的克隆
- 7java读取excel文件的两种方法
- 8解析SpringSecurity+JWT认证流程实现
- 9spring boot里增加表单验证hibernate-validator并在freemarker模板里显示错误信息(推荐)
- 10深入解析java虚拟机