> 技术文档 > Android 16适配指南

Android 16适配指南


一、前言

Google Play老开发者都知道,Android系统版本适配,一直是影响App上架Google Play非常重要的因素。

适配不佳有哪些影响?

  • 功能异常被拒若 App 对 Android 版本适配不佳,在某些版本系统上出现如无法安装、安装后无法加载、加载后无响应、频繁崩溃等功能异常情况,根据 Google Play “最基本的功能” 政策,这类应用是不得上架的。
  • 用户体验差被拒不同的 Android 版本在系统特性、界面风格、操作逻辑等方面可能存在差异。如果 App 没有做好适配,可能在新系统上出现界面显示错乱、操作不流畅等问题,无法提供稳定、响应迅速、引人入胜的用户体验,也会导致上架被拒。
  • 影响 App 流量如果 Google Play ⽤⼾的设备搭载的 Android OS 版本,⾼于应⽤的 TargetSDK 版本,⽤⼾将⽆法再发现此类应⽤,这就会对 App 流量产生直接影响。

我们的适配建议:

  • 及时关注政策更新Google Play 会不定期更新开发者政策,在政策更新后会给予一定的缓冲时间让开发者进行应用适配,但开发者需及时关注政策变化,以便提前规划适配工作。
  • 全面测试在提交上架前,要在多种不同 Android 版本的设备上进行充分测试,覆盖低版本到高版本,尽可能模拟用户的真实使用场景,检查应用在不同系统版本上的功能完整性、稳定性和兼容性等。
  • 遵循官方文档和指南Google 提供了丰富的官方文档和开发指南,如关于不同 Android 版本的特性介绍、适配建议、API 使用说明等,开发者应仔细阅读并按照要求进行应用开发和适配。

每年Google Play都会有一项关于TargetSDK版本要求:所有待上架App要在8月底(新包上架)及11月底(在架包更新)进行新系统版本的适配。

Android 16已于6月3日正式发布,应广大Android出海粉丝要求,我们迅速发布了本篇Android 16适配教程!

目前市场上只有极少量有关Android 16适配文章,但是此类文章要么不专业,要么不全面,所以本人特开此Android 16应用适配保姆级教程,以帮助更多开发者适配好Android 16,从而更顺利的上架到Google Play。

Android 16适配有2大核心要点:

  • 影响Android 16 上所有应用的行为变更。
  • 影响以Android 16 为目标平台应用的行为变更。

那么本篇就围绕这2大核心变更要点开讲。

二、影响Android 16 上所有应用的行为变更

1、锁屏后MediaProjection被自动停止

一、特性背景

Android 近年来一直在加强MediaProjection的能力与隐私安全。Android 16 上,应用使用MediaProjection投屏、录屏时,屏幕左上角会出现醒目的标志,表明有应用正在录屏;同时,用户使用power键锁屏,或手机被长时间搁置自然锁屏后,MediaProjection会被stop,投/录屏被停止。

官网链接:

https://developer.android.com/media/grow/media-projection#status_bar_chip_auto_stop

二、适用范围

Android 16 上的所有App

三、特性内容

谷歌意图:通过新增的MediaProjectionStopController对象管控MediaProjection的停止逻辑,并添加了两种需要停止的场景,来实现更好的管控和更高的隐私安全。

变更内容:新增的MediaProjectionStopController对象管控MediaProjection的释放逻辑,手机按电源键/长时间搁置进入息屏状态时或者先打电话后投屏/录屏后挂断电话时,会停止当前的MediaProjection。

MediaProjection因息屏被停止时系统会打出这样的日志:

四、应用适配

建议开发者考虑到上述MediaProjection被系统停止的情形,做出针对性的处理。例如,可以实现MediaProjection.Callback.onStop()回调,在MediaProjection被系统停止的时候及时做出响应。

2、提高了对 Intent 重定向攻击的安全性

一、特性背景

Intent重定向漏洞是指,其他应用利用intent的Parcelable的特性,通过intent 的extra等信息,嵌套Intent从而绕过安全检查实现启动非导出组件 或者触发特权操作或获得对敏感数据的 URI 访问权限,可能导致数据被盗和任意代码执行。

重定向原理图:

Android 16 提供了针对常规 Intent 重定向攻击的默认安全防护,可帮助保护应用和用户免受恶意应用的侵害。

官网链接:

https://developer.android.com/about/versions/16/behavior-changes-all?hl=zh-cn#intent-redirect-attacks

二、适用范围

Android 16 上的所有App

三、特性内容

