Android 12 快速适配要点_android 12 需要升级agp-程序员宅基地

技术标签: Android开发  android  

Android 12 需要更新适配点并不多,本篇主要介绍最常见的两个需要适配的点:android:exportedSplashScreen

一、android:exported

它主要是设置 Activity 是否可由其他应用的组件启动, “true” 则表示可以,而“false”表示不可以。

若为“false”,则 Activity 只能由同一应用的组件或使用同一用户 ID 的不同应用启动。

当然不止是 ActivityServiceReceiver 也会有 exported 的场景。

一般情况下如果使用了 intent-filter,则不能将 exported 设置为“false,不然在 Activity 被调用时系统会抛出 ActivityNotFoundException 异常。

相反如果没有 intent-filter,那就不应该把 Activityexported 设置为true这可能会在安全扫描时被定义为安全漏洞

而在 Android 12 的平台上,也就是使用 targetSdkVersion 31 时,那么你就需要注意:

如果 ActivityServiceReceiver 使用 intent-filter ,并且未显式声明 android:exported 的值,App 将会无法安装。

这时候你可能会选择去 AndroidManifest 一个一个手动修改,但是如果你使用的 SDK 或者第三方库没有支持怎么办?或者你想要打出不同 target 平台的包?这时候下面这段 gradle 脚本可以给你省心:

com.android.tools.build:gradle:3.4.3 以下版本
/**
 * 修改 Android 12 因为 exported 的构建问题
 */
android.applicationVariants.all {
     variant ->
    variant.outputs.all {
     output ->
        output.processResources.doFirst {
     pm ->
            String manifestPath = output.processResources.manifestFile
            def manifestFile = new File(manifestPath)
            def xml = new XmlParser(false, true).parse(manifestFile)
            def exportedTag = "android:exported"
            ///指定 space
            def androidSpace = new groovy.xml.Namespace('http://schemas.android.com/apk/res/android', 'android')

            def nodes = xml.application[0].'*'.findAll {
    
                //挑选要修改的节点,没有指定的 exported 的才需要增加
                (it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(androidSpace.exported) == null

            }
            ///添加 exported,默认 false
            nodes.each {
    
                def isMain = false
                it.each {
    
                    if (it.name() == "intent-filter") {
    
                        it.each {
    
                            if (it.name() == "action") {
    
                                if (it.attributes().get(androidSpace.name) == "android.intent.action.MAIN") {
    
                                    isMain = true
                                    println("......................MAIN FOUND......................")
                                }
                            }
                        }
                    }
                }
                it.attributes().put(exportedTag, "${isMain}")
            }

            PrintWriter pw = new PrintWriter(manifestFile)
            pw.write(groovy.xml.XmlUtil.serialize(xml))
            pw.close()
        }
    }

}
com.android.tools.build:gradle:4.1.0 以上版本
/**
 * 修改 Android 12 因为 exported 的构建问题
 */

android.applicationVariants.all {
     variant ->
    variant.outputs.each {
     output ->
        def processManifest = output.getProcessManifestProvider().get()
        processManifest.doLast {
     task ->
            def outputDir = task.multiApkManifestOutputDirectory
            File outputDirectory
            if (outputDir instanceof File) {
    
                outputDirectory = outputDir
            } else {
    
                outputDirectory = outputDir.get().asFile
            }
            File manifestOutFile = file("$outputDirectory/AndroidManifest.xml")
            println("----------- ${manifestOutFile} ----------- ")

            if (manifestOutFile.exists() && manifestOutFile.canRead() && manifestOutFile.canWrite()) {
    
                def manifestFile = manifestOutFile
                ///这里第二个参数是 false ,所以 namespace 是展开的,所以下面不能用 androidSpace,而是用 nameTag
                def xml = new XmlParser(false, false).parse(manifestFile)
                def exportedTag = "android:exported"
                def nameTag = "android:name"
                ///指定 space
                //def androidSpace = new groovy.xml.Namespace('http://schemas.android.com/apk/res/android', 'android')

                def nodes = xml.application[0].'*'.findAll {
    
                    //挑选要修改的节点,没有指定的 exported 的才需要增加
                    //如果 exportedTag 拿不到可以尝试 it.attribute(androidSpace.exported)
                    (it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(exportedTag) == null

                }
                ///添加 exported,默认 false
                nodes.each {
    
                    def isMain = false
                    it.each {
    
                        if (it.name() == "intent-filter") {
    
                            it.each {
    
                                if (it.name() == "action") {
    
                                    //如果 nameTag 拿不到可以尝试 it.attribute(androidSpace.name)
                                    if (it.attributes().get(nameTag) == "android.intent.action.MAIN") {
    
                                        isMain = true
                                        println("......................MAIN FOUND......................")
                                    }
                                }
                            }
                        }
                    }
                    it.attributes().put(exportedTag, "${isMain}")
                }

                PrintWriter pw = new PrintWriter(manifestFile)
                pw.write(groovy.xml.XmlUtil.serialize(xml))
                pw.close()

            }

        }
    }
}

com.android.tools.build:gradle:7.0.3 以下版本

调试中,目前 AGP 有奇怪的 Bug,XmlUtil 无法正常序列化。

这段脚本你可以直接放到 app/build.gradle 下执行,也可以单独放到一个 gradle 文件之后 apply 引入,它的作用就是:

在打包过程中检索所有没有设置 exported 的组件,给他们动态配置上 exported。这里有个特殊需要注意的是,因为启动 Activity 默认就是需要被 Launcher 打开的,所以 "android.intent.action.MAIN" 需要 exported 设置为 true 。(PS:应该是用 LAUNCHER 类别,这里故意用 MAIN

如果有需要,还可以自己增加判断设置了 "intent-filter" 的才配置 exported

二、SplashScreen

Android 12 新增加了 SplashScreen 的 API,它包括启动时的进入应用的动作、显示应用的图标画面,以及展示应用本身的过渡效果。

它大概由如下 4 个部分组成,这里需要注意:

  • 1 最好是矢量的可绘制对象,当然它可以是静态或动画形式。
  • 2 是可选的,也就是图标的背景。
  • 与自适应图标一样,前景的三分之一被遮盖 (3)。
  • 4 就是窗口背景。

启动画面动画机制由进入动画和退出动画组成。

  • 进入动画由系统视图到启动画面组成,这由系统控制且不可自定义。
  • 退出动画由隐藏启动画面的动画运行组成。如果要对其进行自定义,可以通过 SplashScreenView 自定义。

更详细的介绍这里就不展开了,有兴趣的可以自己看官方的资料: https://developer.android.com/guide/topics/ui/splash-screen ,这里主要介绍下如何适配和使用的问题。

首先不管你的 TargetSDK 什么版本,当你运行到 Android 12 的手机上时,所有的 App 都会增加 SplashScreen 的功能

如果你什么都不做,那 App 的 Launcher 图标会变成 SplashScreen 界面的那个图标,而对应的原主题下 windowBackground 属性指定的颜色,就会成为 SplashScreen 界面的背景颜色。这个启动效果在所有应用的冷启动和热启动期间会出现。

其实不适配好像也没啥问题。

关于如何迁移和使用 SplashScreen 可以查阅官方详细文档: https://developer.android.com/guide/topics/ui/splash-screen/migrate

另外还可以参考 《Jetpack新成员SplashScreen:打造全新的App启动画面》 这篇文章,文章详细介绍了如果使用官方的 Jetpack 库来让这个效果适配到更低的 Target 平台。

而正常情况下我们可以做的就是:

  • 1、升级 compileSdkVersion 31targetSdkVersion 31 & buildToolsVersion '31.0.0'
  • 2、 添加依赖 implementation "androidx.core:core-splashscreen:1.0.0-alpha02"
  • 3、增加 values-v31 的目录
  • 4、添加 styles.xml 对应的主题,例如:
<resources>
    <style name="LaunchTheme" parent="Theme.SplashScreen">
        <item name="windowSplashScreenBackground">@color/splashScreenBackground</item>
        <!--<item name="windowSplashScreenAnimatedIcon">@drawable/splash</item>-->
        <item name="windowSplashScreenAnimationDuration">500</item>
        <item name="postSplashScreenTheme">@style/AppTheme</item>
    </style>
</resources>
  • 5、给你的启动 Activity 添加这个主题,不同目录下使用不同主题来达到适配效果。

PS: 我个人是一点都不喜欢这个玩意。

三、其他

1、通知中心又又又变了

Android 12 更改了可以完全自定义通知外观和行为,以前自定义通知能够使用整个通知区域并提供自己的布局和样式,现在它行为变了

使用 TargetSDK 为 31 的 App,包含自定义内容视图的通知将不再使用完整通知区域;而是使用系统标准模板。

此模板可确保自定义通知在所有状态下都与其他通知长得一模一样,例如在收起状态下的通知图标和展开功能,以及在展开状态下的通知图标、应用名称和收起功能,与 Notification.DecoratedCustomViewStyle 的行为几乎完全相同。

2、Android App Links 验证

Android App Links 是一种特殊类型的 DeepLink ,用于让 Web 直接在 Android 应用中打开相应对应 App 内容而无需用户选择应用。使用它需要执行以下步骤:

如何使用可查阅:https://developer.android.com/training/app-links/verify-site-associations#auto-verification

使用 TargetSDK 为 31 的 App,系统对 Android App Links 的验证方式进行了一些调整,这些调整会提升应用链接的可靠性。

如果你的 App 是依靠 Android App Links 验证在应用中打开网页链接,那么在为 Android App Links 验证添加 intent 过滤器时,请确保使用正确的格式,尤其需要注意的是确保这些 intent-filter 包含 BROWSABLE 类别并支持 https 方案

3、安全和隐私设置

3.1、大致位置

使用 TargetSDK 为 31 的 App,用户可以请求应用只能访问大致位置信息

如果 App 请求 ACCESS_COARSE_LOCATION 但未请求 ACCESS_FINE_LOCATION 那么不会有任何影响。

TargetSDK 为 31 的 App 请求 ACCESS_FINE_LOCATION 运行时权限,还必须请求 ACCESS_COARSE_LOCATION 权限。当 App 同时请求这两个权限时,系统权限对话框将为用户提供以下新选项:

3.2、SameSite Cookie

Cookie 的 SameSite 属性决定了它是可以与任何请求一起发送,还是只能与同站点请求一起发送。

  • 没有 SameSite 属性的 Cookie 被视为 SameSite=Lax
  • 带有 SameSite=None 的 Cookie 还必须指定 Secure 属性,这意味着它们需要安全的上下文,需要通过 HTTPS 发送。
  • 站点的 HTTP 版本和 HTTPS 版本之间的链接现在被视为跨站点请求,因此除非将 Cookie 正确标记为 SameSite=None; Secure,否则 Cookie 不会被发送。

WebView devtools切换界面标志 webview-enable-modern-cookie-same-site,可以在测试设备上手动启用 SameSite 行为。

4、应用休眠

Android 12 在 Android 11(API 级别 30)中引入的自动重置权限行为 的基础上进行了扩展。

如果 TargetSDK 为 31 的 App 用户几个月不打开,则系统会自动重置授予的所有权限并将App 置于休眠状态。

更多可以查阅:https://developer.android.com/topic/performance/app-hibernation

四、最后

大致需要注意的就是这些,基本上其实除了 exprotedSplashScreen 之外,其他基本都不怎么需要适配,事实上 SplashScreen 我个人觉得会很遭产品嫌弃,毕竟 Material Design 在国内的待遇确实有点惨,没办法去掉 SplashScreen 这点估计需要和产品扯皮一段时间,不过产品和设计一般没有 Android 手机,何况 Android 12,所以日后再说吧~

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ZuoYueLiang/article/details/121687834

智能推荐

Kali之——OpenVAS 8.0 Vulnerability Scanning_openvas vulnerability scanner-程序员宅基地

文章浏览阅读1.4k次。Vulnerability scanning is a crucial phase of a penetration test and having an updated vulnerability scanner in your security toolkit can often make a real difference by helping you discover overlooked..._openvas vulnerability scanner

SAP ABAP 数据字典中的数量单位维护_abap 在zh下维护单位怎么维护-程序员宅基地

文章浏览阅读663次。SAP ABAP 数据字典中的数量单位维护在数据字典中创建构造时,出现如下错误。如图切换到 [通货/ 数量项目]页,对于该构造,其中的MENGE和NETRP字段需要进行维护。维护方法如下:去该字段所在系统表搜索该字段,并参照该字段的参照项目进行维护,以MENGE例。到系统表EKKO里使用快捷键Ctrl + F搜索”MENGE“字段。切换到 [通货/数量项目] 页,参照 [参照..._abap 在zh下维护单位怎么维护

嵌入式软件测试工具--LOGISCOPE _logicscope是什么测试软件-程序员宅基地

文章浏览阅读1.7k次。LOGISCOPE 是一组嵌入式软件测试工具集。它贯穿于软件开发、代码评审、单元/集成测试、系统测试、以及软件维护阶段。它面向源代码进行工作。LOGISCOPE 针对编码、测试和维护。因此,LOGISCOPE 的重点是帮助代码评审(Review )和动态覆盖测试(Testing )。  LOGISCOPE对软件的分析,采用基于国际间使用的度量方法(Halstead、McCabe等)的质量模型,_logicscope是什么测试软件

SQL数据库损坏,报错,原因及注意事项_cad sql服务器已损坏-程序员宅基地

文章浏览阅读5.4k次。同岳科技最近一段时间接到很多客户咨询SQL数据库损坏,附加数据库报错的一些问题,在这里整理一下SQLSEVER数据库常见的一些故障现象及注意事项。  目前中小型企业使用SQLSEVER应用的非常多,但由于各种原因,也会经常出现一些不同的故障,常见的有一下几种:  一,附加数据库文件MDF及日志文件LDF时,报“823”错误。    故障出现原因:  _cad sql服务器已损坏

qemu编译_编译qemu-程序员宅基地

文章浏览阅读2.5k次。一、下载qemuhttps://download.qemu.org/qemu-5.2.0.tar.xzhttps://download.qemu.org/qemu-5.2.0.tar.xz二、安装依赖库$ sudo apt-get install ninja-buildsudo apt-get install libglib2.0-devsudo apt-get install libpixman-1-dev三、安装python3.6sudo add-apt-repositor._编译qemu

kafka的安装和启动_kafka安装启动-程序员宅基地

文章浏览阅读6k次。一、kafka介绍1,kafka简单介绍kafka是一款分布式、支持分区的、多副本,基于zookeeper协调的分布式消息系统。最大的特性就是可以实时处理大量数据来满足需求。2,kafka使用场景1,日志收集:可以用kafka收集各种服务的日志 ,通过已统一接口的形式开放给各种消费者。2,消息系统:解耦生产和消费者,缓存消息。3,用户活动追踪:kafka可以记录webapp或app用户的各种活动,如浏览网页,点击等活动,这些活动可以发送到kafka,然后订阅者通过订阅这些消息来做监控。4,运营_kafka安装启动

随便推点

QT——入门之常用控件属性设置_qt::alignjustify-程序员宅基地

文章浏览阅读2.5k次。这里我在这讲一下,我在开发项目的时候常用的使用方式。QT的功能很强大,构造函数的方式也很多。以下方式可供初学者参考。后续会继续更新!QLabel 标签//需要添加头文件#include <QLabel> //标签//常见属性设置//建立对象this->InterfaceTitle = new QLabel(this);//设置标签的位置,注意我传入的是QRectInterfaceTitle->setGeometry(QRect(154, 63, 170_qt::alignjustify

Linux内核中断处理“下半部”机制(超详细~)_内核中断下半部-程序员宅基地

文章浏览阅读1.1k次。Linux内核中断处理“下半部”机制(超详细~)///插播一条:我自己在今年年初录制了一套还比较系统的入门单片机教程,想要的同学找我拿就行了免費的,私信我就可以哦~点我头像黑色字体加我地球呺也能领取哦。最近比较闲,带做毕设,带学生参加省级或以上比赛///1.中断处理“下半部”机制·中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。·因此,Linux内核的目标就是_内核中断下半部

C语言运算符优先级(大全)-程序员宅基地

文章浏览阅读7.9k次,点赞15次,收藏142次。.” 和 "->"多用在结构体(引用类,共用体)选择成员时使用。直接访问结构的成员时用点运算符,通过指针访问结构的成员用箭头运算符。注意:当优先级相同时,按结合方向来进行计算。 结合方向从左到右。 结合方向从左到右。_c语言运算符优先级

DirectX?_标准图形库directx-程序员宅基地

文章浏览阅读356次。介绍DirectXDirectX是什么?DirectX是一种计算机成为运行和显示具有丰富多媒体元素的应用程序的理想平台。现在最新版本是12,好多游戏和应用程序用DirectX制作了。 用DirectX制作的游戏DirectX是很多API组成的,如果你要用DirectX的话,先要搞清楚你要用什么性质,按你要用的性质。 它们具备DIRECT3D,2D,DXGI,SOUND等许多API,其中介绍了_标准图形库directx

chrome safari 中input垂直居中对齐 行高问题line-height-程序员宅基地

文章浏览阅读217次。现在CSS Reset的时候,都流行使用line-height:1;了,这个明显的好处就是解决了在input Chrome与Safari里Input框里垂直居中的问题,有兴趣人鞋童可以试一下。_chrome 114 line-height

bootstrap-select实现加载时默认选中功能_bootstrap select默认选中-程序员宅基地

文章浏览阅读6.4k次。**bootstrap-select实现加载时默认选中功能**前言,萌新最近有了个jq项目的select多选功能的需求,就找到了下面的插件想要实现这个功能bootstrap-select因为萌新见识浅薄,所以没有找到默认展示某几项的demo,就尝试着自己写了一下function loadInfo() { //默认数据 var area = ..._bootstrap select默认选中