• Android 12 适配攻略


    疫情又又又又又来了,疫情封控居家办公已经两周了,希望疫情快点过去✊。

    接下来进入今天的主题👉 Android 12。

    首语

    2022年2月11日,谷歌发布了首个 Android 13 开发者预览版。2022年7月,发布了Beta 4版本,接下来就是Final Relase版本了。是时候适配一波Android12了,为后面项目适配铺平道路。

    介绍

    2021年2月,谷歌发布了首个 Android 12开发者预览版,2021年10月5日谷歌发布Android 12正式版。

    使用 Material You 打造的全新系统界面,富有表现力、活力和个性。使用重新设计的微件、AppSearch、游戏模式和新的编解码器扩展您的应用。支持隐私信息中心和大致位置等新的保护功能。使用富媒体内容插入功能、更简便的模糊处理功能、经过改进的原生调试功能等提高工作效率。

    行为变更

    应用启动画面

    从Android 12开始,所有的App在每次启动时(特指冷启动与温启动),系统都会为我们加上一个默认的启动画面。
    启动画面主要由4个元素组成

    • 应用图标:可以是静态或动画形式。默认情况下,使用Launcher图标。
    • 图标背景:可选,在图标与窗口背景之间需要更高的对比度时很有用。
    • 前景遮罩:可选,前景的 ⅓ 将被遮盖。
    • 窗口背景:不透明的单色,默认是所设置主题的windowBackground。
    外观更改
    • 背景填充
    <item name="android:windowSplashScreenBackground">@color/...item>
    
    • 1
    • 中心图标替换

    如果通过AnimationDrawableAnimatedVectorDrawable实现动画效果,还需要设置windowSplashScreenAnimationDuration,以在显示启动页面的同时播放动画。

    <item name="android:windowSplashScreenAnimatedIcon">@drawable/...item>
    
    • 1
    • 启动画面图标动画的时长
    <item name="android:windowSplashScreenAnimationDuration">1000item>
    
    • 1
    • 启动画面图标后面的背景
    <item name="android:windowSplashScreenIconBackgroundColor">@color/...item>
    
    • 1
    • 启动画面底部的图片
    <item name="android:windowSplashScreenBrandingImage">@drawable/...item>
    
    • 1
    • 启动画面在初始化数据加载完关闭场景
    val content: View = findViewById(android.R.id.content)
    //绘制监听
    content.viewTreeObserver.addOnPreDrawListener(
        object : ViewTreeObserver.OnPreDrawListener {
            override fun onPreDraw(): Boolean {
               return if(isFinsh){
                   //数据加载完
                   content.viewTreeObserver.removeOnPreDrawListener(this)
                   true
               }else{
                   false
               }
    
           }
       }
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 自定义关闭启动画面的动画
            getSplashScreen().setOnExitAnimationListener { splashScreenView: SplashScreenView ->
                val slideUp = ObjectAnimator.ofFloat(
                    splashScreenView,
                    View.TRANSLATION_Y,
                    0f, -splashScreenView.height.toFloat()
                )
                slideUp.interpolator = AnticipateInterpolator()
                slideUp.duration = 200L
    
                slideUp.doOnEnd { splashScreenView.remove() }
    
                slideUp.start()
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 计算动画的剩余时长
    val animationDuration = splashScreenView.iconAnimationDuration
    val animationStart = splashScreenView.iconAnimationStart
    val remainingDuration = if (animationDuration != null && animationStart != null) {
        (animationDuration - Duration.between(animationStart, Instant.now()))
            .toMillis()
            .coerceAtLeast(0L)
    } else {
        0L
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    用户使用App时会在视觉上感觉多个启动画面,可以通过Jetpack的SplashScreen库进行定制。

    implementation 'androidx.core:core-splashscreen:1.0.0-alpha01'
    
    • 1

    启动页调用splashScreen.setKeepVisibleCondition{true}使得默认的启动画面持续覆盖原有的启动页,直到广告页开始显现时,才调用splashScreen.setKeepVisibleCondition{false}让页面重新显示,以此实现平稳过渡。

    组件导出

    以Android 12为目标平台的App,如果其包含的四大组件中使用到了Intent过滤器(intent-filter),则必须显式声明 android:exported 属性,否则App将无法在Android 12及更高系统版本的设备上安装
    Manifest中Activity标签未设置android:exported属性error如下:

    As of Android 12, android:exported must be set; use true to make the activity available to other apps, and false otherwise. For launcher activities, this should be set to true.

    意思大概是:从 Android 12 开始,必须设置 android:exported;使用 true 使Activity可用于其他应用程序,否则使用 false。对于启动Activity,这应该设置为 true。
    Merged Manifest也会出现Merging Errors:

    Error: android:exported needs to be explicitly specified for . Apps targeting Android 12 and higher are required to specify an explicit value for android:exported when the corresponding component has an intent filter defined. See https://developer.android.com/guide/topics/manifest/activity-element#exported for details. Android12Test.app main manifest (this file)

    这里有个棘手的问题是:依赖的第三方库使用到了intent-filter

    解决方案:打包时会合并多个Module的AndroidManifest.xml文件,可以通过Gradle脚本,在打包过程中检索合并后的AndroidManifest.xml使用到intent-filter但没有声明android:exported属性的四大组件,动态添加android:exported属性。

    脚本方案参考如下:http://events.jianshu.io/p/1913b48f2dad

    安全和隐私设置
    大致位置

    Android提供了两种不同精确度的位置权限,分别是:

    • ACCESS_COARSE_LOCATION(大致位置)

      提供设备位置的估算值,将范围限定在大约 1.6 公里(1 英里)内

    • ACCESS_FINE_LOCATION(确切位置)

      通常将范围限定在大约 50 米(160 英尺)内,有时精确到几米(10 英尺)范围以内

    在以Android 12 系统的APP上,请求ACCESS_FINE_LOCATION权限时,系统权限对话框会提供两个选项,即允许App获取确切位置,还是仅允许获取大致位置。

    大致位置
    用户拒绝提供确切位置后,可再次请求获取确切位置。
    精确位置
    在这次请求前可给用户添加权限说明来帮助获取确切位置,当然App也要做好大概位置的业务处理。

    麦克风和摄像头切换开关

    以Android 12为目标平台的App,用户状态栏新增麦克风使用权限和摄像头使用权限,可以一键启用/停用。
    摄像头和麦克风使用权限
    如果在画面录制过程中,关闭摄像头使用权限时,录制的会是空白画面;如果在声音录制过程中,关闭麦克风使用权限时,录制的会是无声音频。

    官网提供了设备是否支持麦克风和摄像头切换开关的代码。

    val sensorPrivacyManager = applicationContext
            .getSystemService(SensorPrivacyManager::class.java)
            as SensorPrivacyManager
    val supportsMicrophoneToggle = sensorPrivacyManager
            .supportsSensorToggle(Sensors.MICROPHONE)
    val supportsCameraToggle = sensorPrivacyManager
            .supportsSensorToggle(Sensors.CAMERA)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    SensorPrivacyManager类提供麦克风和摄像头切换开关的信息,但由于是SystemApi( 调用系统API就必须要系统签名),无法调用。

    @SystemApi
    @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
    public boolean isSensorPrivacyEnabled(@Sensors.Sensor int sensor) {
       return isSensorPrivacyEnabled(sensor, UserHandle.USER_CURRENT);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果用户主动关闭了摄像头或麦克风的使用权限,那么当下次App再需要启动摄像头或麦克风时,系统就会提醒用户,相关硬件的使用权限已关闭,并申请重新开启。

    当应用使用麦克风或相机时,图标会出现在状态栏中。

    Activity生命周期

    以 Android 12 为目标平台的App,在根启动Activity(intent过滤器声明ACTION_MAIN和CATEGORY-LAUNCHER的Activity)页面按下返回按钮,系统会将 Activity 及其任务移到后台,而不是finish Activity。

    自定义通知

    以 Android 12 为目标平台的App,包含自定义内容视图的通知将不再使用完整通知区域;相反,系统会应用标准模板。为用户提供可看到且熟悉的通知展开功能,使所有通知保持外观一致且易于浏览。
    所有通知都是可展开的。通常,这意味着,如果您使用的是 setCustomContentView,则还需要使用 setBigCustomContentView,以确保收起状态和展开状态保持一致。

    权限重置

    以 Android 12 为目标平台的App,用户几个月未与App互动,系统会自动重置授予的所有权限并将您的应用置于休眠状态。

    待处理 intent 可变性

    以 Android 12 为目标平台的App,构建PendingIntent时需要指定Flag为FLAG_IMMUTABLE(建议)或FLAG_MUTABLE二者之一,否则App将崩溃并出现以下error。

    • FLAG_IMMUTABLE,指示创建的 PendingIntent 应该是不可变的标志。
    • FLAG_MUTABLE,指示创建的 PendingIntent 应该是可变的标志。
    if (Compatibility.isChangeEnabled(PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED)
                    && !flagImmutableSet && !flagMutableSet) {
                String msg = packageName + ": Targeting S+ (version " + Build.VERSION_CODES.S + " and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE  be specified when creating a PendingIntent.\nStrongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.";
                    throw new IllegalArgumentException(msg);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    对于使用的第三方库未正确指定Flag的问题,等待库更新或者使用同样功能的正确指定flag的库进行替代。

    前台服务启动限制

    以 Android 12 为目标平台的App,无法在后台运行时启动前台服务,否则会引发异常。

     if (fgRequired) {
                logFgsBackgroundStart(r);
                if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
                    String msg = "startForegroundService() not allowed due to "
                            + "mAllowStartForeground false: service "
                            + r.shortInstanceName;
                    Slog.w(TAG, msg);
                    showFgsBgRestrictedNotificationLocked(r);
                    logFGSStateChangeLocked(r,
                            FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED,
                            0);
                    if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, callingUid)) {
                        throw new ForegroundServiceStartNotAllowedException(msg);
                    }
                    return null;
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    考虑改用WorkManager在App后台运行时,启动完成加急工作。
    通过adb命令,监控App是否有后台启动前台服务的行为,一旦发现,通知栏推送一条通知,定位到代码处。

    adb shell device_config put activity_manager \ default_fgs_starts_restriction_notification_enabled true
    
    • 1
    精确的闹钟权限

    以Android 12为目标平台的App,通过AlarmManager来设置定时任务,并且设置的是精确的闹钟(使用了setAlarmClock()setExact()setExactAndAllowWhileIdle()这几种方法),需要声明SCHEDULE_EXACT_ALARM权限声明且授权,否则会有如下error。

    Caused by: java.lang.SecurityException: Caller com.yhj.Android12Test needs to hold android.permission.SCHEDULE_EXACT_ALARM to set exact alarm.
    
    • 1

    因此在AndroidManifest.xml清单文件中声明 SCHEDULE_EXACT_ALARM 权限,代码判断是否具有设置闹钟的权限。

    private val singlePermissions=registerForActivityResult(ActivityResultContracts.RequestPermission()){isGranted ->
            when {
                isGranted -> Log.e("yhj", "申请成功")
                !ActivityCompat.shouldShowRequestPermissionRationale(
                    this,
                    Manifest.permission.SCHEDULE_EXACT_ALARM
                ) -> Log.e("yhj", "拒绝且不在询问")
                else -> Log.e("yhj", "拒绝")
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    通知 trampoline 限制

    在配置通知的点按行为时,可能会通过PendingIntent来启动一个Service或BrocastReceiver。以Android 12为目标平台的App,如果尝试在Service或BrocastReceiver中内调用 startActivity(),系统会阻止该Activity启动,并在 Logcat 中显示以下消息:

    Indirect notification activity start (trampoline) from PACKAGE_NAME,
    this should be avoided for performance reasons.

    可通过adb命令识别在点按通知后,识别哪个Service或BrocastReceiver调用了startActivity(),并输出相关信息到Logcat,

    adb shell dumpsys activity service \
    com.android.systemui/.dump.SystemUIAuxiliaryDumpService
    
    • 1
    • 2
    Toast提示重新设计

    文本旁边显示应用图标,上限为两行文本。

    新功能和API

    Material You

    Android 12 引入了一种名为Material you的新设计语言,可以理解它是Material Design的替代品。

    依赖

    implementation 'com.google.android.material:material:1.8.0-alpha01'
    
    • 1

    提供一个官方文档及源码供使用学习。

    圆角API

    Android 12 引入了 RoundedCornerWindowInsets.getRoundedCorner(int position),它们可以提供圆角的半径和中心点。

    通知的丰富图片支持

    以Android 12为目标平台的App,可以通过在 MessagingStyle()BigPictureStyle()通知中提供动画图片来丰富应用的通知体验。此外,您的应用现在还可以让用户在从通知栏回复消息时发送图片消息。

    蓝牙权限

    Android 12 引入了 BLUETOOTH_SCAN(寻找蓝牙设备)、BLUETOOTH_ADVERTISE(当前设备可被其他蓝牙设备发现)和 BLUETOOTH_CONNECT(与已配对的蓝牙设备通信)权限。

    自动更新应用

    使用 PackageInstallerAPI 的应用引入了 setRequireUserAction()方法。此方法可让安装程序应用执行应用更新而无需用户确认操作。

    设备芯片组信息

    Android 12 向 android.os.Build 添加了两个常量, Build.SOC_MANUFACTURER(设备主要片上系统的制造商)和 Build.SOC_MODEL (设备上主要片上系统的型号名称)

    总结

    更多变更细节参考官网:https://developer.android.google.cn/about/versions/12

  • 相关阅读:
    VScode + PHPstudy + PHP Debug 调试PHP代码
    页面加载动画_渐隐变色旋转小圆圈
    Scrapy运行爬虫文件报错:ValueError: attempted relative import beyond top-level package
    hyperf框架聚合查询多字段查询
    【日常】一名开发人员总结的好习惯,欢迎补充
    【华为OD机试真题 python】最大花费金额 【2022 Q4 | 100分】
    从官方源码精简出第1个FreeRTOS程序
    73个产品小白必备知识,项目管理也可看
    Spring Boot框架中的@Conditional系列注解
    通讯网关软件016——利用CommGate X2Access实现OPC数据转储Access
  • 原文地址:https://blog.csdn.net/yang_study_first/article/details/126820005