App和SDK开发必看 | 个推分享Android12适配指南

768 阅读13分钟

前言

10 月 4 日,谷歌将Android12源代码推送至 Android 开源项目 (AOSP)。自从2021年2月发布Android12第一个预览版以来,历经9个月时间测试和优化,正式版本的Android12终于来了!不仅在UI方面做了不少升级,Android12对个人隐私安全的保护也得到了进一步增强。整体来讲,Android12更加智能、高效和安全,感兴趣的开发者可以登录官网下载源码测试学习。

个推服务开发者多年,打磨SDK产品的同时,一直密切关注和跟进行业发展趋势。Android12稳定版发布后,我们使用模拟器进行了研究和适配测试。本文将从安全变更、权限变化、性能更新等方面来谈谈 Android12 新特性,以帮助开发者更快速、更便捷地上手适配Android新系统。

安全变更

01 更安全的组件导出

从事Android开发的同学都知道,Android有四大组件,分别是活动(Activity)、服务(Service)、广播接收器(Broadcast Receive)和内容提供器(Content Provider)。Activity组件为用户提供可视化操作界面;服务组件在后台运行,支撑各类功能的实现;广播接收器顾名思义主要用于接受各种广播;内容提供器主要支持多个应用中存储和读取数据,相当于一个数据库。

这四大组件赋予了App各种各样丰富的功能,因此无论是对App还是用户来讲,它们的安全性都至关重要。**在App开发过程中,会有一些特定需求使用到第三方SDK,如支付、消息推送等,这些都会涉及到组件导出的问题。**为保护隐私以及改善整体用户体验,Android12对组件的导出有了更严格的要求。

使用Android12的开发者需要关注,**如果您对四个组件配置了intent 过滤器,则务必要在代码中显式声明android:exported 属性。**如果未设置该属性,那应用将无法安装在 Android12 上。

android:exported 属性声明代码示例:

<service android:name="com.example.app.backgroundService"         
    android:exported="false">    
    <intent-filter>        
    <action android:name="com.example.app.START_BACKGROUND" />    
        </intent-filter>
</service>

02 PendingIntent

PendingIntent是一种特殊的Intent,和Intent的区别在于Intent是立刻执行的,而PendingIntent不是,可以被理解为一种异步处理机制。PendingIntent执行的操作实质上是参数传进来的Intent操作,像通知栏消息的发送就是使用PendingIntent实现。

为了提升应用的安全性,Android12新特性要求应用创建的每个PendingIntent对象都要指定可变性, 使用PendingIntent.FLAG_MUTABLE 或PendingIntent.FLAG_IMMUTABLE标志。

PendingIntent pendingIntent = PendingIntent.getActivity(
getApplicationContext(), 
REQUEST_CODE, 
intent, 
PendingIntent.FLAG_IMMUTABLE);

如果不设置任一可变性标志, 系统将会抛出IllegalArgumentException异常,报错内容如下:

java.lang.IllegalArgumentException: 
XXX: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.

03 不安全的intent启动

而对于一般的intent而言,开发者如何确保其安全性呢?Android12为开发者们提供了一种调试功能,如果应用以不安全的方式启动intent,此功能将会发出警告。具体实现也比较简单,开发者在application中添加以下代码即可:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {    
    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()        
    // Other StrictMode checks that you've previously added.        
    // ...        
    .detectUnsafeIntentLaunch().penaltyLog()        
    // Consider also adding penaltyDeath()        
    .build());
}

这里要非常注意嵌套intent。嵌套intent是在其他intent中作为extra传递的intent。如果有以下行为,系统将发生StrictMode违规警告:

从intent的extra中解析提取嵌套intent。

使用该嵌套intent启动应用组件,例如将intent传递给startActivity()。

关于嵌套 intent 安全问题, 这篇文章写得比较详细,感兴趣的开发者可以深入阅读:blog.csdn.net/m0\_5754698…

隐私保护

为了更好地保障用户的个人信息,Android12引入了大致位置选项、应用休眠、ADB备份限制等新的隐私功能。

01 大致位置选项

Android12之前,用户在授予位置信息访问权限时只能允许系统层面的设置,如果想要更改某一特定应用的位置权限,则需要找到相应的设置界面进行手动处理。为了更好地保护用户隐私,Android12引入了“大致位置”选项。当应用需要访问位置权限时,弹窗将会出现“确切位置”和“大致位置”两个选项供用户进行授权:

  • 确切位置,通常精确到几米之内。

  • 大致(粗略)位置,一般为几百米。

位置信息一共有三种授权方式:仅在使用该应用时允许、仅限这一次和不允许。个推对这些授权方式进行了实测:

如果Manifest 配置了且请求了Manifest.permission.ACCESS_COARSE_LOCATION位置弹框:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {    
    if (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {        
        requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1);    
    }
}

在前端界面将会出现弹框如下:

