8

AndroidQ适配手册

 3 years ago
source link: https://yutiantina.github.io/2019/09/04/AndroidQ%E9%80%82%E9%85%8D%E6%89%8B%E5%86%8C/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Android预计在今年Q3发布androidQ的最终版本, 8月份Android发布了Beta6版本, 距离Final Release也不远了, 针对Q的适配已经迫在眉睫!

针对Q的隐私权和行为变更, 我们主要焦点还是着重于, 针对于所有应用(不论targetSdkVersion是否为Q的应用)都有影响的变更上来看

Q 针对所有应用隐私权和行为变更

在当前的Beta5版本内, 分区存储的只应用在部分应用上, 但是, 明年无论是所有应用均需要分区存储, 所以应用需要提前确保支持分区存储.

默认情况下, 都需要通过Context.getExternalFilesDir())来获取过滤视图, 如果存在文件应用卸载时不应该删除的, 应该通过MediaProvider存储在共享集合内(如拍照的图片), 同时, 应用请求过滤视图内的文件, 不再需要请求对应的读写权限.而访问外部存储文件, 如 /sdcard/DCIM/IMG1024.JPG 等路径文件,应用必须使用 MediaStore,并调用 openFile()) 等方法, 同时需要请求READ_EXTERNAL_STORAGE权限才可访问

具体适配需要参考官网的建议

针对后台启动Activity的限制

在未与用户进行交互的前提下启动Activity存在该条限制, 如果存在该场景的应用(比如闹钟, 语音, 视频电话等), 需要通过通知提醒的方式解决

增加针对后台定位的权限

AndroidQ引入了新的权限ACCESS_BACKGROUND_LOCATION, 用来授予是否允许后台定位, 在低于Q的应用版本上, 如果原来的清单中申请了定位权限, 会自动加上ACCESS_BACKGROUND_LOCATION权限, 但是用户仍然可以通过拒绝授权, 导致后台定位失败. 这个限制主要出现在存在导航或者智能家居操作的应用中. 我们的应用中可以不做处理

设备唯一标识符

三方引用既无法通过READ_PHONE_STATE老权限, 无法通过申请READ_PRIVILEGED_PHONE_STATE新权限(需要系统签名)来读取deviceId, 依赖于deviceId数据上报的接口需要额外适配, Android提供推荐做法, 但是它允许用户重置标识符, 需要根据具体应用场景设计唯一标识符.另外通过WifiInfo.getMacAddress()) 获取Mac地址的将只能获取固定的值02:00:00:00:00:00

相机和网络连接的变更

访问所有相机信息均需要获取权限, AndroidQ更改了 getCameraCharacteristics()) 方法默认返回的信息的广度。具体而言,应用必须具有 CAMERA 权限才能访问此方法的返回值中可能包含的设备特定元数据。

非SDK接口限制

从android P开始做了非SDK接口的限制, Q版本更新了对应的非SDK接口列表, 需要测试并根据接口的不同受限情况进行相对适配

targetSdkVersion限制

当前最低目标版本需要保证在23以上

分区存储兼容性处理

为了改进当前Android手机文件夹混乱的现象, 在Android Q, Google出了分区存储的政策, 开发者将无法通过Environment.getExternalStorageState()访问文件.

存储空间特性

所有Android设备都存在两个文件存储空间: 内部存储和外部存储.在Android早期, 内部存储代表的是内置的存储器, 而外部存储表示可移动的存储介质(譬如sd卡), 现在大部分设备无论是否存在可移动存储介质, 这两个存储空间都会存在. 而无论外部存储是否可移动, 在其API行为上, 是没有任何区别的, 当然我们可以通过以下代码去判断外部存储是否支持去读写

1
2
3
fun isExternalStorageWritable(): Boolean {
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}

在Android Q之前的手机上, 对于外部存储的文件的读写没有任何限制, 在获取外部存储的相关读写权限后, 就可以随意新建目录, 导致文件目录非常混乱

根据下图, 我们可以看到在Android Q启动了分区存储的功能后, 外部访问范围将仅限定于三个区域, 分别是过滤视图(app-specific), 共享媒体区域, 以及下载文件区域

存储空间P和Q的区别

首先, 我们可以看下这三个区域的区别

