[Andoid][踩坑]CTS 11_r3开始出现的testBootClassPathAndSystemServerClasspath_nonDuplicateClasses FAIL问题分析
问题背景
从CTS测试结果可以清晰看到导致FAIL的类;这个类是我自己加的,为了在Installd与system_server之间注册回调使用;
由于项目进度紧张,最初报出该问题的项目采用了代码回退的方式,保证CTS测试结果正常;
在通过将近2天的分析中,逐步把该问题的原因以及解决方案理清,现总结如下:
问题发生原因
根据问题描述,使用CTS 11_r3测试时会FAIL,而使用11_r2测试的结果是PASS;
测试指令为:
run cts -m CtsStrictJavaPackagesTestCases -t android.compat.sjp.cts.StrictJavaPackagesTest#testBootClassPathAndSystemServerClasspath_nonDuplicateClasses
根据查看代码,梳理出该测试项的逻辑与目的如下:
- 获取环境变量BOOTCLASSPATH与SYSTEMSERVERCLASSPATH的值;
- 将BOOTCLASSPATH与SYSTEMSERVERCLASSPATH的值对应的jar包拉取到主机端;
- 使用dexlib2对这些jar包进行反编译,获取其中dex文件;
- 基于dex文件获取其中包含的类;
- 将所有类进行查重;
- 重复的类再进行白名单筛选;
- 最终通过比对记录所有重复类的列表,如果不为空,则测试FAIL,反之则PASS;
从逻辑不难看出,这是Google想保障system_server与framework中不包含重复类的一个手段;
但是为什么11_r2可以PASS,到了11_r3就不行了呢?
根据对比基线代码与AOSP最新代码后可以发现;
Google在18-Nov-2020时对改测试项进行过一次更新,改进的地方主要有两点:
- 增加白名单中类的数量;
- 修改getDuplicateClasses方法的实现,以支持multi-dex的jar包;
由此可见,11_r2可以PASS完全是钻了该测试项的漏洞,因为我们添加的ICallback正好被打包到非第一个classes.dex中了;而在11_r3中对multi-dex进行了支持,这个问题就暴露出来了;
不过这里插一句,Google也知道自己的IInstalld也是FAIL的,因此自己更新了下白名单把IInstalld排除了,这操作也只能说是佩服…
问题解决思路
好了,不管Google这手操作如何,我们总归是要解决这个问题的。但是在进行了更进一步分析后发现,这个问题比想想中的麻烦。
下图是AOSP原始设计结构,可以看到:installd_aidl这个filegroup分别被services与framework两个模块导入、编译了;
而我添加的用于注册回调的AIDL文件(暂且命名为ICallback.aidl,下同),需要的ICallback.aidl也是在这里面追加的:
filegroup {
name: "installd_aidl",
srcs: [
"binder/android/os/IInstalld.aidl",
"binder/android/os/storage/CrateMetadata.aidl",
"binder/android/os/ICallback.aidl", //Added
],
path: "binder",
}
从而导致ICallback及其内部类与IInstalld一样,被编译了两次,分别打入了services.jar与framework.jar中了;
到这里,解决思路应该是比较清晰了:
让ICallback只编译进framework.jar,而不编译进services.jar中;
解决步骤
- 首先我们需要将ICallback从installd_aidl这个filegroup中剥离,并进行单独控制;同时,我们需要定义规则将这ICallback导出,使全局可见:
filegroup {
name: "installd_aidl_ext",
srcs: [
"binder/android/os/ICallback.aidl",
],
path: "binder",
visibility: ["//visibility:public"],
}
cc_defaults {
name: "installd_aidl_ext_cc_defaults",
aidl: {
//Note, this path should vary according to current location
include_dirs: ["xxx(当前目录)/binder/"],
export_aidl_headers: true,
},
export_include_dirs : ["."],
srcs: [
":installd_aidl_ext",
],
}
java_defaults {
name: "installd_aidl_ext_java_defaults",
aidl: {
//Note, this path should vary according to current location
include_dirs: ["xxx(当前目录)/binder/"],
},
}
- 然后在installd的编译规则中照旧引入:
cc_defaults {
name: "installd_defaults",
defaults: ["installd_aidl_ext_cc_defaults"],//Added
...
srcs: [
...
"InstalldNativeService.cpp",
...
":installd_aidl",
],
...
}
- 而frameworks/base下的是最麻烦的,基于最小改动原则,我结合上面的结构图,决定在framework-non-updatable-sources的filegroup中(与原先的:installd_aidl放在一起):
filegroup {
name: "framework-non-updatable-sources",
srcs: [
...
":installd_aidl",
...
":installd_aidl_ext",//Added
],
}
- 同时需要使services在编译installd_aidl时有ICallback这个类可以引用,在services.core.unboosted中添加:
java_library_static {
name: "services.core.unboosted",
defaults: ["installd_aidl_ext_java_defaults"],//Added
srcs: [
...
":installd_aidl",
...
],
...
}
- 编译后替换system.img重新测试CTS FAIL项,结果PASS;
修改后的结构示意图如下:(实际上只是单独加了一个installd_aidl_ext)
后记
上面只是最终的结果,中间探索的坎坷并未提及;
整个过程最难、也是我印象最深的地方,莫过于“怎么让aidl解析AIDL文件时可以找到ICallback,但不把它编译进services”
而我最终能确定上述的方案,很大程度上时依赖于这里提到的手段:通过定位到编译的参数,再倒过来查看Android.bp支持的选项,从而使用最小的改动,解决这一问题;