Android 14(UPSIDE_DOWN_CAKE)在10月份正式发布了,又需要进行新一轮的适配了。
每一个新版本的变更中,适配都分为两种,一种是不论开发时是否将targetSdkVersion更改为为最新版,只要App运行在Android 14的手机上都得适配。另一种是开发时将targetSdkVersion更改为最新版本,才需要适配。本文主要介绍适配针对targetSdkVersion升级为34的应用的变更。
针对targetSdkVersion为34的应用
核心功能
必须设置前台服务类型
本节以定位前台服务为例。
1. 必须配置foregroundServiceType
。
所有的前台服务必须配置foregroundServiceType
,可以在AndroidManifest
中通过android:foregroundServiceType
属性配置,也可以在使用ServiceCompat.startForeground()
启动服务时配置。
- 在
AndroidManifest
中配置android:foregroundServiceType
。
1 | xml复制代码<?xml version="1.0" encoding="utf-8"?> |
- 通过
ServiceCompat.startForeground()
配置。
1 | kotlin复制代码class ExampleLocationServices : Service() { |
如果没有在AndroidManifest
中配置android:foregroundServiceType
属性,也没有通过ServiceCompat.startForeground()
方法而是使用Services.startForeground()
方法启动服务,系统会抛出MissingForegroundServiceTypeException
。
2. 必须声明所选前台服务类型对应的权限
必须在AndroidManifest
中声明所选前台服务类型对应的权限,这些权限都是普通权限,声明后默认授予。
- 在
AndroidManifest
中声明location
前台服务权限。
1 | xml复制代码<?xml version="1.0" encoding="utf-8"?> |
如果在没有声明对应权限的情况下启动了前台服务,系统会抛出SecurityException
。
3. 检测运行时权限
不同类型的前台服务可能会使用一些需要权限的API,例如location
前台服务所用的定位API需要定位权限,所以在启动前台服务时务必先获取该类型使用的API所需的权限。
1 | kotlin复制代码<?xml version="1.0" encoding="utf-8"?> |
如果在没有获取到前台服务类型对应的运行时权限的情况下启动了前台服务,系统会抛出SecurityException
。
4. 前台服务类型
可选择的前台服务类型如下:
camera
connectedDevice
dataSync
health
location
mediaPlayback
mediaProjection
microphone
phoneCall
remoteMessaging
shortService
specialUse
systemExempted
可以一次声明一个或者多个组合。如果上述类型不满足需求,建议改为使用WorkManager或JobServices。
蓝牙API权限调整
当targetSdk设置为34时,BluetoothAdapter.getProfileConnectionState()
方法需要BLUETOOTH_CONNECT
权限。
如果要使用BluetoothAdapter.getProfileConnectionState()
方法,确保在AndroidManifest
中声明BLUETOOTH_CONNECT
权限,并在执行BluetoothAdapter.getProfileConnectionState()
方法前检测用户是否授予了BLUETOOTH_CONNECT
权限。
示例代码如下:
- 在
AndroidManifest
中声明BLUETOOTH_CONNECT
权限。
1 | xml复制代码<?xml version="1.0" encoding="utf-8"?> |
- 执行
BluetoothAdapter.getProfileConnectionState()
方法前检测用户是否授予了BLUETOOTH_CONNECT
权限。
1 | kotlin复制代码class TargetSdk14AdapterExampleActivity : AppCompatActivity() { |
如果在未获得BLUETOOTH_CONNECT
权限的情况下直接调用BluetoothAdapter.getProfileConnectionState()
方法,系统会抛出SecurityException
。
JobScheduler
行为变更
- 过长的执行时间会导致ANR。
当targetSdk设置为34时,如果JobServices
的onStartJob
或onStopJob
方法在主线程允许的执行时间内没有结束,系统会触发ANR并显示对应的错误消息。
举个例子,在JobServices
的onStartJob
中模拟耗时操作,代码如下:
1 | kotlin复制代码class ExampleJobServices : JobService() { |
效果如图:
- 配置可执行任务的网路情况的约束条件需要对应权限。
当targetSdk设置为34时,如果没有在AndroidManifest
中声明ACCESS_NETWORK_STATE
权限,使用JobInfo.Builder.setRequiredNetworkType
或JobInfo.Builder.setRequiredNetwork
配置可执行任务的网路情况的约束条件时,系统会抛出SecurityException
。
安全
限制隐式Intent
和PendingIntent
当targetSdk设置为34时,通过隐式Intent
或隐式Intent
创建的PendingIntent
只能打开设置了android:exported="true"
的组件,如果android:exported
属性值为false
,系统会抛出异常。
举个例子,通过隐式Intent
打开ExampleIntentActivity
,代码如下:
AndroidManifest
1 | xml复制代码<?xml version="1.0" encoding="utf-8"?> |
示例页面
1 | kotlin复制代码class TargetSdk14AdapterExampleActivity : AppCompatActivity() { |
通过Intent("com.chenyihong.exampledemo.EXAMPLE_INTENT")
打开ExampleIntentActivity
时,系统抛出如下异常:
如果需要打开android:exported="false"
的Activity
,改为使用显示Intent
,改动代码如下:
1 | kotlin复制代码class TargetSdk14AdapterExampleActivity : AppCompatActivity() { |
效果如图:
通过Context
注册的广播接收者必须设置是否导出
当targetSdk设置为34时,通过Context
注册接收自定义广播的广播接收者时必须设置是否导出。通过Context
注册接收系统广播的广播接收者时不用设置是否导出。
举个例子,注册一个接收自定义Action
的广播接收者
1 | kotlin复制代码class TargetSdk14AdapterExampleActivity : AppCompatActivity() { |
系统会抛出SecurityException
异常,如图:
正确注册方式代码如下:
1 | kotlin复制代码class TargetSdk14AdapterExampleActivity : AppCompatActivity() { |
动态加载代码方式调整
当targetSdk设置为34时,必须将所有动态加载的文件标记为只读,否则系统会抛出异常。
限制在后台启动Activity
当targetSdk设置为34时,系统新增了对从后台启动Activity
行为的限制。
- 尝试使用
PendingIntent
从后台打开Activity
时,必须设置setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
。
1 | kotlin复制代码val activityOptions = ActivityOptions.makeBasic().apply { |
- 当一个可见应用使用
bindService()
绑定另一个应用在后台运行的服务时,如果想要将后台启动Activity
的权限授予被绑定服务,需要在bindService()
方法中传入BIND_ALLOW_ACTIVITY_STARTS
。
1 | scss复制代码bindService(serviceIntent, serviceConnection, Context.BIND_ALLOW_ACTIVITY_STARTS); |
压缩路径遍历漏洞优化
当targetSdk设置为34时,为了防止Zip路径遍历的漏洞,使用ZipFile(String)
或ZipInputStream.getNextEntry()
时,如果路径以”..”或者”/“开头,系统会抛出ZipException
。
测试了一下,在Assets中存放一个test.zip文件然后将其移动到内部存储空间中并命名为testZipFile.zip,通过testZipFile.zip的相对路径来实例化ZipFile
或ZipInputStream
,代码如下:
1 | kotlin复制代码class TargetSdk14AdapterExampleActivity : AppCompatActivity() { |
testZipFile.zip的相对路径为:
1 | bash复制代码/storage/emulated/0/Android/data/com.chenyihong.exampledemo/files/Download/com.chenyihong.exampledemo/testZipFile.zip |
结果App并不会崩溃。
MediaProjection行为变更
当targetSdk设置为34时,使用MediaProjection
进行屏幕捕获或录制时,下列情况会导致系统抛出异常。
- 调用
MediaProjection.createVirtualDisplay()
前不注册MediaProjection.Callback
回调,示例代码如下:
1 | kotlin复制代码// 错误代码 |
执行错误代码,系统会抛出IllegalStateException
,如下:
- 使用同一个
MediaProjection
对象多次调用createVirtualDisplay
方法,示例代码如下:
1 | kotlin复制代码class TargetSdk14AdapterExampleActivity : AppCompatActivity() { |
判断MediaProjection
对象为空则创建,不为空就重复使用该对象。系统会抛出SecurityException
,如下:
通常来说,这种方式可以避免重复创建对象,是不错的做法。但是,为了避免App在仅获得用户一次同意之后,就无限制的使用同一个MediaProjection
来获取屏幕内容,系统限制了通过一个MediaProjection
只能调用一次createVirtualDisplay
。
本文转载自: 掘金