3.1、特性内容介绍

  • 增强了针对 intent 重定向攻击的安全性
    Android 16 提供了针对常规 intent 重定向攻击的默认安全防护,并且所需的兼容性和开发者更改最少。
    我们将默认针对 intent 重定向漏洞引入安全增强解决方案。在大多数情况下,使用 intent 的应用通常不会遇到任何兼容性问题。
  • 停用 intent 重定向处理
    Android 16 引入了一个新 API,允许应用选择停用启动安全保护。在默认安全行为干扰合法应用用例的特定情况下,这可能很有必要。
    对于针对 Android 16 SDK 或更高版本进行编译的应用,您可以直接对 intent 对象使用removeLaunchSecurityProtection()方法。
 val i = intent val iSublevel: Intent? = i.getParcelableExtra(\"sub_intent\") iSublevel?.removeLaunchSecurityProtection() // Opt out from hardening iSublevel?.let { startActivity(it) }

3.2、源码解读

主要涉及到4个类的变动 ActivityStarter 、Intent 、BaseBundle 和Clipdata。

Intent 类新增和修改内容

类型 名称 备注 新增 静态变量 EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN 用于该标志表示当前Intent的创建者令牌(creator token)缺失或无效。 新增 静态内部类 CreatorTokenInfo mCreatorToken用于标识嵌套Intent的创建者身份mNestedIntentKey 跟踪所有 包含嵌套Intent的 Extra Key 新增 静态内部类 NestedIntentKey NestedIntentKey 嵌套Intent的管理 新增 方法 removeLaunchSecurityProtection 移除Intent的 EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN 标志和 CreatorTokenInfo 修改 方法 writeToParcel/readFromParcel - 序列化时会 附加创建者身份凭证 (mCreatorToken) 和 跨进程传递的嵌套 Intent 键信息- 反序列化时恢复 创建者 Token ,用于校验源头合法性(对比当前进程的 Binder 身份)

ActivityStarter 类新增和修改内容

类型 名称 备注 新增 静态变量 ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION 用来表示重定向限制功能是否开启 新增 方法 maybeMarkAsMissingCreatorTokenmaybeMarkAsMissingCreatorTokenInternal 对序列化的intentadd XTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN 标志 修改 内部类 Requst 新增 intentCreatorUid 和intentCreatorPackage用于确认intent的creator 修改 方法 resolveActivity 负责 解析要启动的 Activity 信息 并执行关键的 安全校验逻辑 修改 方法 executeRequest 执行request时,新增了intentCreatorUid的判断是否为DEFAULT_INTENT_CREATOR_UID 新增 方法 getIntentRedirectPreventedLogMessagelogAndThrowExceptionForIntentRedirect 获取触发intent重定向日志信息获取以及打印该日志

BaseBundle 类新增和修改内容

类型 名称 备注 修改 方法 getValueAt 在获取Bundle的值时调用maybeMarkAsMissingCreatorToken给Intentadd EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN 标志

Clipdata 类新增和修改内容

类型 名称 备注 修改 方法 getIntent 在获取Clipdata的值时调用maybeMarkAsMissingCreatorToken给Intentadd EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN 标志

主要流程分为:

1、Intent的CreatorTokenInfo 初始化。

  • 在应用构建 完成intent之后,调用startactivity随后调用到Instrumentation 的execStartActivity,随后执行执行prepareToLeaveProcess,做intent跨进程的处理。
  • 在intent 的prepareToLeaveProcess的方法中判断intent是否为初始intent,如果是则会调用内部类NestedIntentKey的collectExtraIntentKeys 方法。
  • NestedIntentKey的collectExtraIntentKeys的方法中会判断intent是否的mExtras和mClipData是否为空,如果不为空,就针对其中类型为intent的实例,初始化对应intent以及嵌套NestedIntent的mCreatorTokenInfo用于后续Intent的Creator的信息。

2、Intent序列化/反序列化。

  • 在Intent进行序列化中,Android W新增了将CreatorTokenInfo.mCreatorToken 写入,在读的时候也会将Intent 的 CreatorTokenInfo读出。

3、解析Intent中Bundle和Clipdata。

  • 在被调用的APP内部如果对Intent的Extra的信息进行解析,即应用执行Intent 的getParcelableExtra,随后就会调用到Bundle的getParcelableExtra 最终走到BaseBundle 的getValueAt方法,在这个方法中会判断是否包含多个key的 Extra 信息,并调用Intent 的maybeMarkAsMissingCreatorToken方法。
  • 执行 maybeMarkAsMissingCreatorToken方法时,会对Extra包含的intent以及嵌套NestedIntent添加 EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN的标志位。
  • Clipdata也是也是同理,在被调用的APP内部如果对Intent的Clipdata的信息进行解析,会对Clipdata包含的intent以及嵌套NestedIntent添加 EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN的标志位。

4、Intent 校验流程。

ActivityStarter在Request 默认初始化时intentCreatorUid 设置DEFAULT_INTENT_CREATOR_UID。

分为两种情况:

  • executeRequest (显示 Intent 启动)。
    在此过程中,如果intentCreatorUid不为DEFAULT_INTENT_CREATOR_UID 即说明该Intent 的Creator 属其他应用,随后checkStartAnyActivityPermission后判断应用无权限,会调用logAndAbortForIntentRedirect 抛出异常信息堆栈。
  • resolveActivity(隐式 Intent 未指定组件)。
    首先判断Intent的扩展标志检查是否包含EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN,如果设置则表明Intent 重定向处理开启,且Intent存在问题。在检查URI权限时,如果存在Intent的CREATOR的权限问题,就会记录并抛出安全异常。

四、应用适配

总体来说,Android 16 的新增特性不会影响那些遵循正常启动流程的应用。

以下是开发者在适配应用时需要注意:

Android 16 上如果希望停用 intent 重定向处理,可以直接对 intent 对象使用removeLaunchSecurityProtection()方法。

vali= intent val iSublevel: Intent? = i.getParcelableExtra(\"sub_intent\")//执行removeLaunchSecurityProtection,停用 intent 重定向处理 iSublevel?.removeLaunchSecurityProtection() // Opt out from hardening iSublevel?.let { startActivity(it) }

如果您在 intent 里添加嵌套 intent ,启动其他APP的非导出组件请注意这种方式将在Android 16 之后无法使用,请使用其他方式实现。

3、setImportantWhileForeground失效

一、特性背景

JobInfo.Builder#setImportantWhileForeground(boolean) 方法用于在调度应用位于前台或暂时豁免于后台限制时指示作业的优先级。

自 Android 12(API 级别 31)起,此方法已废弃。从 Android 16 开始,它不再有效,系统会忽略调用此方法。

此功能移除也适用于 JobInfo#isImportantWhileForeground()。从 Android 16 开始,如果调用该方法,该方法会返回 false。

官网链接:

https://developer.android.com/about/versions/16/behavior-changes-all?hl=zh-cn#jobinfo-setimportantwhileforeground

二、适用范围

Android 16 上的所有App

三、特性内容

JobInfo.Builder#setImportantWhileForeground将此设置为 true 表示当调度应用处于前台或处于后台限制的临时允许列表中时,此作业很重要。这意味着系统将在此期间放宽对此作业的打盹限制。应用应仅将此标志用于对应用在前台正常运行至关重要的短时间作业。请注意,一旦调度应用不再处于后台限制允许列表中,并且处于后台,或者作业因未满足约束而失败,则此作业的行为应与没有此标志的其他作业一样。

标记为前台时重要的作业默认被赋予 JobInfo.PRIORITY_HIGH。

从 android.os.Build.VERSION_CODES#B 开始,此标志将被忽略,并且不再有效运行,无论调用应用的目标 SDK 版本如何。{#isImportantWhileForeground()} 将始终返回 false。应用应使用 #setExpedited(true)来指示此作业很重要并且需要尽快运行。

四、应用适配

//-----------------------------------original------------------------------------------ JobSchedulerjobScheduler= (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); intjobId=1; // 任务的唯一标识符 ComponentNamejobService=newComponentName(this, MyJobService.class); // 指定要执行任务的 JobService 类 JobInfo.Builderbuilder=newJobInfo.Builder(jobId, jobService); builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); // 设置任务需要的网络类型 builder.setImportantWhileForeground(true);// 如果应用在前台运行时非常重要,则设置这个标志 JobInfojobInfo= builder.build(); jobScheduler.enqueue(jobInfo); // 将任务加入到 JobScheduler 的队列中并开始执行任务 //-----------------------------------now------------------------------------------ JobSchedulerjobScheduler= (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); intjobId=1; // 任务的唯一标识符 ComponentNamejobService=newComponentName(this, MyJobService.class); // 指定要执行任务的 JobService 类 JobInfo.Builderbuilder=newJobInfo.Builder(jobId, jobService); builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); // 设置任务需要的网络类型 builder.setExpedited(true); // 设置任务为加急状态 JobInfojobInfo= builder.build(); jobScheduler.enqueue(jobInfo); // 将任务加入到 JobScheduler 的队列中并开始执行任务

4、JobScheduler 配额优化

一、特性背景

Android 16 对常规作业和加急作业执行运行时配额进行了调整,将根据应用的待机状态、应用是否处于顶部状态以及是否与前台服务同时执行任务等因素进行调整,旨在更好地管理系统资源,提升性能和电池寿命。

官网链接:

https://developer.android.com/about/versions/16/behavior-changes-all?hl=zh-cn#job-quota-opt

二、适用范围

Android 16 上的所有App

三、特性内容

从 Android 16 开始,谷歌将根据以下因素调整常规作业和加急作业执行运行时配额:

  • 应用位于哪个应用待机分桶:在 Android 16 中,系统将开始使用充足的运行时配额来强制执行处于活动状态的待机分桶。
  • 如果作业在应用处于顶部状态时开始执行:在 Android 16 中,如果作业在应用对用户可见时启动,并在应用变为不可见后继续执行,则将遵循作业运行时配额。
  • 如果作业在运行前台服务时执行:在 Android 16 中,与前台服务同时执行的作业将遵循作业运行时配额。如果您要使用作业进行用户发起的数据传输,请考虑改用用户发起的数据传输作业。

上述调整意味着处于不同待机状态的应用将根据其待机级别受到不同的运行时限制,活跃的应用将获得更多的执行时间。同时确保了即使应用不在前台,其后台任务也不会无限制地运行。

此更改会影响使用 WorkManager、JobScheduler 和 DownloadManager 调度的任务。如需调试作业停止的原因,我们建议您通过调用 WorkInfo.getStopReason() 来记录作业停止的原因(对于 JobScheduler 作业,请调用 JobParameters.getStopReason())。

如需详细了解有关延长电池续航时间的最佳实践,请参阅有关优化任务调度 API 的电池用量的指南。

我们还建议您利用 Android 16 中引入的新 JobScheduler#getPendingJobReasonsHistory API 来了解作业未执行的原因。

四、应用适配

如需测试应用的行为,您可以启用替换特定作业配额优化,前提是应用在 Android 16 设备上运行。

如需停用强制执行“顶部状态将遵守作业运行时配额”政策,请运行以下 adb 命令:

adb shell am compat enable OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS APP_PACKAGE_NAME

如需停用强制执行“与前台服务同时执行的作业将遵守作业运行时配额”政策,请运行以下 adb 命令:

adb shell am compat enable OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS APP_PACKAGE_NAME

如需测试特定的应用待机分桶行为,您可以使用以下 adb 命令设置应用的应用待机分桶:

adb shell am set-standby-bucket APP_PACKAGE_NAME active|working_set|frequent|rare|restricted

您还可以使用该命令一次设置多个软件包:

adb shell am set-standby-bucket package1 bucket1 package2 bucket2...

如需了解应用所在的应用待机分桶,您可以使用以下 adb 命令获取应用的应用待机分桶:

adb shell am get-standby-bucket APP_PACKAGE_NAME

对于应用已遵循应用待机模式处理作业功能应该不会有太大的影响。
对于异常情况,Android 16 引入了新 JobScheduler#getPendingJobReasonsHistory API 来供开发者了解作业未执行的原因。

5、被废弃的空作业停止原因

一、特性背景

由于应用程序没有正确管理JobParameters对象的生命周期,可能会导致资源浪费、不一致性等问题,Android 16新增被废弃的空作业停止原因以确保应用开发者可以及时发现和解决问题。

官网链接:

https://developer.android.com/about/versions/16/behavior-changes-all?hl=zh-cn#abandoned-job-stop-reason

二、适用范围

Android 16 上的所有App

三、特性内容

如果与作业关联的 JobParameters 对象已被垃圾回收,但尚未调用 JobService#jobFinished(JobParameters, boolean) 来指示作业已完成,则会发生作业被废弃的情况。这表示作业可能会在应用不知情的情况下运行和重新调度。

依赖于 JobScheduler 的应用不会维护对 JobParameters 对象的强引用,并且超时现在将获得新的作业停止原因 STOP_REASON_TIMEOUT_ABANDONED,而不是 STOP_REASON_TIMEOUT。

如果新的作业被废弃停止原因频繁出现,系统会采取缓解措施来降低作业频率。
应用应使用新的停止原因来检测和减少被废弃的作业。

如果您使用的是 WorkManager、AsyncTask 或 DownloadManager,则不会受到影响,因为这些 API 会代表您的应用管理作业生命周期。

四、应用适配

应用开发者可以自己选择是否使用此原因来及时判断Job中是否出现被废弃的空作业问题。

6、弃用干扰性的无障碍公告

一、特性背景

官网链接:

https://developer.android.com/about/versions/16/behavior-changes-all#disruptive-a11y

二、适用范围

Android 16 上的所有App

三、特性内容

Android 16 废弃了无障碍功能通告,其特征是使用 announceForAccessibility 或调度 TYPE_ANNOUNCEMENT 无障碍功能事件。这可能会给 TalkBack 和 Android 屏幕阅读器用户带来不一致的用户体验,而替代方案可以更好地满足各种 Android 辅助技术的用户需求。

四、应用适配

1、对于窗口更改等重大界面更改,请使用 Activity.setTitle(CharSequence) 和 setAccessibilityPaneTitle(java.lang.CharSequence)。

valbtShowDialog= findViewById(R.id.bt_show_dialog) btShowDialog.setOnClickListener {valdialog= TestDialogFragment() dialog.show(supportFragmentManager, \"dialog\") } classTestDialogFragment : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState) setStyle(STYLE_NORMAL, R.style.MyDialogTheme) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {return inflater.inflate(R.layout.dialog_layout, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState) dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.LTGRAY)) dialog?.setCanceledOnTouchOutside(true) dialog?.setCancelable(true) view.findViewById(R.id.bt_close).setOnClickListener { dismiss() } } }

布局文件:

 .......

2、如需向用户告知关键界面的更改,请使用 setAccessibilityLiveRegion(int)。在 Compose 中,请使用 Modifier.semantics { liveRegion = LiveRegionMode.[Polite|Assertive]}。应谨慎使用这些事件,因为它们可能会在每次更新视图时生成通知。

valtvCurrentTime= findViewById(R.id.tv_current_time) tvCurrentTime.text = buildString { append(\"当前时间: \") append(getFormattedTime()) } tvCurrentTime.accessibilityLiveRegion  = View.ACCESSIBILITY_LIVE_REGION_POLITE// tvCurrentTime.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE// tvCurrentTime.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE findViewById(R.id.bt_update_time).setOnClickListener { view -> tvCurrentTime.text = buildString { append(\"当前时间: \") append(getFormattedTime()) } }

3、如需向用户发送错误通知,请发送类型为 AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR 的 AccessibilityEvent 并设置 AccessibilityNodeInfo#setError(CharSequence),或使用 TextView#setError(CharSequence)

valeditTextPassword= findViewById(R.id.editText_password) findViewById(R.id.bt_accessibility_error).setOnClickListener { view ->// 获取 AccessibilityManager 并发送事件 getSystemService(Context.ACCESSIBILITY_SERVICE)?.let { service ->valaccessibilityManager= (service as AccessibilityManager)if (!accessibilityManager.isEnabled) { Log.e(\"Accessibility\", \"Accessibility is off. Please enable it.\")return@let } editTextPassword.error = \"这是一个错误信息\"// 创建 AccessibilityEventvalevent= AccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED ).apply { contentChangeTypes = AccessibilityEvent.CONTENT_CHANGE_TYPE_ERRORsetSource(editTextPassword) className = editTextPassword.javaClass.namepackageName= editTextPassword.context.packageName }// 发送事件 accessibilityManager.sendAccessibilityEvent(event) } }

7、有序广播优先级范围不再是全局

一、特性背景

通过兼容性变更限制了广播优先级的作用域,旨在解决跨进程优先级滥用导致的安全和性能问题。

官网链接:

https://developer.android.com/about/versions/16/behavior-changes-all#ordered-broadcast-priority

二、适用范围

Android 16上的所有App

三、特性内容

1、在 Android 16 中,无法保证使用 android:priority 属性或 IntentFilter#setPriority() 在不同进程中传送广播的顺序。广播优先级仅在同一应用进程内有效,而不会跨所有进程有效。

2、如果您的应用执行以下任一操作,可能会受到影响:

  • 您的应用声明了具有相同广播 intent 的多个进程,并且希望根据优先级以特定顺序接收这些 intent。
  • 您的应用进程与其他进程交互,并期望以特定顺序接收广播 intent。

如果进程需要相互协调,则应使用其他协调渠道进行通信。

四、应用适配

开发者应避免依赖跨进程的广播优先级逻辑,确保应用行为在不同 Android 版本上的一致性。

8、预测性返回支持三键导航

一、特性背景

预测性返回在Android16以前仅支持全面屏手势,现在Android16也支持三键导航。长按后退按钮会启动预测性后退动画,可预览后退到的界面。

官网链接:

https://developer.android.com/about/versions/16/behavior-changes-all#three-button-predictive-back

二、适用范围

Android 16 上的所有App

三、特性内容

三键导航支持所有的系统预测返回动画,包括:返回桌面,Task切换或Activity切换等动画。

同样在Android 16 中若app要不适配该特性,需要在AndroidManifest.xml文件中声明以下内容:

在Application中:

 ...

或对于单个Activity:

 <activity android:name=\".MainActivity\" android:enableOnBackInvokedCallback=\"false\" ... 

四、应用适配

若应用选择适配三键导航预测性返回手势动画,以下是适配的关键内容。

4.1、在AndroidManifest.xml中添加以下内容

 ...   //只在MainActivity生效 <activity android:name=\".MainActivity\" android:enableOnBackInvokedCallback=\"true\" ...  <activity android:name=\".SecondActivity\" android:enableOnBackInvokedCallback=\"false\" ...  

4.2、Android X 和系统接口

app是否使用Android X UnsupportApi SupportApi 是 OnBackPressed()(Android12及以下始终回调,高于Android12优先级低于OnPressedCallback) OnBackPressedDispatcherOnBackPressedCallback 否 onBackPressed()(Android12及以下始终回调,高于Android12优先级低于OnPressedCallback) onBackInvokedDispatcherOnBackInvokedCallback

具体参考:

https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture#update-custom

9、更新对non-SDK API的限制

一、特性背景

从 Android 9(API 级别 28)开始,Android 平台对应用能使用的非 SDK 接口实施了限制。只要应用引用非 SDK 接口或尝试使用反射或 JNI 来获取其句柄,这些限制就适用。

Android W继续增加了non-SDK API的限制名单,ART的变化也对现有的绕过限制工具产生了影响。

官网链接:

https://developer.android.com/about/versions/16/changes/non-sdk-16?hl=zh-cn

二、适用范围

Android 16 上的所有App

三、特性内容

笔者统计了V和W的hiddenapi-flags.csv中V上公开但W上被屏蔽或移除的API共6个。涉及到以下几个类别:

1、android/bluetooth/le/BluetoothLeScanner: 这个类提供了低功耗蓝牙扫描功能,如启动截断扫描。

API名称 V上标签 W上标签 Landroid/bluetooth/le/BluetoothLeScanner;->startTruncatedScan(Ljava/util/List;Landroid/bluetooth/le/ScanSettings;Landroid/bluetooth/le/ScanCallback;) sdk,system-api,test-api removed,unsupported

2、java/lang/Thread: 这个类提供了线程管理的底层控制方法,如强制停止或挂起线程。

V上标签 W上标签 core-platform-api,public-api,sdk,system-api,test-api removed,unsupported core-platform-api,public-api,sdk,system-api,test-api removed,unsupported core-platform-api,public-api,sdk,system-api,test-api removed,unsupported core-platform-api,public-api,sdk,system-api,test-api removed,unsupported core-platform-api,public-api,sdk,system-api,test-api removed,unsupported

3、Tag的 含义:

代码标记 说明 blocked已弃用:blacklist 无论应用的目标 API 级别是什么,您都无法使用的非 SDK 接口。 如果您的应用尝试访问其中任何一个接口,系统就会抛出错误。 max-target-x已弃用:greylist-max-x 从 Android 9(API 级别 28)开始,当有应用以该 API 级别为目标平台时,我们会在每个 API 级别分别限制某些非 SDK 接口。这些名单会以应用无法再访问相应名单中的非 SDK 接口之前可以作为目标平台的最高 API 级别 (max-target-x) 进行标记。例如,在 Android Pie 中未被屏蔽,但现在已被 Android 10 屏蔽的非 SDK 接口会列入 max-target-p (greylist-max-p) 名单,其中“p”表示 Pie 或 Android 9(API 级别 28)。如果您的应用尝试访问受目标 API 级别限制的接口,系统就会将此 API 视为已列入屏蔽名单。 unsupported已弃用:greylist 不受限制且您的应用可以使用的非 SDK 接口。但请注意,这些接口不受支持,可能会在不另行通知的情况下随时发生更改。预计这些接口在未来的 Android 版本中会被有条件地屏蔽,并列在 max-target-x 名单中。 public-api 和 sdk已废弃:public-api 和 whitelist 已在 Android 框架软件包索引中正式记录、现已受支持并且可以自由使用的接口。 test-api 用于内部系统测试的接口,例如推动通过兼容性测试套件 (CTS) 进行测试的 API。测试 API 不是 SDK 的一部分。从 Android 11(API 级别 30)开始,测试 API 已包含在屏蔽名单中,因此无论应用的目标 API 级别是什么,都禁止使用测试 API。所有测试 API 均不受支持,且无论平台 API 级别如何,都可能会在不另行通知的情况下随时发生更改。

四、应用适配

根据之前的异常日志中,Accessing hidden field日志表明应用尝试利用反射调用某个被hidden的方法。

NoSuchMethodException或者NoSuchFieldException日志,表示未找到该Method 或者Field。

结合以上两种日志,即可判断出是Android 更新对non-SDK API的限制 此特性导致的闪退问题,后续修复即可。

三、影响以Android 16 为目标平台应用的行为变更

1、Edge to edge退出选项被停用

一、特性背景

Android 15 设备上为 targetSdk >= 35的应用强制开启 Edge-to-edge 特性,但同时也提供了 R.attr#windowOptOutEdgeToEdgeEnforcement设为true来让开发者主动停用该特性。现在,对于 targetSdk >= 36且运行在 Android 16 上的应用来说,R.attr#windowOptOutEdgeToEdgeEnforcement将处于停用状态。

官网链接:

https://developer.android.com/about/versions/16/behavior-changes-16?hl=zh-cn#edge-to-edge

二、适用范围

targetSDK ≥ 36

三、特性内容

对于之前通过将R.attr#windowOptOutEdgeToEdgeEnforcement设为true来主动停用 Edge-to-edge 的应用来说,若将 targetSdk 升级至36及以上且运行在 Android 16 上时,那么需要对 Edge-to-edge 特性重新进行适配。

四、应用适配

  • 应用如果在 Compose 中使用 Material 3组件,例如 TopAppBar、BottomAppBar 和 NavigationBar,这些组件可能不会受到影响,因为它们可以自动处理 insets。
  • 如需要使用 Material 2的组件作为头布局和底布局,需要应用自己使用 Padding 或者contentWindowInsets 来处理。
  • 应用如果使用视图 (Views)或自定义容器进行布局,请使用 ViewCompat.setOnApplyWindowInsetsListener 进行适配处理。

详细的关于edge-to-edge的说明可以参考Android 15的Edge-To-Edge(边到边)强制执行。

2、Health and fitness permissions

一、特性背景

谷歌在android 16 大版本升级中涉及一项权限规则变更:对于android 16及更高平台的设备,android.permissions.BODY_SENSORS权限需转换为android.permissions.health下的细粒度权限。以前需要BODY_SENSORSBODY_SENSORS_BACKGROUND的 API 现在都需要相应的android.permissions.health权限。

官网链接:

https://developer.android.com/about/versions/16/behavior-changes-16?hl=zh-cn#health-fitness-permissions

二、适用范围

targetSDK ≥ 36

三、特性内容

BODY_SENSORSBODY_SENSORS_BACKGROUND影响的数据类型、API和前台服务类型如下:

  • Wear Health Services 中的一种数据类型:HEART_RATE_BPM
  • 心率传感器:Sensor.TYPE_HEART_RATE
  • Wear ProtoLayout 的API:heartRateAccuracy & heartRateBpm
  • 前台服务:FOREGROUND_SERVICE_TYPE_HEALTH

关于特性详情可参考官网。

四、应用适配

如果应用内部使用了上述 API及服务等,则现在应请求相应的细粒度权限:

  • 对于使用时监测心率、血氧饱和度或体表温度,请求android.permissions.health下的细粒度权限,例如使用READ_HEART_RATE代替BODY_SENSORS
  • 对于后台传感器访问,使用READ_HEALTH_DATA_IN_BACKGROUND代替BODY_SENSORS_BACKGROUND

3、Fixed rate定时任务调度优化

一、特性背景

Android 16 在 scheduleAtFixedRate定时任务调度上进行了优化。之前版本中,当应用进程处于无效生命周期时,错过多次任务执行后,恢复时会立即执行所有错过的任务。这可能导致性能问题,比如ANR或资源争抢。

官网链接:

https://developer.android.com/about/versions/16/behavior-changes-16?hl=zh-cn#schedule-at-fixed-rate

二、适用范围

targetSDK ≥ 36

三、特性内容

Google对ScheduledThreadPoolExecutor类中的部分接口进行了调整,使得在Android16上最多补发一次错过的任务,后续任务按原定间隔继续执行。

四、应用适配

1、主动计算并处理错过的周期:

executor.scheduleAtFixedRate(() -> { long currentTime = System.currentTimeMillis(); long missedCycles = ((currentTime - lastExecutionTime) / interval) - 1; if (missedCycles > 0) { // 自定义补偿逻辑 } ... lastExecutionTime = currentTime; }, initialDelay, interval, TimeUnit.MILLISECONDS);

2、 换用其他定时器方案。

4、迁移到或停用预测性返回

一、特性背景

预测性返回就是在返回的过程中可提前预知将要返回到哪个界面,给用户带来更好的体验。预测性返回自Android13开始支持,经过近几年的完善,当在Android16或更高版本的应用程序,默认会开启预测性后台系统动画(包括:返回桌面、切换Activity或切换Task等)。

官网链接:

https://developer.android.com/about/versions/16/behavior-changes-16#predictive-back

二、适用范围

targetSDK ≥ 36

三、特性内容

在Android16中android:enableOnBackInvokedCallback的默认值为true。若targetSDK ≥ 36的app不适配该特性,需要在AndroidManifest.xml文件中声明以下内容。

在Application中:

 ... 

或对于单个Activity:

 <activity android:name=\".MainActivity\" android:enableOnBackInvokedCallback=\"false\" ... 

影响:若不在AndroidManifest.xml中没有主动声明android:enableOnBackInvokedCallback=\"false\",则不会回调onBackPressed()方法的以及不分发KeyEvent.KEYCODE_BACK。

四、应用适配

若应用选择适配预测性返回手势动画,以下是适配的关键内容。

4.1、在AndroidManifest.xml中添加以下内容:

  ...   //只在MainActivity生效 <activity android:name=\".MainActivity\" android:enableOnBackInvokedCallback=\"true\" ...  <activity android:name=\".SecondActivity\" android:enableOnBackInvokedCallback=\"false\" ...   

4.2、AndroidX和系统接口

app是否使用AndroidX UnsupportApi SupportApi 是 OnBackPressed()(Android12及以下始终回调,高于Android12优先级低于OnPressedCallback) OnBackPressedDispatcherOnBackPressedCallback 否 onBackPressed()(Android12及以下始终回调,高于Android12优先级低于OnPressedCallback) onBackInvokedDispatcherOnBackInvokedCallback

5、ElegantTextHeight的属性

一、特性背景

ElegantTextHeight:优雅的文本高度。设置为 true,会将紧凑字体替换为比较宽松的字体,更易于阅读,效果如下2张图。

官网链接:

https://developer.android.com/about/versions/16/behavior-changes-16#elegant-text-height

二、适用范围

targetSDK ≥ 36

三、特性内容

Android 16 弃用了 elegantTextHeight 属性,并且在您的应用以 Android 16 为目标平台后,系统会忽略该属性。这些 API 控制的“界面字体”即将停用,因此您应调整所有布局,以确保以阿拉伯语、老挝语、缅甸语、泰米尔语、古吉拉特语、卡纳达语、马拉雅拉姆语、奥里亚语、泰卢固语或泰语呈现一致且可持续的文字。

对于以 Android 14(API 级别 34)及更低版本为目标平台的应用,或者对于通过将 elegantTextHeight 属性设置为 false 而替换默认值的以 Android 15(API 级别 35)为目标平台的应用,elegantTextHeight 行为。

对于以 Android 16 为目标平台的应用,或者对于未通过将 elegantTextHeight 属性设置为 false 来替换默认值的以 Android 15(API 级别 35)为目标平台的应用,elegantTextHeight 行为。

四、应用适配

对汉语无影响,对阿拉伯语、老挝语、缅甸语、泰米尔语、古吉拉特语、卡纳达语、或泰语等语言有影响。elegantTextHeight 属性被废弃,开发者设置该属性将不起作用,默认字体效果与以Android 15(API 级别 35)为目标平台的应用的字体效果一致。

四、总结

以上就是Android 16应用适配指南全篇。当然在APP提交Google Play前,需要尽可能在多种不同 Android 版本的设备上进行充分测试,覆盖低版本到高版本,尽可能模拟用户的真实使用场景,检查应用在不同系统版本上的功能完整性、稳定性和兼容性等。

最后,希望Android 16应用适配指南对各位出海大佬们有所帮助,上架Google Play都能顺顺利利!