Android12要求,必须同时申请大致(粗略)位置和确切位置的权限,才能完成ACCESS_FINE_LOCATION 确切位置权限的授权。所以,如果Manifest没有配置<uses-permissionandroid:name="android.permission.ACCESS_FINE_LOCATION" />但是请求Manifest.permission.ACCESS_FINE_LOCATION 弹框, 则前端界面会弹出和 情况一致的弹框,即“大致(粗略)位置弹框”。

如果Manifest配置了 和<uses-permissionandroid:name="android.permission.ACCESS_COARSE_LOCATION" /> ,且同时请求两个位置权限的弹框:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {    
    if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {        
        requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);    
    }
}
// 和以下代码调用, 弹框一致
requestPermissions(new String[] {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, 1);
@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
}

在前端界面将会出现弹框如下:

在第③种情况下,根据用户的选择,会出现以下几种情况:

a. 如果用户选择了“确切位置”, 同时选择了“仅在使用该应用时允许”或者“仅限这⼀次”,onRequestPermissionsResult 参数int[] grantResults 两个返回值均为0,表示已授权,应用重启后权限将仍保持授权状态。

★ 也就是说,“仅限这一次”并非严格意义上的仅限这一次授权。对当前应用完成授权后,下次重启该应用将仍是已授权状态。

