1.背景
客户想要拿到Framework的源码自己编译系统,但是我们在Framework中以源码形式集成了自己的拓展代码,故需要将我们的修改部分代码封装起来且以不暴露源码的Jar包形式重新集成到Framework中。
2.关键难点
(1)由于拓展部分的代码在设计之初并未打算封装,而是作为Framework的一部分源码直接编译,故与Framework的其他部分存在相互依赖的关系,这为后续封装代码创造了困难。
(2)由于需要的是Framework部分的源码,所以也不能直接将编译完成后的framework.jar提供给客户。
(2)本项目是基于Android 10的,故Framework中已从Android.mk换成了Android.bp,而网上只有Android.bp的基本使用语法,对于使用Android.bp封装源码的资料并不多。
3.必要知识准备
建议还是先看看必要知识,不要急于完成需求,不然可能会碰到很多问题,但是又不知道是哪里导致的,会浪费很多时间去瞎改。
(1)Android.bp语法以及与mk语法之间的对比
最好作为参照物之一的就是Framework中service/core部分的bp文件
java_library_static { //编译一个静态库
name: "services.core.unboosted",//模块名称, 类似Android.mk中的LOCAL_MODULE
aidl: {//aidl文件路径
include_dirs: [//指定的文件查找路径
"frameworks/base/cmds/idmap2/idmap2d/aidl",
],
},
srcs: [//源码
"java/**/*.java",
":dumpstate_aidl",
],
libs: [//动态库
"services.net",
],
required: [//前提条件
"gps_debug.conf",
],
static_libs: [//静态库
"time_zone_distro",
],
}
java_genrule {//类似于file_group的用法
name: "services.core.priorityboosted",
srcs: [":services.core.unboosted"],
tools: ["lockedregioncodeinjection"],
cmd: "$(location lockedregioncodeinjection) " +
" --targets \"Lcom/android/server/am/ActivityManagerService;,Lcom/android/server/wm/WindowManagerGlobalLock;\" " +
" --pre \"com/android/server/am/ActivityManagerService.boostPriorityForLockedSection,com/android/server/wm/WindowManagerService.boostPriorityForLockedSection\" " +
" --post \"com/android/server/am/ActivityManagerService.resetPriorityAfterLockedSection,com/android/server/wm/WindowManagerService.resetPriorityAfterLockedSection\" " +
" -o $(out) " +
" -i $(in)",
out: ["services.core.priorityboosted.jar"],
}
java_library {//编译生成一个库
name: "services.core",
static_libs: ["services.core.priorityboosted"],
}
prebuilt_etc {//预编译配置文件,权限文件也通过该方式
name: "gps_debug.conf",
src: "java/com/android/server/location/gps_debug.conf",
}
针对该场景和需求所需知道的MK与BP文件的部分对照内容,详情可直接阅读源码:
build/soong/androidmk/cmd/androidmk/android.go
"BUILD_JAVA_LIBRARY": "java_library",
"BUILD_STATIC_JAVA_LIBRARY": "java_library_static",
"BUILD_HOST_JAVA_LIBRARY": "java_library_host",
"BUILD_HOST_DALVIK_JAVA_LIBRARY": "java_library_host_dalvik",
"BUILD_PACKAGE": "android_app",
"SHARED_LIBRARIES": "cc_prebuilt_library_shared",
"STATIC_LIBRARIES": "cc_prebuilt_library_static",
"EXECUTABLES": "cc_prebuilt_binary",
"JAVA_LIBRARIES": "prebuilt_java_library",
常见属性:
platform_apis : 用 sdk 的 hide 的 api 來编译
certificate : 指定用的是什么签名,如上用的是 platform 签名。
jni_libs : 依赖使用的 JNI 库
libs : 工程中的 libs 库
static_libs : 静态库,其中 nearme_nfc 为下方定义的:java_import
optimize : 压缩配置,enabled 是否开启,obfuscate 是否开启混淆,proguard_flags_files 混淆规则配置文件
(2)库文件在AOSP源码环境下的用法
首先,我们要知道一件事,在Android.bp中为什么会有libs和static_libs两种库的引用,原因在于使用libs时,只是在编译该模块时去依赖所添加的libs,而static_libs则是会将所依赖的代码一起编译在模块中。
因此,如果仅仅只是想要模块编译通过,又能确保在系统运行时系统中是存在所依赖的那部分代码的话,只需要使用libs即可。但是在当前这个需求中,不仅需要通过编译,又要将其他模块所依赖的该模块封装为库,故,需要将该模块编译出两种库来。
Jar A:仅包含接口文件,即类和方法接口的class文件,但是在方法中没有函数实现,目的是为了使所依赖该模块的模块通过编译。
Jar B:不包含接口文件,但是包含该模块的源代码的dex文件,用于系统加载该模块的运行代码。
关于java和class和dex的关系,可以看看下面这个图。
(3)BootJar添加方法
Step1:定义需要做成BootJar的模块Rabbit
Android.mk或者Android.bp均可,但是最好用Android.bp,因为以后都会是BP。
Step2:在device中用于指定编译哪些模块的mk文件(各公司各项目可能不一样)中添加Rabbit
以及权限文件
Step3:在mk文件中定义PRODUCT_BOOT_JARS的地方添加Rabbit,并将Rabbit加入白名单。
白名单文件路径:
build/make/core/tasks/check_boot_jars/package_whitelist.txt
Step4:在adb shell中通过指令adb shell $BOOTCLASSPATH
验证BOOTCLASSPATH是否已包含所添加的Rabbit
4.实际操作
其实实际上就第一步复杂点,其他的都是照葫芦画瓢网上也有一些相关的资料可以参考。
Step 1
首先,我们需要取得仅包含接口文件的Jar A以及仅包含dex的Jar B。
这里就不贴代码了,一般来说能碰到这个需求说明已经有了源码,只需要修改一下编译文件,生成一个java_library,并指定installable: true
,即可,关于如何生成库文件网上有很多教程。
在编译完成后,在目录out\target\common\obj\JAVA_LIBRARIES\中,会生成一个rabbit_intermediates文件夹,其中会包含3个库文件,我们需要的是class-header.jar
和javalib.jar
。
这里的class-header.jar
就是后面会用到的rabbitsrc.jar,主要用来给其他依赖Rabbit模块的模块添加依赖用于通过编译,也可以对上层应用开放,所需要的权限问题网上也有相关资料。
javalib.jar
则是后面用到的rabbitdex.jar,主要用来编译并以BootJar的形式安装在系统中,不难理解,毕竟只有壳没有内核也是不行的。
PS:当然如果不用隐藏源码,直接使用classes.jar
,里面是包含所有源代码的class文件,会省去很多工作量,也并非本文所描述的需求。
接下来,在模块Rabbit的bp文件中,就可以直接删掉源代码,然后在BP文件中添加对应的编译语句了。
// 1. 引入接口Jar,并再生成一个其他模块所需要依赖的库
// ==== import library ====
java_import {
name: "rabbitlibsrc",
jars: ["libs/rabbitsrc.jar"],
}
// ==== Java library ====
java_library {
name: "RabbitSrc",
static_libs: [
"rabbitlibsrc",
],
}
// 2. 引入Dex Jar,并安装到系统中
// ==== import library ====
java_import {
name: "rabbitlibdex",
jars: ["libs/rabbitdex.jar"],
}
// ==== Java library ====
java_library {
name: "RabbitDex",
static_libs: [
"rabbitdex",
],
installable: true,
}
// 3. 添加dex Jar编译的Boot Jar所需的权限文件预编译
// ==== ETC permissions ====
prebuilt_etc {
name: "RabbitDex.xml",
src: "RabbitDex.xml",
sub_dir: "permissions",
}
RabbitDex.xml:
放在和Android.bp同目录下
<?xml version="1.0" encoding="UTF-8"?>
<permissions>
<library
name="RabbitDex"
file="/system/framework/RabbitDex.jar" />
</permissions>
PS:这里可能会有疑问,能否封装一个Jar C,既有接口文件,又有dex文件呢?
那么,可不可以一个Jar包中既有class文件又有dex文件呢,理论上是可以的,但是在实际编译过程中遇到了这样的报错。
containing both DEX and Java-bytecode content
Step 2
device/company/project/package.mk(随便编的,根据具体项目定)中加入
PRODUCT_PACKAGES += RabbitDex \
RabbitDex.xml
Step 3
继续在该mk文件中PRODUCT_BOOT_JARS处加入Rabbit,如果没有现成的PRODUCT_BOOT_JARS可以直接添加下面的语句。
PRODUCT_BOOT_JARS += RabbitDex
Step 4
编译刷机
可能出现的问题
(1)编译错误
installable+static_libs引用导致,原理未知。
Java java.lang.ArrayIndexOutOfBoundsException: 0
解决:去除installable或者用libs取代static_libs。
(2)运行时错误
java.lang.NoClassDefFoundError: Failed resolution of XXX
解决:该报错说明已经找到了Jar包中的类,但是缺少方法实现,对于该需求,应重点检查在引用Jar包的地方是否引用成了接口Jar包而不是Dex Jar包。如果确实是引用的Dex Jar包,则要检查生成Jar包的源码和过程是否有问题,以及权限文件是否加入了编译。
(2)运行时错误
java.lang.ClassNotFoundException:XXX
解决:该报错说明Jar包未正确添加,可以现在adb shell里的system/framework中查看是否已存在所需的Jar包。以及引用Jar包的地方如果未提前预编译,是否是通过ClassLoader来加载的,ClassLoader的使用方法可以参考SystemServer.java。