
与早期版本一样,Android 12包含可能会影响您的应用程序的行为更改。以下行为更改仅适用于以Android 12或更高版本为目标的应用。如果您的应用程序针对Android 12,则应在适用的情况下修改您的应用程序以正确支持这些行为。
前言
2021年2月18号
Android 12 的第一个开发者预览版(DP1 - Developer Preview 1)正式发布
之后,在3月3日发布了内部版本为SPP1.210122.022
的DP1.1
能支持该系统的最低Pixel设备也就是Pixel 3
了
这个周末 终于能腾出一点时间 写点笔记了 : )
来吧,这次一起来简单的看看 目标版本为12的相关行为变化
文档基于: Android 12 DP1 / DP1.1
运行环境: Windows 10 、Android Studio 4.1.2 、 Android Emulator API S
安全
组件暴露安全问题
如果您的应用以Android 12为目标并且包含使用intent filters的activities,services以及broadcast receivers ,则必须为这些应用组件显式声明android:exported
属性。
警告:一个activity, service, 或者 broadcast receiver 使用了 intent filters , 但是并没有显式声明android:exported属性值,您的app将无法被安装到运行android 12的设备上
当使用Android Studio时,你试图去安装该app, Logcat将会展示如下的错误消息:
1 | Installation did not succeed. |
如果您的应用在需要时未声明android:exported的值,则Logcat提供以下错误消息:
1 | Targeting S+ (version 10000 and above) requires that an explicit value for \ |
以下代码段显示了一个包含意图过滤器且已针对Android 12正确配置的服务示例:
1 | <service android:name="com.example.app.backgroundService" |
示例
创建一个 targetSdkVersion “S” 的新项目
- CASE 1 不添加 android:exported 属性
项目构建完成,默认的情况下activity下是没有 android:exported 属性
1 | <activity android:name=".MainActivity"> |
运行项目,Run
窗口出现错误信息
Logcat
窗口 下显示错误日志
设备上没有应用安装成功 ~~ Hmm….
- CASE 2 添加上 android:exported 属性
添加 android:exported=”false” 到 MainActivity 节点内
1 | <activity |
运行项目,,Run
窗口出现如下成功消息
手机上的APP没有启动起来,但是安装成功了
然后手动去点击APP,启动它, 提示 应用未安装
切换下Logcat
到Error
看看有没有什么发现? 抛出了 安全性异常 ….
?????????????? TM 居然运行一个例子跑不起来? WTF !!!
好吧 ~ 你赢了! 那可能我的打开方式不对吧,重新再试一次
false
不行,那我试试 true
1 | <activity |
走你 Run
Hmmm … 第一个Android S 的例子,可算把你成功运行起来了。
那么,问题来了 ?
Q : 为什么 MainActivity 必须要设置 android:exported=”true” ?
A : 略.(留给大家回答了)
程序运行起来了,我们继续后面的内容
Pending intents 必须声明可变性
如果您的应用程序针对Android 12,则必须指定PendingIntent应用程序创建的每个对象的可变性。此附加要求可提高应用程序的安全性。
要声明给定PendingIntent
对象是可变的或不可变的 ,请分别使用 PendingIntent.FLAG_MUTABLE 可变标志 或 PendingIntent.FLAG_IMMUTABLE 不可变标志。如果您的应用尝试在PendingIntent
未设置任何可变性标志的情况下创建对象,则系统将抛出 IllegalArgumentException,并且Logcat中将显示以下消息:
1 | PACKAGE_NAME: Targeting S+ (version 10000 and above) requires that one of \ |
尽可能创建不可变的 Pending intents
在大多数情况下,您的应用应创建不可变的PendingIntent
对象,如以下代码片段所示。如果PendingIntent
对象是不可变的,则应用程序无法修改意图以调整调用意图的结果。
1 | val pendingIntent = PendingIntent.getActivity(applicationContext, |
但是,某些应用程序需要创建可变PendingIntent
对象,而不是:
- 一个通知中直接回复动作需要改变的剪辑数据PendingIntent是与答复相关联的对象。通常,您可以通过将
FILL_IN_CLIP_DATA
标记作为fillIn()方法传递给方法来 请求此更改。
- 如果您的应用 使用a将对话放入气泡中
PendingIntent
,则意图应该是可变的,以便系统可以应用正确的标志,例如 FLAG_ACTIVITY_MULTIPLE_TASK 和 FLAG_ACTIVITY_NEW_DOCUMENT。
如果您的应用创建了可变PendingIntent
对象,则强烈建议您使用明确的意图 并填写 ComponentName
。这样,每当另一个应用程序调用PendingIntent并将控制权传递回您的应用程序时,该应用程序中的同一组件始终会启动。
测试可变性Pending intents 更改
要确定您的应用是否缺少可变性声明,请在Android Studio中查找以下lint警告:
1 | Warning: Missing PendingIntent mutability flag [UnspecifiedImmutableFlag] |
在开发者预览期间,您可以通过关闭 PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED 应用程序兼容性标志来禁用此系统行为以进行测试。
示例
在 MainActivity 中创建一个意图通知
1 | //创建一个意图 |
因为缺少可变性声明,没有声明,抛出异常
FLAG_IMMUTABLE
(不可变性) 声明 @RequiresApi(23)
1 | val pIntent = PendingIntent.getActivity(this, 1, intent, /*flags*/ PendingIntent.FLAG_IMMUTABLE) |
可变性声明: 指示创建的PendingIntent应该是不可变的标志。这意味着传递给send方法以填充此intent的未填充属性的其他intent参数将被忽略。FLAG_IMMUTABLE仅限制了更改send()由的调用者 发送的意图的语义的能力send()。PendingIntent的创建者始终可以通过来更新PendingIntent本身FLAG_UPDATE_CURRENT。
FLAG_MUTABLE
(可变性) 声明 @RequiresApi(31)
1 | val pIntent = PendingIntent.getActivity(this, 1, intent, /*flags*/ PendingIntent.FLAG_MUTABLE) |
可变性声明: 指示创建的PendingIntent应该是可变的标志。此标志不能与组合FLAG_IMMUTABLE。
直到 Build.VERSION_CODES.R,除非FLAG_IMMUTABLE已设置,否则默认情况下都假定PendingIntents是可变的。从开始Build.VERSION_CODES.S,将需要使用(@link #FLAG_IMMUTABLE}或FLAG_MUTABLE。显式指定创建时PendingIntents的可变性。强烈建议FLAG_IMMUTABLE在创建PendingIntent时使用。FLAG_MUTABLE仅当某些功能依赖于修改基础意图时才应使用。例如需要与内联回复或气泡一起使用的任何PendingIntent。
性能
前台服务通知不允许在后台开启
除了一些特殊情况,以Android 12为目标的应用在后台运行时将无法再启动前台服务。如果应用程序在后台运行时尝试启动前台服务,则会发生异常(少数特殊情况除外),考虑在您的应用程序在后台运行时使用WorkManager调度和开始工作。
示例
创建一个前台服务
1 | package com.xw.androidstest |
添加权限
1 | <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> |
在MainActivity 中调用
1 | override fun onCreate(savedInstanceState: Bundle?) { |
程序运行起来了,然后按Home键回到launcher, 将app切到后台 等待效果。
无法从服务或广播接收者创建通知跳板
当用户与通知交互时,某些应用程序会通过启动应用程序组件来响应通知点击,该组件最终会启动用户最终看到并与之交互的Activity。此应用程序组件称为通知跳板。
为了提高应用程序性能和UX,面向Android 12的应用程序无法从用作通知蹦床的服务或 广播接收器启动活动 。换句话说,用户点击通知或通知内的操作按钮后,应用程序无法在 Service 或 BroadcastReceiver 内调用startActivity()
当您的应用尝试从充当通知蹦床的服务或广播接收器启动活动时,系统将阻止该活动启动,并且Logcat中将显示以下消息 :
1 | Indirect notification activity start (trampoline) from PACKAGE_NAME, \ |
更新您的应用
如果您的应用从充当通跳板的服务或广播接收器启动活动,请完成以下迁移步骤:
创建PendingIntent与以下活动之一关联的对象:
- 用户点击通知后看到的Activity(首选)。
- 跳板Activity或启动用户点击通知后看到的Activity的Activity
使用
PendingIntent
在上一步中创建的对象作为构建通知的一部分。
切换行为
在开发人员预览版中测试应用程序时,可以使用NOTIFICATION_TRAMPOLINE_BLOCK
应用程序兼容性标志启用和禁用此限制。
示例
新建了一个服务、一个广播、一个MainActivity2 , 分别在服务和广播里面去启动 MainActivity2
MyService.kt
1 | class MyService : Service() { |
MyReceiver.kt
1 | class MyReceiver : BroadcastReceiver() { |
MainActivity2.kt
1 | class MainActivity2 : AppCompatActivity() { |
最后分别在MainActivity里进行两种情况处理
1 | //创建一个服务意图 点击通知栏消息启动服务 |
通过shell
切换行为:
1 | adb shell am compat enable 167676448 com.xw.androidstest |
1 | 2021-03-13 23:58:24.356 533-1591/system_process I/ActivityTaskManager: START u0 {flg=0x10000000 cmp=com.xw.androidstest/.MainActivity2} from uid 10177 |
自定义通知变更
- 原状:以前,自定义通知可以使用整个通知区域,并提供自己的布局和样式。这导致可能会混淆用户或导致不同设备上的布局兼容性问题。
- 现状:针对 targetSdkVersion Android 12,自定义通知不再使用完整的通知区域,android12 提供了系统应用标准模板;
模板可确保自定义通知与所有状态下的其他通知具有相同的修饰(人人平等),例如通知的图标和扩展提示(处于折叠状态)以及通知的图标、应用程序名称和折叠提示(处于扩展状态)。
Android 12 通过这种方式使得所有通知在视觉上保持一致,为用户提供了一个可发现的、熟悉的通知扩展。
标准模板中的自定义通知:
折叠与非折叠状态下的样式
官方建议使用了构建自定义通知的相关接口的应用,都要重新适配,相关接口有:
- setCustomContentView(RemoteViews)
- setCustomBigContentView(RemoteViews)
- setCustomHeadsUpContentView(RemoteViews)
如果您的应用使用的是完全自定义的通知,建议您尽快使用新模板进行测试并进行必要的调整:
启用自定义通知更改:
- 改变你的应用程序的targetSdkVersion,以S开启新的行为。
- 重新编译。
- 在运行Android 12的设备或模拟器上安装您的应用。
测试所有使用自定义视图的通知,确保它们在阴影中看起来像您期望的那样。
请注意自定义视图的度量。通常,自定义通知的高度要小于以前。在折叠状态下,自定义内容的最大高度已从106dp降低到48dp。同样,水平空间也更少。
为了确保“Heads Up”状态看起来像您期望的那样,请不要忘记将通知通道的重要性提高到“HIGH”(屏幕弹出)。
示例
创建一个自定义的通知
remote_view.xml
1 |
|
MainActivity.kt
1 | private fun notification() { |
运行一下效果呢:
折叠状态
展开状态
修改一下折叠后的
private fun notification() {
val mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val builder: NotificationCompat.Builder?
val channel = NotificationChannel(
"通知渠道ID",
"通知渠道名称", NotificationManager.IMPORTANCE_DEFAULT
)
channel.description = "通知渠道描述"
mNotificationManager.createNotificationChannel(channel)
builder = NotificationCompat.Builder(this, "通知渠道ID")
builder.setContentText("可可托海的牧羊人-王琪")
builder.setSmallIcon(R.drawable.ic_baseline_music_note_24)
builder.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.ic_baseline_music_note_24))
builder.priority = NotificationCompat.PRIORITY_HIGH
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://developer.android.google.cn/"))
val pIntent = PendingIntent.getActivity(this, 1, intent, PendingIntent.FLAG_IMMUTABLE)
builder.setContentIntent(pIntent)
val remoteViews = RemoteViews(this.packageName, R.layout.remote_view)
builder.setCustomBigContentView(remoteViews)
builder.setCustomHeadsUpContentView(remoteViews)
builder.setDefaults(NotificationCompat.DEFAULT_ALL)
val notification = builder.build()
mNotificationManager.notify(0, notification)
}
折叠状态
展开状态
其他目标版本为 Android 12的行为变化,请 参考官网