[Android Framework]隐藏模块源码并封装为BootJar实战

文章讲述了在Android10背景下,面临客户需要源码但需隐藏拓展代码的挑战。关键难点在于代码的相互依赖和使用Android.bp的封装。解决方案包括理解Android.bp语法,创建包含接口和Dex的两个Jar包,并处理编译和运行时可能遇到的问题。最后,详细步骤解释了如何将DexJar作为BootJar安装到系统中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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.jarjavalib.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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值