文件位置 需要的权限 访问方式 当应用卸载的时候, 是否会删除文件 过滤视图(App-specific directory) 无 getExternalFilesDir 是 媒体资源集合(照片, 视频, 语音) READ_EXTERNAL_STORAGE(当访问其他应用的文件时) MediaStore 否 下载文件 无 SAF(加载系统的文件管理器) 否

通过哪些API可以访问到过滤视图?

  • Context.getExternalFilesDir()
    • e.g. /sdcard/Android/data/packageName/files
  • Context.getExternalCacheDirs()
    • e.g. /sdcard/Android/data/packageName/cache
  • Context.getExternalMediaDirs()
    • e.g. /sdcard/Android/media/packageName/
  • Context.getObbDirs()
    • e.g. /sdcard/Android/obb/packageName/

在Android P中, 应用可以通过READ_EXTERNAL_STORAGE权限访问外部存储中所有路径文件, 而针对于过滤视图(原先叫共享存储空间, Shared Storage), 是可以不通过权限就可以直接访问读写的.

在Android Q中, 应用只可以直接访问应用自身的过滤视图, 而共享媒体资源仅可通过MediaStore来访问, 同时, 其他文件需要通过SAF进行访问, 假设直接访问过滤视图目录外的目录文件, 则会抛出异常. 这样使得文件存储更加规范, 也使文件访问变得更为安全.

MediaStore

MediaStore在Android 1就已经存在, 在Android Q 中进行了加强. 他主要用于存储用户行为生成的媒体资源文件(且这些资源文件应是应用卸载后用户仍然希望保存的文件), 当我们需要访问媒体文件的时候, 我们需要达成两个条件:

  1. 拥有READ_EXTERNAL_STORAGE权限
  2. 对应访问文件位于以下明确定义的媒体集合中
MediaStore Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
val resolver = context.getContentResolver()

// 打开指定的媒体列
resolver.openFileDescriptor(item, mode).use { pfd ->
// todo something
}

// 加载缩略图
val mediaThumbnail = resolver.loadThumbnail(item, Size(640, 480), null)

// 查找所有视频资源
// 包括被标记独占访问的资源(被IS_PENDING标记的资源)
val collection = MediaStore.Video.Media.getContentUri(volumeName)
val collectionWithPending = MediaStore.setIncludePending(collection)
resolver.query(collectionWithPending, null, null, null).use { c ->
// ...
}

// 将指定的媒体文件发布到外部存储空间内
val values = ContentValues().apply {
put(MediaStore.Audio.Media.RELATIVE_PATH, "Video/My Videos")
put(MediaStore.Audio.Media.DISPLAY_NAME, "My Video.mp4")
}
val item = resolver.insert(collection, values)

这里需要注意的是, 在Q版本新增了两个标记

  1. IS_PENDING标记用来表示标记应用具有媒体访问独占权
  2. RELATIVE_PATH 自定义指定相对路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
val values = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG")
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
put(MediaStore.Images.Media.IS_PENDING, 1)
}

// 通知指定资源文件发布在外部存储空间内
val resolver = context.getContentResolver()
val collection = MediaStore.Images.Media
.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val item = resolver.insert(collection, values)

resolver.openFileDescriptor(item, "w", null).use { pfd ->
// 写文件
FileOutputStream(pfd.fileDescriptor).write(byte[])
}

// 当我们文件已写入资源集合, 则将IS_PENDING改为0, 表示释放媒体访问独占权
// 允许其他应用访问这个资源
values.clear()
values.put(MediaStore.Images.Media.IS_PENDING, 0)
resolver.update(item, values, null, null)

另外, 在Android Q中, DATA标记已经被废弃, 在Q之前, 我们可以通过它去获取文件的绝对路径用来访问文件, 在Q乃至之后版本, 我们只能通过ContentResolver#openFileDescriptor(Uri, String)去访问对应的文件

编辑其他应用的媒体文件

理论上, 我们无法去编辑(写, 删除操作)其他应用提供给MediaStore的媒体文件, 当我们去编辑的时候, 会抛出一个RecoverableSecurityException异常, 我们可以通过捕获这个异常, 并请求用户授权针对该文件进行写操作

1
2
3
4
5
6
7
8
try {
// ...
} catch (rse: RecoverableSecurityException) {
val requestAccessIntentSender = rse.userAction.actionIntent.intentSender

startIntentSenderForResult(requestAccessIntentSender, requestCode,
null, 0, 0, 0, null)
}

相关Demo可以看下PhotoQSelector


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK