前言
自从2024年初时候,谷歌发布了第一个Android15的预览版,我就一直在关注着这个版本的走向,为什么呢?因为据说Android15里面的变化非常大,后来事实证明果真如此,无论是特性上的改动如预测性返回,edge to edge还是16kb内存页面,或者是编译环境上的改动如targetSdkVersion,Agp,gradle以及jdk的一些升级,都让我觉得这次升级非同一般,来势凶猛,不是那么的好对付,不过好在之前工作上都没有海外上的业务,国内的应用商店对应用的targetSdk也没有太强制的要求,所以Android15跟我都一直保持着一种敌不动我不动的和平局势,但是局势终将是被打破了...
乱世中的相逢
前段时间经历了裁员以及三个月的找工作,让我深刻体会到了严峻的行情局势以及可怕的就业压力,促使我不得不降低自己的底线,面了各种未曾尝试过的岗位面试,如车载,FW或者面向谷歌市场开发,有一次面试中:
- 面试官:有开发过谷歌市场的应用吗?
- 我:好几年前做过,知道现在发布的是aab格式的包
- 面试官:适配过Android15吗?
- 我:没有,就只适配过Android12
- 面试官:哦~~
然后offer就来了...虽然明白这个offer不是那么的好接,入职后主要任务就是跟谷歌的一系列政策斗法,但由于行情实在是不容乐观,招聘市场上已经被一堆外包跟短期项目搞得乌烟瘴气了,考虑再三还是接下了这个offer,于是乎我跟Android15之间斗争也拉开了序幕
升级targetSdkVersion与compileSdkVersion
接手到项目之后,无意外的被分配到的第一个任务就是谷歌市场合规要求,适配Android15,不得不感慨不到两年前还在庆幸自己与Android15无缘,谁知道这么快就要跟这个"大魔王"面对面接触,实在是世事难料,应了那句话,出来混的,终究要还的,吃Android这碗饭,就要做好适配各种Android新版本的准备,不过另我感到欣慰的是项目并不是特别的老,让我产生一种错觉,应该没有想象中的那么费劲,项目基本配置如下

既然如此,废话不多说了,咱先把targetSdk跟compileSdk都升级到35再说,万一跑下来没啥问题呢,我就嘻嘻了,修改了一下gradle.properties中的几个变量

sync一下后发现没啥问题,嗯?就这?run一下试试,发现果然报错了,瞬间不嘻嘻

报了个无法加载sdk路径下/platforms/android-35/android.jar的这个文件,有点摸不着头脑,查了一下马上就知道了,原因很简单,项目中使用的gradle插件以及AGP插件已经不支持targetSdk35了,必须要升级了,升到多少呢,官方文档上看看去

写的很清楚了,当升级到target35后,AGP版本最低需要8.6,此版本对应的gradle最低版本是8.7,那么开始升级这俩插件
升级gradle和AGP
首先将项目中gradle/wrapper/gradle-wrapper.properties中的以下代码

替换成

然后再把根目录build.gradle文件里面的AGP插件从

替换成

一顿操作之后心里就犯怵,这俩插件一下子都抬高了一个大版本,真的没问题吗?编译下看看吧,果然怕啥来啥,出现以下报红

不过还好还算容易懂,现在用的jdk版本11过低了,需要用更高的jdk版本17才可以。
升级JDK
没想到刚开始还没干啥呢,随着升完gradle与AGP之后,jdk版本也要升级了,一下子就感到疑惑了起来,要知道jdk11这个版本大多数项目还是在用的,不过人家gradle插件的changelog里面可是写的清清楚楚,升级到8.0后,jdk的最小版本就要求必须是17了

那就升吧,升这个东西没啥难的,左上角Android Studio->Settings->Build Tools->Gradle里面选择JDK17,没有的话就去下一个

Apply并且ok后我们再编译看看

添加buildConfig=true配置
这真是头一次见啊,说的啥debug的build type有一些自定义的BuildConfig的变量,但是被禁用了,说的应该是下面这段代码

这段代码很常见,大多数项目中都会在buildTypes里面定义一些buildConfigField变量,用来区分生产环境跟debug环境,我这里定义了一个布尔类型的Coffee_Test值为true的变量,现在说的是这个已经被禁用了,肯定又是升级gradle带来的副作用,果然在AGP8.0的更新中就有看到

同时解决方法也很简单,就是在每个定义过自定义变量的模块的build.gradle文件下添加一句代码android.buildFeatures.buildConfig true就可以了,像这样

这个问题也就解决了
添加namespace

随着再次编译,又出现上述报错,跟之前的BuildConfig类似,这次是要求在每个模块的build文件中需要添加个namespace的命名空间,这个变更是在AGP7.3中出现,但是AGP8.0就要被强制执行

通常这个命名空间就是指的是当前模块的包名,所以可以看到报错红字下方也提示了,可以借助AGP的升级助手来完成这一步,完成后在你的build.gradle文件中就会多出一行配置

使用subprojects统一添加配置
上面说的添加android.buildFeatures.buildConfig true以及命名空间的做法,在一些小型,模块没几个的项目中,配置起来不是特别难受,但是若是项目规模大一些,动辄十几个几十个模块的话,逐个加那就真的是件很痛苦的事情,那么有没有轻松一点的办法呢?第一个想到的就是新增一个common.gradle的文件,在里面添加一些诸如android.buildFeatures.buildConfig true这样的配置,然后其他模块引入这个common.gradle文件,倒是可以达到效果,但是存在着一些缺点
- 逐个gradle文件添加common.gradle的依赖也需要耗费一些时间精力
- 如果项目还需要源码依赖一些其他项目的模块,那么其他项目的模块由于没有添加类似配置,也是会报错
所以我这里选择的是在项目的根目录的gradle文件中使用subprojects,动态给每个参与到编译的模块添加android.buildFeatures.buildConfig true以及命名空间,代码如下

如果这个模块没有定义过命名空间,比如依赖的一些源码库,那么才会给这个模块添加命名空间,至于buildConfig,由于我不知道如何判断gradle文件中已经声明过自定义变量了,所以只能全量添加,这样就能避免某些模块因漏加这两个配置而产生编译报错
R文件丢失
言归正传,刚才已经添加完namespace了,我心里琢磨着这下应该差不多了吧,可以正常跑了吧,谁知道Android15给我憋了坨大的,报出以下红字

莫名其妙啊,怎么有些string资源说找不到就找不到了呢?开始没太在意,琢磨着补全R路径就好了,类似于这样

这种又奇葩又丑的方式不用想肯定也知道没法解决本质问题,果然再次编译后又报出来一堆找不到资源的R文件,其中包括layout,color,drawable等,这下彻底凌乱了,肯定不能一个个路径去补全,心想这么大的事官网上肯定会有说明吧,找找看果然在gradle8.0的更新日志里以及设置命名空间的介绍里找到了具体更新信息,谷歌引入namespace的主要原因是为了以后在每个模块里面,只会生成当前模块下的资源文件,其他子模块的资源文件就不会生成了,若要访问的话必须使用对应命名空间导入R类,我认为谷歌这一改动就是为了加速构建速度吧,但是真的要我们开发者一个个资源前面补全路径吗,当然不会,谷歌还是给出了对策的

在标志项的表格中就看到了两个跟R文件相关的设置
- android.nonFinalResIds:关闭这个开关AGP 8.0 默认情况下就不会生成包含非最终字段的
R类 - android.nonTransitiveRClass:关闭这个开关AGP 8.0 就不会仅为当前模块中定义的资源生成
R类
将这俩设置加在gradle.properties中后,项目果然可以正常运行了,不容易!终于可以提测了!
最后
还是以前搞适配没啥压力啊,每个版本出来就一点变动,现在这个Android15着实有点费体力,光一个编译环境都折腾了那么久,瞅瞅这些坑,我写都写半天,更别说一个个踩了,那些新出来的特性都还没开始正经适配,休息一下,下一篇文章正式领教下edge-to-edge以及16kb内存页面