Physical Address:
ChongQing,China.
WebSite:
随着Android系统的演进,整个系统已经变得越来越复杂,越来越多的功能与代码加入其中,随之而来的是越来越漫长的编译耗时。
从Android7.0之前,Android系统使用GNU Make来进行版本构建。在这之后,Android逐渐使用Soong系统来进行版本构建。前者需要编写.mk文件来指定我们每个编译模块的规则,而后者则需要编写.bp文件来对我们模块的编译行为进行指定。
关于Android系统中makefile的编写,可以参见我之前的博客。今天这篇博客,主要讲讲我们Android系统中blueprint(.bp)文件的编写。
Android.bp编写时以模块进行划分,模块类型可以参考官方指导。
在编写时,我们以模块类型字段作为开头,以{}囊括具体的配置内容,每一个配置内容都支持Key:Value的形式。那么每个模块内可以配置的内容具体有哪些呢,同样地你可以在上面的链接中找到相关的材料,在这里我们介绍一些基本的语法规则。
数据类型:在Android.bp中一般涉及到的数据类型包括布尔(true/flase)、整形、字符串、数组等。在形式表达上可以参考如下示例:
//布尔类型,直接使用 true 或者 false
proto: {
export_proto_headers: true,
type: "lite",
},
//字符串类型,以双引号进行包裹表示
cc_binary {
name: "evs_app",
}
//数组,以[]进行囊括表示
include_dirs: [
"frameworks/av/media/libaudiohal/impl/",
"packages/services/Car/cpp/evs/apps/default/",
"packages/services/Car/cpp/evs/apps/default/utils/",
],
变量:在Android.bp中支持变量定义与引用,一般在我们需要重复使用某部分规则时通过定义变量后进行引用来实现。在形式表达上可参考如下示例:
//定义gzip_srcs变量,并赋值为arry,包含一个元素,值为src/test/minigzip.c
gzip_srcs = ["src/test/minigzip.c"],
//进行引用
cc_binary {
name: "gzip",
srcs: gzip_srcs,
shared_libs: ["libz"],
stl: "none",
}
注释:在Android.bp中我们同样支持注释,其注释规则与大部分编程语言所使用的类似,使用//进行行注释,使用/**/进行块注释。
运算符:是的,你没看错,Android.bp中同样支持运算符,不过当前仅支持”+“运算符,可以理解为Append的意思。如下示例:
cflags: ["-DLOG_TAG=\"EvsApp\""] + [
"-DGL_GLEXT_PROTOTYPES",
"-DEGL_EGLEXT_PROTOTYPES",
//"-DEVS_RENDER_ORIGINAL",
] + [
"-Wall",
"-Werror",
"-Wunused",
"-Wunreachable-code",
"-Wno-unused-variable",
],
逻辑控制:Android.bp目前最为诟病的是其对逻辑控制的支持相当不足,不像Android.mk那样能为我们提供方便的诸如条件判断等逻辑控制,但是也有一定程度上的支持,这通常与平台相关的,如下所示:
cc_library {
...
srcs: ["generic.cpp"],
arch: {
//arm平台,使用arm.cpp作为源码文件
arm: {
srcs: ["arm.cpp"],
},
//x86平台,使用x86.cpp作为源码文件
x86: {
srcs: ["x86.cpp"],
},
},
}
以下将从几个比较常见的使用场景入手,看看如何去编写一个.bp文件
编译二进制.bin文件是比较常见的,我们所有的进程都是由二进制文件执行的实体,以下为一段编译二进制所对应的bp配置:
cc_binay{
name:”gzip”,
srcs:[“src/test/minigzip.c”],
shared_libs:[“libz”],
stl:“none”,
}
如上所示,如我们需要编译C/C++的二进制文件(.bin)文件,我们应使用cc_binary关键字作为模块名,其中各配置字段含义如下:
name :字符串,定义编译产物的名称。如示例所示,最终编译产物名为gzip.bin
srcs:字符串数组,定义编译此模块所对应的源文件。如示例所示,对应的源码文件应为 src/test/minigzip.c文件。
shared_libs:字符串数组,定义此模块所依赖的动态库。如示例所示,本模块依赖于libz.so。
stl :字符串,定义此模块所选择的STL库,一般可选项包括“libc++”,“libc++_static”,”libstdc++”与“none”。
除此之外还有一些常用的配置选项,这里一并列出:
init_rc:字符串,定义进程的rc启动文件。
cflag:字符串数组,用于定义编译此模块编译时适用于编译器的编译选项,一般我们可以在这里面传递一些宏参数。
cppflags:字符串数组,定义编译此模块时适用于C++编译器的编译选项。
static_libs :字符串数组,定义编译此模块时需要被静态链接到该模块的库。
visibility :定义此模块的可见性。
proprietary :布尔值,用于决定此模块是否属于Vendor,并且是否应当安装到vendor内。
如果你需要的是预编译的二进制库,则可以使用cc_prebuilt_binay关键字作为模块名,其余配置项则比较类似。
有时候我们需要编译一些库,以提供给其他模块使用,这时候如何编写bp文件呢,这里给出一个例子:
cc_library {
name: "PixelVibratorCommonSunfish",
srcs: [
"HardwareBase.cpp",
],
shared_libs: [
"libbase",
"libcutils",
"liblog",
"libutils",
],
cflags: [
"-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)",
"-DLOG_TAG=\"android.hardware.vibrator@1.x-common\"",
],
export_include_dirs: ["."],
vendor_available: true,
}
其中各个配置项含义如下:
name :字符串,定义此库文件的名称。
srcs :字符串数组,定义此库文件所对应的源码文件。
shared_libs :字符串数组,用于定义此库文件所依赖的其他库文件。
cflags :字符串数组,用于定义编译此库文件所需要配置的编译选项。
export_include_dirs :字符串数组,用于定义编译此库所需要包含的头文件。
vendor_available :布尔值,用于定义此模块是否能够被其他Vendor内的模块所访问。
更多其他配置项,可以参考官方指导文档。
作为Android开发人员,是需要经常去编译APK的。虽然我们一般会使用Android Studio事先编译好APK,作为预编译的一部分打包镜像,但有些时候还是需要进行通过源码,在编译时生成APK,这里给出一个编译APK文件的bp示例:
android_app {
name: "VehicleSetting",
platform_apis: true,
certificate: "platform",
srcs: ["java/**/*.java"],
resource_dirs: [
"res",
],
manifest: "AndroidManifest.xml",
optimize: {
enabled: false,
},
libs: [
],
static_libs: [
"com.google.android.material_material",
"androidx.appcompat_appcompat",
"androidx-constraintlayout_constraintlayout",
],
}
各个配置项的含义如下:
name :字符串,用于定义此APK的名称,在此示例中生成的APK名为 VehicleSetting .apk
platform_apis :布尔值,用于判定是否使用平台限定的API等级还是可以设置SDK的API的等级,如果设置为true,则sdk_version必须为空。
certificate :字符串,用于定义此APK所使用的签名证书。
srcs :字符串数组,用于定义此APK的源码文件。
resource_dirs :字符串数组,用于定义此APK所依赖的资源文件。
manifest :字符串,用于定义此APK的Manifest.xml文件。
optimize :可再行扩展的配置,此处enabled设为true则开启优化,设为false则不需要进行优化。
static_libs :字符串,用于定义此APK所依赖的静态库。
除此之外还有一些常用的配置选项:
max_sdk_version :字符串,定义此APK所支持的最大SDK版本。
min_sdk_version :字符串,用于定义此APK所支持的最小的SDK版本。
jni_libs :字符串数组,用于定义此APK所依赖的JNI调用库。
package_name :字符串,用于定义此apk的包名。
如果我们需要编译Jar包,以便其他APP使用,如何通过bp文件来进行编译呢,这里给出一个示例:
android_library {
name: "launcher-aosp-tapl",
static_libs: [
"androidx.annotation_annotation",
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.uiautomator_uiautomator",
"androidx.preference_preference",
"SystemUISharedLib",
],
srcs: [
"tests/tapl/**/*.java",
"src/com/android/launcher3/ResourceUtils.java",
"src/com/android/launcher3/testing/TestProtocol.java",
],
resource_dirs: [ ],
manifest: "tests/tapl/AndroidManifest.xml",
platform_apis: true,
}
从该示例可以看出,其实编译APK与编译Jar包是很相似的写法,这里不再赘述。
无论是编写makefile还是编写bp文件,其实都没什么技巧可言,重要的是根据AOSP的示例与Soong官方的指导文件来进行编写。但两者之间还是有一些可以类比的地方的,这里给出编写Makefile与编写bp文件的一些类比:
含义 | Android.bp | Android.mk |
---|---|---|
模块名定义 | name:” ModuleName” | LOCAL_MODULE := ModuleName |
编译产物定义 | cc_library_shared cc_library_static cc_binary cc_prebuilt_binary | include (BUILDSHAREDLIBRARY)include(BUILD_STATIC_LIBRARY) include (BUILDEXECUTABLE)include(PREBUILT_SHARED_LIBRARY) |
源代码引用 | srcs | LOCAL_SRC_FILES:= |
头文件引用 | include_dirs | LOCAL_C_INCLUDES:= |
共享库依赖 | shared_libs | LOCAL_SHARED_LIBRARIES := |
静态库依赖 | static_libs | LOCAL_STATIC_LIBRARIES := |
编译器选项 | cflags | LOCAL_CFLAGS += |
rc启动文件 | init_rc | LOCAL_INIT_RC := |
更多的字段对比,可以参考build/soong/androidmk/androidmk/android.go文件内的定义。
1.打包资源文件
有些时候我们程序还需要依赖一些资源文件,这些资源文件也需要在编译系统时一起打包到系统中,在以往Android.mk中,提供了一些字段用于打包,也可以通过调用shell脚本来进行copy,不过在Android.bp中就没有那么灵活了,这里举例说明。
如与Android.bp同级目录下存在/res子目录,需要将/res子目录中的config.json中打包到/system/etc/automotive/目录中,则可以借助prebuilt_etc字段来完成:
prebuilt_etc {
name: "config.json",
src: "./res/config.json",
sub_dir: "automotive/evs",
}
如与Android.bp同级目录下存在config.json配置文件,需要将config.json打包到/system/etc/automotive/evs/cyber/conf目录下,此时prebuilt_etc字段可如下配置:
prebuilt_etc {
name: "config.json",
src: "config.json",
sub_dir: "automotive/evs/cyber/conf",
}
此时在编译时会自动为你创建automotive/evs/cyber/conf目录,并完成拷贝。
这里拷贝后的文件名通过name字段定义,src字段用于定义源文件,sub_dir用于定义目录。
以上就是本篇博客的全部内容了,希望能对您有所帮助~