b. 如果位置选择了“大致位置”, 同时选择了“仅限这⼀次”, onRequestPermissionsResult参数int[] grantResults返回值为0 和-1,意味着此次“大致位置”已经授权, 但“确切位置”仍未授权。下次启动再次调用requestPermissions(new String[] {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, 1), 则弹框如下: 从“大致位置”改为“确切位置”弹框询问,此时“大致位置”已默认授权。

  • 如果用户选择 “继续使用大致位置”, 则int[] grantResults 返回值仍为0和-1 , 下次请求还会出现同样弹框内容。

  • 如果选择“仅限这一次”, 则int[] grantResults返回值为0和0 ,下次启动应用时,两个位置权限将仍保持授权状态。

以上测试总结如下:

关于权限申请这块,Google给我们提供了很好的示例, 以下代码, 感兴趣的开发者可以根据“位置”选择和“选项列表”选择分别调试看看效果。更多关于Android12的位置权限变更内容,可参考:developer.android.com/about/versi…

ActivityResultLauncher<String[]> locationPermissionRequest =        
    registerForActivityResult(new ActivityResultContracts                        
        .RequestMultiplePermissions(), result -> {                    
        Boolean fineLocationGranted = result.getOrDefault(                            
        Manifest.permission.ACCESS_FINE_LOCATION, false);                    
        Boolean coarseLocationGranted = result.getOrDefault(                            
        Manifest.permission.ACCESS_COARSE_LOCATION,false);                    
        if (fineLocationGranted != null && fineLocationGranted) {                        
            // Precise location access granted.                    } 
    else if (coarseLocationGranted != null && coarseLocationGranted) {                        
    // Only approximate location access granted.                    } else {                       
    // No location access granted.                    
        }                
       }        );
locationPermissionRequest.launch(new String[] {        
Manifest.permission.ACCESS_FINE_LOCATION,        
Manifest.permission.ACCESS_COARSE_LOCATION});

Android12的位置权限变更对旅游出行、地图导航、酒店预订等App影响较大。尤其是在“个保法”正式实施的背景下,App开发者更要关注系统和相关法律法规对位置信息授权方式的要求,做好个人信息安全的有效保护。****比如,App需通过弹窗等显著的方式向用户申请个人信息的授权,在用户隐私条款中明确相关信息的用途、保存方式、处理方式等。

02 应用休眠

此外,Android12还对Android11 “自动撤销权限”功能进行了升级,引入了“应用休眠”功能。App自动休眠功能代码由谷歌于今年一月份在AOSP系统项目中提交。这项功能可以让用户自行标记出一个应用列表,受标记的应用如果几个月未被使用,则系统会自动取消其权限、停止各种后台通知,将该应用置于休眠状态,以省电并移除其占有的应用空间。Android12的该功能不仅有助于释放手机存储空间,还将极大提升用户体验。

★对于用户交互频率较低的特定类型应用而言,开发者可以调用包含 Intent.ACTION_APPLICATION_DETAILS_SETTINGS intent 操作的intent,向用户发送请求,让其准许应用免于休眠和自动重置权限限制。

03 ADB备份限制

同时,为了帮助保护私有应用数据,Android12还更改了adb backup命令的默认行为。对于以Android12为目标平台的应用,当运行adb backup命令时,从设备导出的其他任何系统数据都不会包含应用的数据。

如果测试或开发工作流程依赖于使用adb backup的应用数据,则可以选择在AndroidManifest文件中将android:debuggable设置为true来导出应用数据。注意,release线上版本务必将android:debuggable设置为false。

性能更新

性能优化是Android系统每次版本更新的重点内容。Android12改进了应用程序启动时间并优化了I/O,以加快应用程序加载速度。同时,为改进用户体验,Android12对通知trampoline、前台服务启动以及闹钟权限等均进行了限制。

01 通知trampoline限制

当用户点击通知后,会启动一个组件来响应用户的点击操作, 一般最终会打开一个界面。这个界面组件就是通知trampoline。

考虑⼀种情形, 如果通知构建的PendingIntent使用了PendingIntent.getService或者 PendingIntent.getBroadcast, 那么,当打开通知时后台可能需要做一些耗时操作或者请求网络等, 等处理完成之后再跳转到目标页面。但有时候网络、耗时操作等各种原因会造成等候时间较长, 进而导致界面弹出较晚, 以致几秒后才跳转出⼀个页面, 这种体验相对而言比较差。

为了改进用户体验,以Android12为目标平台的应用无法从用作通知 trampoline的服务或者广播中启动activity,也就是说应用构建的通知 setContentIntent()参数必须是 PendingIntent.getActivity。

个推对该功能进行了测试:

  • 创建通知, setContentIntent() PendingIntent参数使用 PendingIntent.getService或者PendingIntent.getBroadcast构建 PendingIntent;

  • 通知触发的目标组件, 也就是点击通知后启动的Service或者Broadcast, 启动某个Activity;

  • 弹出通知后, 应用切换到后台, 此时再点击通知, 会报以下错误, 且最终页面无法被启动

    system_process E/NotificationService: Indirect notification activity start (trampoline) from com.gt.brand.push.tst1 blocked ... system_process E/ActivityTaskManager: Abort background activity starts from 10146

对于大部分App开发者来讲,实现通知trampoline的兼容,只需要将应用构建的通知 setContentIntent()参数修改为 PendingIntent.getActivity即可。

但是对于有特殊功能需求的开发者来讲,如果之前的业务逻辑是点击通知以后需启动服务/广播,并在服务/广播中完成⼀些动作(比如打点或者发送回执)后才启动目标 Activity,那么直接将setContentIntent()参数改为 PendingIntent.getActivity,同时这个目标Activity正好是第三方页面的话,点击通知后,直接启动的就是第三方页面,而开发者自身的业务需求则将无法得到处理。

针对此情况,个推提出了两个可参考的方案:

在SDK中新增⼀个透明中转 Activity, 通知点击启动这个透明Activity 之后, 在透明Activity onCreate 方法中再启动目标服务或者广播, 后续逻辑保持⼀致。theme务必配置成android:style/Theme.Translucent.NoTitleBar, 透明activity务必记得要在onCreate最后调用finish方法销毁这个页面。

<activity              
    android:name="透明中转 activity"              
    android:excludeFromRecents="true"              
    android:exported="false"              
    android:taskAffinity="xxx"              
    android:theme="@android:style/Theme.Translucent.NoTitleBar" >          
</activity>

新增一个BaseActivity, 在BaseActivity完成业务逻辑,目标Activity继承这个BaseActivity, 并让目标Activity调用super方法。

相比第二种方案,第一种方案对客户来讲无需做额外处理,集成起来更加方便。因此,个推SDK产品使用的就是第一种方案进行Android12适配。

02 前台服务启动限制

除了一些特殊情况外,以 Android12 为目标平台的应用将无法在后台运行时启动前台服务。如果应用尝试在后台运行时启动前台服务,将会引发异常。针对此情况,Android12官网也给出了前台服务的推荐替代方案:WorkManager。感兴趣的开发者可进入官网了解详情:developer.android.google.cn/about/versi…

03 精确的闹钟权限

闹钟是应用安排定时工作的重要方式。在大多数情况下,应用应该使用非精确闹钟(inexact alarms),这样可以减少电池消耗。然而对于提供时间管理、日程安排等服务的App而言,必须使用精确的闹钟权限才能实现相关功能。精确闹钟功能非常方便可靠,但也会加大电量消耗。为增加用户的自主权,提升用户体验,Android12 对精确的闹钟权限进行了限制。

在Android12系统下,开发者如果想要使用精确闹钟,则需要****在 AndroidManifest.xml 文件中添加该权限的申请:

  • setAlarmClock()

  • setExact()

  • setExactAndAllowWhileIdle()

如下图,用户在 设置>应用>特殊应用权限>闹铃和提醒 可以看到需要精确闹钟权限的应用列表:

并且,用户可以为某一特定应用手动关闭或打开该权限:

总结

Android12的新特性还有很多,比如添加了 SplashScreen API,让App开发者可以自主设置启动应用的画面主题和外观;提供通知的丰富图片支持,使开发者可以通过在 MessagingStyle() 和 BigPictureStyle() 通知中提供动画图片来丰富应用的消息通知体验。总的来讲,Android12带来了很多酷炫的功能,也为用户提供了更多的自主权,尤其注重用户隐私的保护和个人信息的安全。更多Android12更新要点,开发者可进入Android12官网进一步了解:developer.android.google.cn/about/versi…

针对Android12的新特性,个推消息推送SDK已完成了适配测试。开发者用户可联系

@个推技术支持 了解个推消息推送SDK对Android12的兼容进展。

扫码添加@个推技术支持

后续,个推还将持续关注安卓系统和行业发展动态,与开发者们交流相关开发知识和技术原理,共同推进移动互联网的快速发展。