大家好,今天来聊一聊Android 16的新特性。
前不久,Google 25年的I/O大会刚刚结束,我也是全程进行了观看。
当然,现在Google I/O的唯一重头戏就只有AI,Android再也没有办法像过往那样占据主舞台的位置了。
但是,今年将要发布的Android 16仍然包含了许多的新特性和行为变更,对于还在关注Android平台的朋友们来说,依然是值得学习和了解一下的。
今天这篇文章我会整理出我认为的Android 16中比较重要的新特性和行为变更,可能会带有一些我个人关注点的侧重。完整版的内容,请大家还是以参考官方文档为准。
新特性
首先来看Android 16系统带来了哪些新特性。
发版规则
往年的Android系统发布,一般都会经历Developer Preview、Beta、Release Candidate、Stable Release这样的几个周期。通常Developer Preview版本会在当年的2~3月份左右发布,Stable Release则一般在当年的10~11月份左右发布。
而今年的Android 16则改变了这一传统的发版规则。
Android 16今年会发布两个版本,第二季度会发布一个Major Release版本,第四季度会发布一个Minor Release版本,如下图所示。
Major Release版本会包含所有你需要适配的行为变更,并且这个版本就是最终的Stable版本,不像往年同时间段发布的Beta版本的API还有可能会发生变化。
Minor Release版本则会增加更多Android 16系统上专属的新功能。需要注意的是,Minor Release版本只会包含新功能,而不会有任何新的行为变更。这样就保证了我们不需要在适配完了Android 16 Major Release版本之后,到了Minor Release版本又需要再适配一次的情况。
另外,由于Android 16中会包含两个版本,在版本号判断的代码方面也会发生一些变化。
如果你只想判断Major Release的版本号,那么就和过去的判断方法保持一致即可。Android 16的代号是BAKLAVA,所以代码如下所示。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
// Use APIs introduced in Android 16
}
值得一提的是,一轮字母表的甜品代号终于被Android系统全用完了。所以从Android 16开始,貌似又从头开启新一轮的甜品代号了。
而如果你想要判断更加精确的Android 16版本,具体到是Major Release还是Minor Release,那么就可以使用Android 16新增的SDK_INT_FULL和VERSION_CODES_FULL API了,代码如下所示。
if (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.[MAJOR or MINOR RELEASE]) {
// Use APIs introduced in a major or minor release
}
最后,你还可以使用如下代码来精准获取到Minor Release的版本号。
val minorSdkVersion = Build.getMinorSdkVersion(VERSION_CODES_FULL.BAKLAVA)
以进度为中心的通知
Android 16加入了全新的进度类型的通知功能,名字叫作Progress-centric notifications,直译就是以进度为中心的通知。
Android之前也是允许在通知栏中显示进度的,如很多软件在下载更新时都会在通知栏显示当前的下载进度。
然而,过去Android系统对通知栏进度条的样式支持非常有限,只能显示一个简单的ProgressBar而已。
Progress-centric notifications则让通知栏的进度条样式变得丰富多彩起来,拥有诸多可定制的功能。
如导航软件可以在通知栏进度条上显示一些自定义的图标。
除此之外,我们还可以将整个通知进度分成多个段,每段用不同颜色表示,像导航软件就可以用不同颜色来表示路段的拥挤程度。
还可以在某个进度位置加入特定颜色的点,像导航软件就可以用这个功能来表示途经点。
而上实现上述效果其实也很简单,只需要在Android 16系统中使用如下代码就可以了。
Notification.ProgressStyle()
.setStyledByProgress(false)
.setProgress(456)
.setProgressTrackerIcon(Icon.createWithResource(appContext, R.drawable.ic_car_red))
.setProgressSegments(
listOf(
Notification.ProgressStyle.Segment(41).setColor(Color.BLACK),
Notification.ProgressStyle.Segment(552).setColor(Color.YELLOW),
Notification.ProgressStyle.Segment(253).setColor(Color.WHITE),
Notification.ProgressStyle.Segment(94).setColor(Color.BLUE)
)
)
.setProgressPoints(
listOf(
Notification.ProgressStyle.Point(60).setColor(Color.RED),
Notification.ProgressStyle.Point(560).setColor(Color.GREEN)
)
)
其中,setProgressTrackerIcon函数用于设置自定义的图标,当时进度走到了哪里,这个图标就会跟着移动到哪里。
setProgressSegments函数用于设置分段。我们可以传入多个分段,并通过Segment的构造函数来指定该分段的长度,通过setColor函数来指定分段的颜色。
setProgressPoints用于设置点。我们也可以传入多个点,并通过Point的构造函数来指定点的位置,通过setColor函数来指定点的颜色。
用法大概就是这些,很简单,接下来就需要你自己思考如何利用这个功能让你的通知栏变得更加美观实用了。
更好的Job自省
JobScheduler主要用于执行后台的定时任务。
但是很多朋友应该都遇到过JobScheduler添加的后台任务并不能正常运行的情况,中国区的开发者应该更加感同身受。
为了方便大家理解为什么添加的后台任务不能正常运行,Android 14系统中添加了getPendingJobReason(int jobId)这个API,它的返回值会告诉你该后台任务不能正常运行的原因。
但实际上,JobScheduler添加的后台任务是否能够运行,是由多种原因综合决定的。比如说无法运行的原因,有可能是任务自身设定的约束条件导致的,也有可能是系统隐式的约束条件导致的。
那么为了更加方便开发者调试JobScheduler,Android 16添加了getPendingJobReasons(int jobId)这个API,它的返回值变成了一个int数组,里面会包含后台任务不能正常运行的所有原因。
目前,可能导致JobScheduler后台任务无法正常运行的原因一共有如下可能。
PENDING_JOB_REASON_UNDEFINED
PENDING_JOB_REASON_APP
PENDING_JOB_REASON_APP_STANDBY
PENDING_JOB_REASON_BACKGROUND_RESTRICTION
PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW
PENDING_JOB_REASON_CONSTRAINT_CHARGING
PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY
PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER
PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE
PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY
PENDING_JOB_REASON_CONSTRAINT_PREFETCH
PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW
PENDING_JOB_REASON_DEVICE_STATE
PENDING_JOB_REASON_EXECUTING
PENDING_JOB_REASON_INVALID_JOB_ID
PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION
PENDING_JOB_REASON_QUOTA
PENDING_JOB_REASON_USER
PENDING_JOB_REASON_CONSTRAINT_DEADLINE
动态刷新率
现在的Android手机已经越来越高端了,从一开始的只支持60帧的刷新率,到后来旗舰手机开始支持120帧刷新率,再到现在,高端手机已经能够支持动态刷新率了。
动态刷新率指的就是,手机会根据当前的使用场景来自动决定使用多高的刷新率。因为在一个接近静态的页面使用120帧的刷新率其实无非就是在浪费资源而已,动态刷新率可以让手机在性能和能耗之间找到一个最佳的平衡点。
其实早在Android 5系统时,Google就提供了一个Display#getSupportedRefreshRates()函数,用于获取当时设备支持的刷新率是多少。
但是这个函数在Android 15及以下系统只能获取到当前设备默认的刷新率是多少,只有在Android 16及以上系统调用,才会返回当前设备支持的所有刷新率。
Android 16新增了一个Display#hasArrSupport()函数,用于判断当前设备是否支持动态刷新率。以及增加了一个Display#getSuggestedFrameRate(int category)函数,用于根据传入的使用场景,来获取系统推荐的刷新率是多少。
RecyclerView从1.4版本开始已经接入了动态刷新率功能,这也就意味着,使用1.4版本的RecyclerView在支持动态刷新率的手机上将会拥有更好的性能和更低的能耗。
更多Jetpack的库在未来也会加入动态刷新率的支持。
改进了Photo picker
Photo picker这个功能最早是在Android 13系统时引入的。
当时我在写Android 13新特性文章的时候还重点介绍了这个功能的用法,详情可以参考这篇文章 Android 13 Developer Preview一览 。
而这次Android 16系统,Google对Photo picker进行了两大改进。
第一个改进是,允许将Photo picker嵌入到你的App当中,让它看起来就像是你App的一部分。因为之前的Photo picker都是要打开一个专属的对话框,让用户在对话框里选择照片,这可能会让用户有种跳出当前App的感觉。
第二个改进是,增加了搜索API,允许用户在Photo picker中对照片进行搜索。不过这个功能依赖于Google云服务,在国内应该是用不了。
关于Photo picker的改进,Google在官方文档中只是做了介绍,没有具体的代码示例,所以今天这篇文章我也就只简单介绍下它的新功能,后续我可能会专门写一篇文章来讲解Photo picker新功能的具体用法。
高级保护模式
在很多用户的印象当中,Android手机就是没有苹果手机安全。
当然,这在很多方面也确实是事实,因为Android手机要远比苹果手机更加开放,更加开放的另外一面也意味着用户可能会面临更高的安全风险。
是选择更加开放还是更加安全,不同用户的答案可能也会各不相同。但是这次Android好像给出了一个能让双方都满意的答案,那就是:我都要。
Android 16引入了一个新的高级保护模式,用户可以根据自己的需求来决定是否打开这个高级保护模式,打开之后,你的Android手机将会变得比苹果手机更加安全。Google会在设备、应用、网络、网页、通信等方面全面开启非常严格的安全检查。
比如说,很多用户觉得苹果手机安全是因为只能从App Store安装软件,而Android则可以侧载安装。开启了高级保护模式之后,你的Android手机也只能从Google Play Store安装软件了。
当然这只是比较基础的保护方式,除此之外,高级保护模式还会对钓鱼链接、诈骗电话、垃圾邮件、JavaScript脚本、2G网络限制等等方面进行防护。更加详细的内容请参考下面的官方文档进行查看。
那么这个功能对于我们Android开发者来说有什么用呢?
Google在Android 16中提供了几个API,允许开发者来查询当前设备是否启用了高级保护模式。
这样,如果有些App对安全等级的要求特别高,比如银行类的App,在执行某些敏感业务操作时,可以要求用户必须打开高级保护模式才可以进行下一步操作,从而大幅提升业务的安全性。
如果要查询当前设备是否启用了高级保护模式,首先需要声明如下权限:
<uses-permission android:name="android.permission.QUERY_ADVANCED_PROTECTION_MODE" />
接下来调用如下API即可查询到高级保护模式的相关信息。
public class AdvancedProtectionManager() {
// Check the current status
public boolean isAdvancedProtectionEnabled();
// Be alerted when status changes
public void registerAdvancedProtectionCallback(Executor executor, Callback callback);
public void unregisterAdvancedProtectionCallback(Callback callback);
}
public class Callback() {
// Called when advanced protection state changes
void onAdvancedProtectionChanged(boolean enabled);
}
了解完了Android 16的一些新特性,下面我们来看Android 16的行为变更。
影响targetSdk >= 36的行为变更
行为变更分为两种情况,一种是当你的App指定的targetSdk >= 36(Android 16)时会受到影响的行为变更,另一种是所有App都会受到影响的行为变更。
我们先来看第一种。
edge-to-edge无法再关闭
edge-to-edge是去年Android 15中一项强制的行为变更,只要是targetSdk >= 35的App,内容将会自动延伸到整个手机屏幕,包括状态栏和导航栏。
去年我花了不少时间在介绍Android 15的edge-to-edge上面,包括文章就写了两篇,还去参加北京和海口的GDG活动,演讲了关于edge-to-edge的内容。
如果还不了解edge-to-edge的朋友,可以参考我之前写的文章 Android 15新特性,强制edge-to-edge全面屏体验 。
然而,去年我没讲的是,其实Google还是留了一个后门,允许我们临时关闭强制edge-to-edge这个行为变更,那就是借助R.attr#windowOptOutEdgeToEdgeEnforcement这个属性,并将它设置成true即可。
如果你从来没有听说过这个属性,没有任何关系,因为你马上就再也不会用到它了。
从Android 16开始,将再也没有任何办法临时关闭edge-to-edge,所以如果你的App还没有完成edge-to-edge的适配工作,请尽快参考上面的文章链接完成适配。
预测性返回手势适配
预测性返回手势这个功能,在经历过Android 13、14、15的超长版本迭代之后,终于在Android 16上彻底默认启用了。
这个功能主要就是让用户能够提前预览即将返回到的界面,从而减少误操作率,并提升一定的用户体验。
具体如何去适配预测性返回手势功能,可以参考我之前发布的文章 Android 15新特性,预测性返回手势 。
需要注意的是,这次Android 16默认启用了预测性返回手势功能,同时也就默认你已经使用了OnBackPressedCallback API来处理应用程序的返回事件。因此,像之前我们常用onBackPressed()函数,KeyEvent.KEYCODE_BACK事件,在Android 16上都会失效。
不过,由于预测性返回手势的适配复杂度较高,比刚才介绍的edge-to-edge更难适配,不然也不至于一个功能迭代了4年才得以推出,因此Google今年仍然还是给开发者留下了临时关闭这个功能的选项。
只需要在AndroidManifest.xml文件中将enableOnBackInvokedCallback这个属性设置为false,就可以临时关闭预测性返回手势功能,如下所示。
<application
...
android:enableOnBackInvokedCallback="false">
</application>
但不要指望这个属性还可以一直使用下去,明年的Android系统很有可能会禁用掉这个临时关闭选项,所以请大家还是尽快完成适配为好。
自适应布局
自适应布局应该是Android 16中最重磅、影响最大的一个行为变更了。
简单概括一下,那就是在Android 16的大屏设备上,开发者将无法再对App的横竖屏朝向、Activity的宽高比进行限制,一切都以用户的设置为准。
所以像下图中的这种手机兼容模式效果,在Android 16中将不再被允许。
Google的这一改动,应该是在为Android系统桌面化这一战略提前做布局。
未来,平板上的Android系统很可能会像PC系统一样,允许窗口化的应用程序,并且用户还可以随意调整每个应用程序的窗口大小,因此当前很多App的适配程度是满足不了Google的这一战略要求的。
由于自适应布局这一条行为变更的影响巨大,我已经提前写过两篇文章来详细介绍这条行为变更了。
想要了解更多内容的朋友,请参考 Android 16不再支持横竖屏设置?官方文档详尽解读 和 写给初学者的Jetpack Compose教程,大屏设备适配 。
更安全的Intent
近几年,几乎每一代Android系统都会在Intent的用法方面增加更多的限制。这些限制在让你的Android设备变得更加安全的同时,也确实让Intent的用法变得越来越难。
今年Android 16在Intent方面又做出了以下两条限制。
- 显式Intent必须与目标组件的Intent Filter相匹配。
- 没有指定action的Intent无法与任何Intent Filter匹配。
有些朋友看到这里可能要炸锅了,因为这对我们平时日常的开发影响太大了。
以前通过显示Intent来启用目标组件,只需要指定一下目标组件的名字即可,如下所示。
val intent = Intent(this, SharedActivity::class.java)
startActivity(intent)
现在既需要匹配目标组件的Intent Filter,还强制要求所有Intent都得指定action才行,那岂不是类似于上面的代码全都要失效了?
放宽心,没那么夸张。
Google每年对Intent做出的安全限制主要都是针对跨应用程序的场景,今年也不例外。
所以,如果你只是使用Intent在自己App的内部进行跳转,那么并不会有任何的影响,以前怎么用现在还可以怎么用。
但如果是使用Intent来启动另外一个App的组件,那么可能就得要遵守上面的两条变动了。
另外,这两条限制在Android 16上并不会强制开启,而是需要开发者去主动开启的。
假设你是一个需要将自己的组件暴露给外部去调用的开发者,你希望能够使用Android系统更加严格的Intent匹配检查机制来保证安全性,那么就可以使用intentMatchingFlags这个属性来要求组件的调用方必须满足Android 16最新的检查条件。
<activity
android:name=".SharedActivity"
android:exported="true"
android:intentMatchingFlags="enforceIntentFilter">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
这样,当有外部的其他App想要去启用SharedActivity时,就必须要指定action,同时Intent还要满足Intent Filter里的所有条件才行。
比如说,观察如下代码。
val intent = Intent()
intent.component = ComponentName(
"com.example.androidtest",
"com.example.androidtest.SharedActivity")
startActivity(intent)
这里我们在另一个App中使用Intent来启用刚才的SharedActivity,没有指定action和category,只指定了ComponentName,这种写法在Android 16上将变得不再可行。
影响所有App的行为变更
最后我们来看Android 16上对所有App都会有影响的行为变更,而不管它的targetSdk指定的是多少。
由于影响到的是所有App,每年这部分的行为变更通常不会太多,我们来挑几个比较重点的讲一讲吧。
有序广播优化级不再全局化
我们知道,Android中的广播分为标准广播和有序广播两种。
标准广播是异步执行的广播,所有接收方几乎会在同一时刻收到广播消息,且广播消息无法被拦截。
有序广播是同步执行的广播,每个接收方都会有相应的优先级,广播消息会按照优先级的高低依次发送给各个接收方,且高优先级的接收方可以选择将广播消息拦截掉。
由于有序广播的工作机制可能会给恶意软件留下空当,因此这次Android 16对全局有序广播做了比较大的修改。
从Android 16开始,广播接收器将无法再通过android:priority属性或IntentFilter#setPriority()函数来调整优先级,从而保证自己能够更早地收到全局有序广播,各接收方的接收顺序将变得不再确定。
不过这个调整只针对全局有序广播这种情况,如果你只在自己的App内部使用有序广播,那么将不受影响,依然可以通过调整优先级的方式来确保各广播接收器的接收顺序,并可以对广播消息进行拦截。
另外,现在广播接收器的优先级也不能再随意指定了,必须在SYSTEM_LOW_PRIORITY + 1到SYSTEM_HIGH_PRIORITY - 1之间,也就是-999到999之间。
之前有些开发者为了让自己的广播接收器能更早地收到消息,甚至会将优先级调整成整型的最大值2147483647这种离谱的数字,以后请大家不要再这样内卷了。
16KB页大小兼容模式
页是操作系统管理内存的粒度。大多数CPU支持的都是4KB的页大小,因此Android操作系统和应用程序历来都是基于4KB页大小构建和优化的。
而ARM CPU支持更大的16KB页大小,当Android使用这种更大的页大小时,能够得到更好的性能体验。
Google在Android 15中引入了16KB页大小,并建议各个App进行适配。根据Google的数据统计表明,使用16KB页大小之后,Android系统在App启用速度、耗电量、摄像头启动、系统开机速度等诸多性能指标方面均有不小幅度的提升,而付出的代价只是内存使用量略有升高。
在如今手机硬件设备越堆越高的今天,使用一点额外的内存就能换来系统整体性能的提升,看上去是一笔非常划算的交易。
那么具体如何去适配16KB页大小呢?
如果你的应用程序是纯Java或Kotlin代码开发的,那就不需要做任何改动,自动就会适配16KB页大小了。
而如果你的项目使用到了C/C++代码,或是你的项目里引入了其他第三方的so文件,就得要进行16KB页大小适配了。具体适配的方法我们不在这篇文章中展开,有需要的朋友请参阅下面的文章即可。
https://developer.android.com/guide/practices/page-sizes
以上部分都是在Android 15中就已经引入的关于16KB页大小的行为变更,那么Android 16上又有什么不同呢?
当你的App在Android 16系统上运行时,如果系统检测到你的App还没有完成16KB页大小的适配,虽然App还可以正常运行,但是会自动弹出一个如下图所示的提示框。
所以,基本上Google就是在强制推着你去做16KB页大小适配了。
但如果你的App就是非常复杂,短时间内还完成不了16KB页大小的适配工作怎么办?
不用担心,这次Android 16还引入了一个16KB页大小兼容模式。在AndroidManifest.xml文件中将android:pageSizeCompat声明成true,就可以阻止上图中的对话框弹出,并允许你的App继续以4KB页大小的模式运行了。
但明年的Android系统是否还会允许这种兼容模式就不得而知了,所以推荐大家还是尽早地完成适配工作。
预测性返回支持3按钮模式
之前预测性返回手势功能都只能在手势导航的模式下才能使用,这个很容易理解,毕竟这个功能名字就叫做预测性返回手势。
而Android 16将这个功能同时也带到了3按钮导航模式下,并且你不需要进行什么额外的适配,只要你的App之前已经适配过预测性返回手势了,那么在Android 16的3按钮导航模式下自动就会启用。
具体的操作方法是,在3按钮导航模式下,长按返回键,即可触发预测性返回效果,如下图所示。
这个效果的支持场景和手势导航模式下的预测性返回完全相同,包括了返回桌面、跨任务返回、跨Activity返回等。
结语
以上就是我总结的Android 16中比较重要的新特性和行为变更了。当然,正如我前面所说,这只是我认为的Android 16中比较重要的新特性和行为变更,完整版请大家还是参考官方文档,里面可能还会有一些你个人更加感兴趣的内容。
https://developer.android.com/about/versions/16