本篇是适配指南系列第二篇,影响以Android 15为目标平台应用的行为变更。
注意:Android 15应用适配指南系列文章,均以小米14手机搭建Android 15为基准适配。
一、影响以Android 15为目标平台应用的行为变更
1、新的媒体处理前台服务类型
1.1、特性背景
Android 15继续完善前台服务的类型机制。
1.2、适用范围
targetSDK≥Android 15
1.3、特性内容
Android 15引入了一种新的前台服务类型,即mediaProcessing。这种服务类型适用于像转码媒体文件这样的操作。例如,媒体应用程序可能会下载音频文件并需要在播放之前将其转换为不同的格式。您可以使用mediaProcessing前台服务来确保即使应用程序在后台运行,转换也可以继续进行。
该系统允许一个应用的媒体处理服务在24小时内运行总共6个小时,之后系统会调用正在运行的服务的Service.onTimeout(int, int)方法(在Android 15中引入)。此时,服务有几秒钟的时间来调用Service.stopSelf()。如果服务没有调用Service.stopSelf(),则会出现以下错误消息的故障:"A foreground service of <fgs_type> did not stop within its timeout: <component_name>"。在Beta 2中,故障消息显示为ANR,但在未来的Beta版本中,此故障消息将抛出自定义异常。
注意:一个应用所有的mediaProcessing前台服务共享6小时的时间限制。例如,如果一个应用运行了一个mediaProcessing服务4个小时,然后启动了另一个mediaProcessing服务,那么第二个服务只能运行2个小时。但是,如果用户将应用程序置于前台,则计时器将重置,应用程序将有6个小时的可用时间。
为了避免出现ANR,您可以执行以下操作之一:
1. 让您的服务实现新的Service.onTimeout(int, int)方法。当您的应用接收到回调时,请确保在几秒钟内调用stopSelf(),如果您不立即停止应用程序,则可能会出现问题。
2. 确保您的应用的mediaProcessing服务在任何24小时内不超过总共6个小时,除非用户与应用交互,重置计时器。
3. 仅在直接用户交互的结果下启动媒体处理前台服务;由于您的应用程序在服务启动时处于前台,因此在应用程序进入后台后,您的服务有完整的6个小时。
4. 使用替代API(如WorkManager)而不是使用mediaProcessing前台服务。
如果您的应用的mediaProcessing前台服务在过去的24小时内运行了6个小时,则除非用户将您的应用带到前台(重置计时器),否则您不能启动另一个mediaProcessing前台服务。如果您尝试启动另一个mediaProcessing前台服务,则系统会抛出ForegroundServiceStartNotAllowedException异常,并显示类似于“mediaProcessing”前台服务类型的时间限制已经耗尽的错误消息。
更多mediaProcessing前台服务类型的信息,请参考:
https://developer.android.com/about/versions/15/changes/foreground-service-types#media-processing
1.4、应用适配
应用可以选择使用Media processing类型的前台服务来处理转码媒体文件这样的操作。
要使用这种前台服务类型,需要在manifest中申明相关权限和foregroundServiceType属性:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING" />
.....
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="mediaProcessing"
>
</service>
......
2、对启动前台服务的 BOOT_COMPLETED 广播接收器的限制
2.1、特性背景
Android15之前, BOOT_COMPLETED 广播接收方允许启动除 microphone 类型的前台服务。
2.2、适用范围
targetSDK≥Android 15
2.3、特性内容
Android15 对BOOT_COMPLETED
类型的广播接收方启动前台服务有了新的限制
如果 BOOT_COMPLETED 广播接收方尝试启动除 LOCATION、CONNECTED_DEVICE、REMOTE_MESSAGING、HEALTH、SYSTEM_EXEMPTED 和 SPECIAL_USE 类型以外的前台服务,系统则会抛出ForegroundServiceStartNotAllowedException 异常。
此外,持有SYSTEM_ALERT_WINDOW权限的应用现在需要有一个可见的悬浮窗才能从后台启动前台服务。否则,系统也会抛出ForegroundServiceStartNotAllowedException异常。
2.4、应用适配
开发者应该注意不要监听BOOT_COMPLETED来启动除 LOCATION、CONNECTED_DEVICE、REMOTE_MESSAGING、HEALTH、SYSTEM_EXEMPTED 和 SPECIAL_USE 类型以外的前台服务。
持有SYSTEM_ALERT_WINDOW权限的应用需要首先启动一个TYPE_APPLICATION_OVERLAY类型的悬浮窗并保证悬浮窗可见,才能启动前台服务。
开发者可以调用View.getWindowVisibility()来查看悬浮窗的可见性,也可以监听View.onWindowVisibilityChanged()回调。
3、对请求音频焦点的限制
3.1、特性背景
Android15之前, 应用可以无任何限制的申请音频焦点。Android 15针对此做出了规范。
3.2、适用范围
targetSDK ≥ Android 15
3.3、特性内容
面向 Android 15 的应用必须是前台应用或运行与音频相关的前台服务才能请求音频焦点。
关于运行与音频相关的前台服务可参考:
https://developer.android.com/develop/background-work/services/fgs/service-types
如果应用程序在不满足这些要求之一时尝试请求焦点,则调用将返回 AUDIOFOCUS_REQUEST_FAILED。
Demo演示
此Demo会在用户按压Home键时,使用WorkManager执行一个后台延时10s的任务,以验证应用不在前台,且无音频相关的前台服务时,是否可以申请音频焦点。
package com.example.myapplication
import android.content.Context
import android.media.AudioManager
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
class BackgroundWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
// 在这里执行后台任务
Log.w("BackgroundWorker", "Doing work...")
val audioManager =
applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val result = audioManager.requestAudioFocus(
null,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
)
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.w(
"BackgroundWorker",
"requestAudioFocus success, AudioManager.AUDIOFOCUS_REQUEST_GRANTED "
)
} else {
Log.w("BackgroundWorker", "requestAudioFocus failure,result:$result")
}
return Result.success()
}
}
运行代码查看相关日志,可以发现由Demo运行结果可知,当应用不在前台,且无音频相关的前台服务时,音频焦点申请会失败,系统输出类似“I Focus request DENIED for uid:10301 clientId:android.media.AudioManager@c71a082 req:1 procState:8”的日志,即告知开发者音频焦点申请被拒绝。
2025-01-11 18:44:19.641 30567-30567 AudioFocusActivity com.example.myapplication I onPause
2025-01-11 18:44:20.537 30567-30567 VRI[AudioFocusActivity] com.example.myapplication D visibilityChanged oldVisibility=true newVisibility=false
2025-01-11 18:44:29.680 30567-30657 BackgroundWorker com.example.myapplication W Doing work...
2025-01-11 18:44:29.691 2201-5135 AS.HardeningEnforcer system_process I Focus request DENIED for uid:10301 clientId:android.media.AudioManager@c71a082 req:1 procState:8
2025-01-11 18:44:29.693 30567-30657 BackgroundWorker com.example.myapplication W requestAudioFocus failure,result:0
3.4、应用适配
如果需要播放音频或者录制音频,那么必须考虑当前是否为前台应用(在前台或者有和音频相关的前台服务)。
4、elegantTextHeight属性默认为 true
4.1、特性背景
ElegantTextHeight:优雅的文本高度。某些语言的字体较高,如果该值为false会选用紧凑字体,设置为true就会比较宽松的字体。这个特性对中文没有影响。
4.2、适用范围
targetSDK≥Android 15
4.3、特性内容
对于targetSDK大于Android 15的应用来说,TextView中的elegantTextHeight属性默认值将是true。用更易读的字体替换了默认使用的紧凑字体,该紧凑字体是为了防止布局出现问题而引入的;Android13(API级别33)通过允许文本布局拉伸垂直高度,使用fallbackLinewSpacing属性来防止许多这些问题。
在Android15中,紧凑字体仍然存在于系统中,因此应用可以将elegantTextHeight设置为false,以获得与以前相同的行为,但是在未来版本可能不再支持。因此,应用支持以下脚本:阿拉伯语、老挝语,缅甸语、泰米尔语、古吉拉特语、卡纳达语、马拉雅拉姆语、奥迪亚语、泰卢固语或泰语,请将elegantTextHeight设置为true进行测试。
elegantTextHeight behavior for apps targeting Android 14 (API level 34) and lower.
elegantTextHeight behavior for apps targeting Android 15.
注:从左到右的语言分别为缅甸语、缅甸语、汉语和汉语
黄色背景:elegantTextHeight的值为false
绿色背景:elegantTextHeight的值为true
4.4、应用适配
该特性对某些字体有影响,对汉语无影响。如果想保持之前的状态,可将该值设置为false。但是在未来有可能谷歌会要求必须为true。所以三方应用需要提前做好适配准备。
5、Edge-to-edge(边到边)强制执行
5.1、特性背景
在Android 15设备上,targetSDK>=Android15的应用将强制进行全屏展示,并且状态和导航栏将保持透明化。targetSDK<Android 15的应用程序默认不会允许边到边的特性,即仍然保持用户层的View在状态栏和导航栏之间。当在Android15平台上之前使用的设置系统栏颜色的API将被弃用,包括setNavigationBarColor
,setNavigationBarColor
,即便使用这些方法设置了,系统也将默认保持沉浸式的体验。
5.2、适用范围
targetSDK≥Android 15
5.3、特性内容
在Android15之前系统会在DecorView 中添加两个背景View用来控制systembar的背景色,同时也把ContentView的大小范围控制在这两个view的之间,如下图:
在Android15上系统默认去掉上述两个背景View,所以ContentView的范围和DecorView 一样,全屏展示:
这一系列的变化对应用产生的影响,如下图应用没有适配边到边的情况下,会发生应用层的内容布局控件会展示到statusbar的后面,这种情况开发者需要对非compose应用和compose应用进行区分处理:
非compose应用需要在应用布局中添加android:fitsSystemWindows="true",或者将用户View setFitsSystemWindows(true),否则就会出现如下图所示的,内容布局控件会展示到了statusbar的后面的异常现象:
compose 中material3一些组件默认是处理掉这个insets,因此不会有问题,能正常显示:
下图展示了material2系列组件没有添加padding导致的显示异常:
material2添加padding后,显示效果恢复正常:
5.4、应用适配
如果应用targetSdk>=Android 15,那么针对布局需要设置android:fitsSystemWindows="true" :
针对compose:
使用material3 的组件作为头布局和底布局如TopAppBar,BottomAppBar或者NavigationBar,如需要使用material2的组件作为头布局和底布局,需要自己处理padding 或者contentWindowInsets
来处理。并且在Android15版本应用使用setStatusBarColor
将不再生效,而setNavigationBarColor
只针对三键导航栏有效果。
6、稳定的configuration
6.1、特性背景
Configuration类专门用于描述手机设备上的配置信息:
public int densityDpi; //得到设备的密度
public float fontScale; //获取当前用户设置的字体的缩放因子
public int KeyboardHidden;//该属性会返回一个boolean值用于表示当前的键盘是否可用,该属性不仅会判断系统的硬件键盘,也会判断系统位于屏幕上的软键盘,如果该系统的硬件键盘不可用但软键盘可用该属性会返回KEYBOARDHIDDEN_NO,只有当两个键盘都不可用的时候才返回KEYBOARDHIDDEN_YES
public int keyboard;//获取当前设备所关联的键盘的类型
public Locale locale;//获取用于当前的Locale
public int mcc;//得到移动信号的国家码
public int mnc;//得到移动信号的网络码
public int navigation;//判断系统上方向导航设备的类型。该属性的返回值:NAVIGATION_NONAV(无导航)、NAVIGATION_DPAD(DPAD导航)、NAVIGATION_TRACKBALL(轨迹球导航)、NAVIGATION_WHEEL(滚轮导航)
public int orientation;//得到系统屏幕的方向,该属性将会返回ORIENTATION_LANDSCAPE(横向屏幕),ORIENTATION_PORTRAIT(竖向屏幕),ORIENTATION_SQUARE(方形屏幕)三个属性值之一
public int touchscreen;//获取系统触摸屏的触摸方式。该属性的返回值:TOUCHSCREEN_NOTOUCH(无触摸屏)、TOUCHSCREEN_STYLUS(触摸笔式触摸屏)、TOUCHSCREEN_FINGER(接收手指的触摸屏)等属性值
如果应用程序的targetSDK版本是Android15或更高版本,则Configuration不再排除系统栏。如果在布局计算中使用Configuration类中的屏幕大小,开发者应该使用更好的替代方案,如适当的ViewGroup、WindowInsets或WindowMetricsCalculator,具体取决于您的需求。
Configuration自API1来就可用,通常从Activity.onConfigurationChanged中获取它。它提供了密度、方向和大小等信息。Configuration返回的窗口大小的一个重要特征是它排除了系统栏。
Configuration大小通常用于资源选择,例如res/layout-h500dp仍然有效。但是不鼓励使用它来进行布局的计算。如果进行布局计算,应该寻找更合适的方案来代替它。
如果用Configuration来计算布局,请使用适当的ViewGroup,如CoordinatorLayout或ConstraintLayout。如果你用它来确定系统导航栏的高度,使用WindowInsets。如果想确定当前应用窗口的大小,请使用computeCurrentWindowMetrics。
6.2、适用范围
targetSDK≥Android 15
6.3、特性内容
Android15上screenWidthDp和screenHeightDp将包含系统条等内容的尺寸。但是当应用使用Window#setDecorFitSystemWindowWindows(boolean)进行边到边显示的时候,系统条尺寸不被包含。
6.4、应用适配
应用需要适配使用到Configuration中的screenHeightDp和screenWeightDp的地方以及所间接影响到的相关值:
Configuration.smallestScreenWidthDp
Configuration.orientation
Display.getSize(Point) (该接口已在API level30废弃)
Display.getMetrics()(和API30及其以后的接口等级一致)
7、安全的后台Activity启动
7.1、特性背景
Android15继续加强对后台启动activity的管控,以提高安全性。
7.2、适用范围
targetSDK≥Android 15
7.3、特性内容
栈顶应用如果在AndroidManifest.xml 文件中设置allowCrossUidActivitySwitchFromBelow为false,可以阻止自己UID 不匹配的应用启动activity。 PendingIntent的创建者可以通过ActivityOptions#setPendingIntentCreatorBackgroundActivityStartMode(int state)方法来赋予PendingIntent后台启动activity的权力。 PendingIntent的发送者也可以通过ActivityOptions#setPendingIntentBackgroundActivityStartMode(int state)方法来赋予PendingIntent后台启动activity的权力。 防止其他应用的 activity 随意启动到您自己的task中。 防止不可见的窗口被用于后台启动。
7.4、应用适配
应用需要认真考察并测试需要后台启动的情况,防止后台启动被系统限制从而造成功能异常。
8、Android 15 中有关限制非 SDK 接口的更新
Android 15 包含更新后的受限非 SDK 接口列表(基于与 Android 开发者之间的协作以及最新的内部测试)。在限制使用非 SDK 接口之前,我们会尽可能确保有可用的公开替代方案。
如果您的应用并非以 Android 15 为目标平台,其中一些变更可能不会立即对您产生影响。不过,虽然您的应用可以访问某些非 SDK 接口(具体取决于应用的目标 API 级别),但使用任何非 SDK 方法或字段始终存在导致应用出问题的显著风险。
如果您不确定自己的应用是否使用了非 SDK 接口,则可以测试该应用进行确认。如果您的应用依赖于非 SDK 接口,则应开始计划迁移到 SDK 替代方案。不过,我们知道某些应用具有使用非 SDK 接口的有效用例。如果您无法为应用中的功能找到无需使用非 SDK 接口的替代方案,则应请求新的公共 API。
如需查看 Android 15 的所有非 SDK 接口的完整列表,可参考:
https://dl.google.com/developers/android/vic/non-sdk/hiddenapi-flags.csv
9、OpenJDK 17变更
Android 15继续更新Android核心库,以与最新的OpenJDK LTS版本的功能保持一致。其中一个更改可能会影响针对Android 15的应用程序兼容性:
例如,当在格式字符串中使用参数索引为0(%0)时,将抛出以下异常:
IllegalFormatArgumentIndexException: Illegal format argument index = 0
在这种情况下,可以通过使用参数索引为1(%1)来解决问题。
通常情况下,这种更改不应导致应用程序崩溃,但您的代码不应期望从Random.ints()方法生成的序列与Random.nextInt()匹配。
二、结语
以上就是影响以Android 15为目标平台应用的行为变更,Android 15应用适配指南系列下一篇,将介绍Android 15新功能和API,敬请期待。
想了解更多关于App出海技巧,关注公众号!进GP交流群从小白秒变出海专家!