当前位置: 首页 > news >正文

Android构建系统 - 06 添加编译模块

文章目录

  • Android分区
    • 行业流程
    • 分区含义
    • 指定分区
  • 声明添加模块
  • 添加可执行程序
    • 编译期间添加
      • 源码方式添加 cc_binary
      • prebuilt方式添加 cc_prebuilt_binary
      • Java可执行程序 java_library
    • 交叉编译后push
      • 源文件
      • NDK编译
        • 写 CMakeLists.txt
        • .sh 脚本 调用cmake
      • adb push
      • 执行
  • 添加库
    • native
      • 源码方式添加
      • prebuilt(so库)方式添加
    • Java
      • 源码方式添加 java_library
      • prebuilt(Jar包)方式添加
  • App
    • android_library
    • 添加app
  • 拷贝文件

Android分区

行业流程

ARM + Android ,一个简化的普遍流程:

  1. Google

    • 开发迭代 AOSP + Kernel
  2. 芯片厂商

    • 针对自己的芯片特点,移植 AOSP 和 Kernel,使其可以在自己的芯片上跑起来。
  3. 方案厂商(很多芯片厂商也扮演了方案厂商的角色),

    • 在芯片厂商源码基础上开发外设相关软件,主要是驱动和 hal,

      设计电路板,给芯片添加外设。

    • 改进性能和稳定性。

  4. 产品厂商

    • 系统软件开发,UI 定制
    • 硬件上的定制(添加自己的外设),改进性能和稳定性.

分区含义

厂商分区用途
GoogleSystem 分区通用 Android 系统组件。不同厂商、型号的设备通用
芯片厂商 / 方案厂商Vender 分区针对硬件相关的平台通用的可执行程序、库、系统服务和 app
-boot 分区 的 kernel 部分开发的驱动程序

产品厂商要执行软硬件定制,会复杂一些,依据 产品即 Variant(变体) 而定。

对不同的产品会从硬件和软件两个维度来做定制。例如:产品 A 京东方的屏 & 带广告的版本,产品 B 三星的屏 & 不带广告的版本。

厂商分区用途
产品厂商Vendor 分区公用的硬件相关软件
Odm 分区有差异的硬件相关软件
System分区公用的软件
Product 分区有差异的软件(系统源码中程序一般预制到 Product 分区 )

各个产品之间除此之外的分区都是一样的,便于统一维护与升级。

指定分区

System 分区:

# 不需要在 Android.mk/Android.bp 中额外指定,默认就是system 分区
# 但这并不合法,正确做法借鉴自 /product/gsi_common.mk
# 需要在makefile中,在PRODUCT_PACKAGES添加模块之前
#  添加模块名到 PRODUCT_ARTIFACT_PATH_REQUIREMENT_WHITELIST 变量
#  注意 \ 换行前后,不能有注释,不能有多余空格,否则报错 "error: missing separator."
PRODUCT_ARTIFACT_PATH_REQUIREMENT_WHITELIST += \
    system/app/messaging/messaging.apk \
    system/etc/seccomp_policy/crash_dump.%.policy \
    system/lib/libgiftranscode.so \
    system/bin/helloSystem \
# 变量添加要在 PRODUCT_PACKAGES 之前
PRODUCT_PACKAGES += helloSystem  # 这个是我们自己添加的模块

Vendor 分区:

# Android.mk
LOCAL_VENDOR_MODULE := true
# Android.bp
vendor: true

Odm 分区:

# Android.mk
LOCAL_ODM_MODULE := true
# Android.bp
device_specific: true

Product 分区:系统源码中程序一般预制到 Product 分区

# Android.mk
LOCAL_PRODUCT_MODULE := true
# Android.bp
product_specific: true

声明添加模块

在之前的章节,我们在 device/pxlw/irisX目录下创建了一系列makefile,并在这些makefile中指定了一家叫 pxlw 的公司,添加了产品irisX。

其中 irisX.mk 文件就是PRODUCT_COPY_FILES,是产品的配置文件,用于将源码的文件拷贝到 Android 文件系统中。

device/pxlw/irisX
	- AndroidProducts.mk # lunch选单&编译选项
	- irisX.mk # PRODUCT_COPY_FILES 板级特性
    - BoardConfig.mk # 硬件特性
# 现在在命令行,可通过如下命令初始化环境 & 选择产品
source build/envsetup.sh
lunch irisX-eng
emulator

在后续的章节中我们希望添加一些模块(可执行程序/库),首先需要在 irisX.mk 文件中指定要添加的模块(通过追加 PRODUCT_PACKAGES 变量实现)。

示例如下,这些模块我们会在后续逐个介绍

# device/pxlw/irisX/irisX.mk 中追加 PRODUCT_PACKAGES 变量
#  新增模块到 Product 分区
PRODUCT_PACKAGES += helloProduct    # 自定义模块
PRODUCT_PACKAGES += busybox         # 预编译文件
PRODUCT_PACKAGES += helloJava       # 自定义java模块
#  新增模块到 System 分区 - 白名单
PRODUCT_ARTIFACT_PATH_REQUIREMENT_WHITELIST += \
    system/bin/helloSystem \
PRODUCT_PACKAGES += helloSystem     # 添加到 System分区

PRODUCT_PACKAGES += libMymathJavaSource   # source code java
PRODUCT_PACKAGES += libMymathJavaPrebuilt # prebuilt jar java

添加可执行程序

编译期间添加

源码方式添加 cc_binary

这章节,我们新增了两个自定义的模块,分别放在 Product分区和System分区。

文件结构如下:

device/pxlw/irisX/excutable/excutableCppSource
	- Android.bp
	- helloProduct.cpp
	- helloSystem.cpp

在Android.bp中分别指定两个cpp文件为源文件,它们本身这没什么好说的就是打印信息到标准输出流。

// device/pxlw/irisX/excutable/excutableCppSource/helloProduct.cpp
#include <cstdio>
int main()
{
    printf("Hello Android from Product partition 1\n");
    return 0;
}

// device/pxlw/irisX/excutable/excutableCppSource/helloSystem.cpp
#include <cstdio>
int main()
{
    printf("Hello Android from System partition 1\n");
    return 0;
}

值得注意的是,如何将模块(可执行文件)添加到对应分区。虽然在前一章节我们已经介绍过了,但这里还是再多说一嘴。

  • Product 分区:

    • Android.bp 中 添加 product_specific: true
  • System 分区:

    • Android.bp 中

      • 不添加(默认就是System分区)
      • 或 添加 product_specific: false
    • 在makefile中添加模块前,为其添加白名单PRODUCT_ARTIFACT_PATH_REQUIREMENT_WHITELIST,否则编译报错。

      或者PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST

// device/pxlw/irisX/excutable/excutableCppSource/Android.bp
cc_binary {                 //模块类型为可执行文件
    name: "helloSystem",    //模块名 helloSystem
    srcs: ["helloSystem.cpp"],//源文件列表
    cflags: ["-Werror"],    //添加编译选项
}

cc_binary {                 //模块类型为可执行文件
    name: "helloProduct",   //模块名 helloProduct
    srcs: ["helloProduct.cpp"],//源文件列表
    product_specific: true, //编译出来放在/product目录下(默认是放在/system目录下)
    cflags: ["-Werror"],    //添加编译选项
}

在product分区,makefile中添加

# device/pxlw/irisX/irisX.mk 中追加 PRODUCT_PACKAGES 变量
#  新增模块到 Product 分区
PRODUCT_PACKAGES += helloProduct    # 自定义模块
#  新增模块到 System 分区 - 白名单
PRODUCT_ARTIFACT_PATH_REQUIREMENT_WHITELIST += \
    system/bin/helloSystem \
PRODUCT_PACKAGES += helloSystem     # 添加到 System分区

编译成功后,就可以试一试啦。

source build/envsetup.sh
lunch irisX-eng
make
emulator
~/aosp$ adb shell
irisX:/ # helloProduct
Hello Android from Product partition 1
irisX:/ # helloSystem
Hello Android from System partition 1


irisX:/ # which helloProduct                                               
/product/bin/helloProduct
irisX:/ # which helloSystem                                               
/system/bin/helloSystem

prebuilt方式添加 cc_prebuilt_binary

device/pxlw/irisX/excutable/excutableCppPrebuilt
    - Android.bp
    - busybox
cd device/pxlw/irisX
mkdir excutableCppPrebuilt & cd excutableCppPrebuilt
wget https://busybox.net/downloads/binaries/1.30.0-i686/busybox
// device/pxlw/irisX/excutable/excutableCppPrebuilt/Android.bp
cc_prebuilt_binary {
    name: "busybox",
    srcs: ["busybox"],
    product_specific: true,
}

在product分区,makefile中添加

# device/pxlw/irisX/irisX.mk 中追加 PRODUCT_PACKAGES 变量
#  新增模块到 Product 分区
PRODUCT_PACKAGES += busybox         # 预编译文件

编译成功后,就可以试一试啦。

source build/envsetup.sh
lunch irisX-eng
make
emulator
~/aosp$ adb shell
irisX:/ # busybox
....

irisX:/ # which busybox
/product/bin/busybox

Java可执行程序 java_library

device/pxlw/irisX/excutable/excutableJava
    - Android.bp
    /com/pyzhang/main
		- HelloJava.java
// device/pxlw/irisX/excutable/excutableJava/Android.bp
java_library {
    name: "helloJava",
    // installable决定编译出来的 jar 包中的类型:
    // - true: classes.dex 文件, Android 虚拟机可以加载的格式
    // - false/不指定:  .class 文件, 没法安装到系统,只能给其他java模块作为static_libs依赖
    installable: true,
    product_specific: true,
    srcs: ["**/*.java"],
    sdk_version: "current"
}
// device/pxlw/irisX/excutable/excutableJava/com/pyzhang/main/HelloJava.java
package com.pyzhang.main;
public class HelloJava
{
	public static void main(String[] args) 
	{
		System.out.println("Hello Java 1");
	}
}

在product分区,makefile中添加

# device/pxlw/irisX/irisX.mk 中追加 PRODUCT_PACKAGES 变量
#  新增模块到 Product 分区
PRODUCT_PACKAGES += helloJava       # 自定义java模块

编译成功后,就可以试一试啦。

source build/envsetup.sh
lunch irisX-eng
make
emulator
~/aosp$ adb shell

irisX:/ # export CLASSPATH=/product/framework/helloJava.jar 
irisX:/ # app_process /product/framework/ com.pyzhang.main.HelloJava
Hello Java 1

交叉编译后push

Android 平台通常使用 CMake 调用 NDK 工具链编译 C/C++ 代码,具体地:

源文件

写一个 helloworld c++ 可执行程序:

main.c:

# include <iostream>

int main(int argc, char const *argv[])
{
  for(int i = 0; i < 5; ++i)
    std::cout << "Hello World" << std::endl;

  return 0;
}

NDK编译

调用 NDK 工具链

写 CMakeLists.txt

写 CMakeLists.txt:

google 给了两种方式用于支持 CMake 调用 NDK 工具链编译 C/C++ 代码,

  • 一种是 CMake的内置支持,

    • 需要 CMake 版本 >= 3.21,NDK 版本需要大于 r23,
    • 是未来的主流。
  • 一种是通过工具链文件支持,

    • Android Gradle 插件使用的是 NDK 的工具链文件。
    • 是当前的主流。

CMakeLists.txt

cmake_minimum_required(VERSION 3.0)

project(main)

add_executable(${PROJECT_NAME} main.cpp )
.sh 脚本 调用cmake

编译脚本 build.sh:

export ANDROID_NDK=你的ndk完整路径

rm -r build
mkdir build && cd build 

# CMake的内置支持
# cmake -DCMAKE_SYSTEM_NAME=Android \
# 	-DCMAKE_SYSTEM_VERSION=29 \
# 	-DCMAKE_ANDROID_ARCH_ABI=x86_64 \
# 	-DANDROID_NDK=$ANDROID_NDK \
# 	-DCMAKE_ANDROID_STL_TYPE=c++_shared \
# 	..

# 工具链文件支持
cmake \
    -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
    -DANDROID_ABI=x86_64 \
    -DANDROID_PLATFORM=android-29 \
	-DANDROID_STL=c++_shared \
	..

cmake --build .

adb push

在模拟器上执行我们的程序:

# 编译程序
chmod +x build.sh
./build.sh
# 打开模拟器,流程略
# 上传可执行文件
adb push build/test /data/local/tmp
# 上传 STL 动态库
adb push 你的ndk完整路径/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/x86_64-linux-android/libc++_shared.so /data/local/tmp

执行

# 进入到模拟器 shell
adb shell
# 执行程序
cd /data/local/tmp
export LD_LIBRARY_PATH=/data/local/tmp && ./test

添加库

native

源码方式添加

这章节我们新增了一个动态库模块libmymath ,并修改上一章节新增的模块 helloProduct 使其使用我们的库。

文件结构如下:

device/pxlw/irisX/library/libMymathCppSource
	- Android.bp
	- my_math.cpp
	- my_math.h
cc_library_shared {
    name: "libMymathCppSource",
    srcs: ["my_math.cpp"],
    export_include_dirs: ["."],
    product_specific: true,
    // 可编译出 32 64 位的 so 包
    compile_multilib: "both"
}
// my_math.h
int my_add(int a, int b);
int my_sub(int a, int b);

// my_math.cpp
#include "my_math.h"
int my_add(int a, int b) { return a + b; }
int my_sub(int a, int b) { return a - b; }

现在为上一章节的 helloProduct 模块添加该库,

// device/pxlw/irisX/excutable/excutableSource/Android.bp
cc_binary {
    name: "helloProduct",
    srcs: ["helloProduct.cpp"],
    product_specific: true,
    cflags: ["-Werror"],
    shared_libs: ["libMymathCppSource"]    //添加动态库依赖
}
// device/pxlw/irisX/excutable/excutableCppSource/helloProduct.cpp
#include <cstdio>
#include "my_math.h" //添加自定义库的头文件
int main()
{
    printf("Hello Android from Product partition %d\n", my_add(1, 1));
    return 0;
}

编译成功后,就可以试一试啦。

source build/envsetup.sh
lunch irisX-eng
make
emulator
~/aosp$ adb shell
irisX:/ # helloProduct
Hello Android from Product partition 2

prebuilt(so库)方式添加

文件结构如下:

device/pxlw/irisX/library/libMymathCppPrebuild
	- Android.bp
	/include/my_math.h # 直接拷贝
	/lib
		/x86/libmymath.so
		/x86_64/libmymath.so

上一章节源码方式添加库中,增加了一个动态库模块libmymath,编译成功后我们可以在如下路径得到目标文件。

# out/target/product/irisX/system/product/lib/libmymath.so
out/target/product/irisX/system/product/lib64/libmymath.so

# 拷贝
# mv out/target/product/irisX/system/product/lib/libmymath.so  device/pxlw/irisX/library/libMymathCppPrebuild/lib/x86
mv out/target/product/irisX/system/product/lib64/libmymath.so  device/pxlw/irisX/library/libMymathCppPrebuild/lib/x86_64
cc_prebuilt_library_shared {
    name: "libMymathCppPrebuild",
    arch: {
        x86: {
            srcs: ["lib/x86/libmymath.so"],
        },
        x86_64: {
            srcs: ["lib/x86_64/libmymath.so"],
        }
    },
    export_include_dirs: ["include"],
    product_specific: true,
}

同时,为了避免冲突,我们修改上一节通过源码添加的库libmypath移出系统源码目录 / 删除 / 改名。

编译成功后,就可以试一试啦。

source build/envsetup.sh
lunch irisX-eng
make
emulator
~/aosp$ adb shell
irisX:/ # helloProduct
Hello Android from Product partition 2

Java

源码方式添加 java_library

device/pxlw/irisX/library/libMymathJavaSource
    - Android.bp
    /com/pyzhang/mymath
		- MyMathSource.java

Android.bp

installable: false 意味着编译出来的 jar 包里面是 .class 文件,没法安装到系统上的。只能给其他 java 模块作为 static_libs 依赖。

最终生成的 jar 包不会被直接存放到 Android 的文件系统中,而是打包进依赖于当前模块的其他模块中。

java_library {
    // 添加前缀 lib和后缀jar
    name: "libMymathJavaSource",
    // installable决定编译出来的 jar 包中的类型:
    // - true: classes.dex 文件, Android 虚拟机可以加载的格式
    // - false/不指定:  .class 文件, 没法安装到系统,只能给其他java模块作为static_libs依赖
    installable: false,
    product_specific: true,
    srcs: ["**/*.java"],
    sdk_version: "current"
}
// MyMathSource.java
package com.pyzhang.main;
public class MyMathSource {
    public MyMathSource() {}
	public int add(int a, int b) {
		return (a+b);
	}
}

在product分区,makefile中添加

# device/pxlw/irisX/irisX.mk 中追加 PRODUCT_PACKAGES 变量
#  新增模块到 Product 分区
PRODUCT_PACKAGES += libMymathJavaSource       # 自定义java静态库

现在为上一章节的 helloJava 模块添加该库,

// device/pxlw/irisX/excutableCpp/Android.bp
java_library {
    name: "hellojava",
    installable: true,
    product_specific: true,
    srcs: ["**/*.java"],
    sdk_version: "current",
    // 新增: 添加java静态库依赖
    static_libs: ["libMymathJavaSource"]
}
// device/pxlw/irisX/excutable/excutableJava/com/pyzhang/main/HelloJava.java
package com.pyzhang.main;
public class HelloJava
{
	public static void main(String[] args) 
	{
		System.out.println("Hello Java ");
        System.out.println(MyMath.add(1, 1));
	}
}

编译成功后,就可以试一试啦。

source build/envsetup.sh
lunch irisX-eng
make
emulator
~/aosp$ adb shell

export CLASSPATH=/product/framework/helloJava.jar 
app_process /product/framework/ com.pyzhang.main.HelloJava
Hello Java 2

prebuilt(Jar包)方式添加

文件结构如下:

device/pxlw/irisX/library/libMymathJavaPrebuild
	- Android.bp
	- libmymathjava.jar # 直接拷贝

上一章节源码方式添加了Java库,编译成功后我们可以在如下路径得到目标文件。

# 或者再手动编译一遍
cd device/pxlw/irisX/library/libMymathJavaSource
mm
#编译完成后,会打印出编译产物路径
# [100% 11/11] Copy: out/target/product/irisX/obj/JAVA_LIBRARIES/libMymathJavaSource_intermediates/javalib.jar

# 拷贝
cp  out/target/product/irisX/obj/JAVA_LIBRARIES/libMymathJavaPrebuilt_intermediates/javalib.jar  device/pxlw/irisX/library/libMymathJavaPrebuild/libmymathjava.jar 
// device/pxlw/irisX/library/libMymathJavaPrebuild/Android.bp
java_import {
    name: "libMymathJavaPrebuilt",
    installable: false,
    product_specific: true,   
    jars: ["libmymathjava.jar"],
    sdk_version: "current"
}

同时,为了避免冲突,我们修改上一节通过源码添加的库libMymathJavaSource 移出系统源码目录 / 删除 / 改名。

修改使用java包的地方为:

// device/pxlw/irisX/excutable/excutableJava/Android.bp
java_library {
    name: "helloJava",
    // 编译出来的 jar 包里面是
    // - true: classes.dex 文件, Android 虚拟机可以加载的格式
    // - false/不指定:  .class 文件, 没法安装到系统,只能给其他java模块作为static_libs依赖
    installable: true,
    product_specific: true,
    srcs: ["**/*.java"],
    sdk_version: "current",
    static_libs: [
        "libMymathJavaSource",	// 源码 java
        "libMymathJavaPrebuilt" // prebuilt java
    ]
}
package com.pyzhang.main;
public class HelloJava
{
	public static void main(String[] args) 
	{
		System.out.println("Hello Java ");
		System.out.println(MyMathPrebuilt.add(1, 1));
		System.out.println(MyMathSource.add(2, 2));
	}
}

编译成功后,就可以试一试啦。

source build/envsetup.sh
lunch irisX-eng
make
emulator
~/aosp$ adb shell

 export CLASSPATH=/product/framework/helloJava.jar 
 app_process /product/framework/ com.pyzhang.main.HelloJava
Hello Java
2
4

App

android_library

给系统app添加依赖库

添加app

系统app源码添加。

添加带 JNI 的App

系统 App 与 普通 App 的差异:

系统 App 可以使用更多的 api:关于 SDK API 和非 SDK API 的内容可以参考官方文档

如果希望App 就能访问到非 SDK API:

  • 即希望 app 会使用平台 API 进行编译而不是 SDK
  • 在 Android.bp 中做了如下配置:
// platform_apis 为 true 时,sdk_version 必须为空。
platform_apis: true,
sdk_version: "",

系统 App 的签名

AOSP 内置了 apk 签名文件,我们可以在 Android.bp 中通过 certificate 配置系统 app 的签名文件,certificate 的值主要有一下几个选项:

  • testkey:普通 apk,默认情况下使用
  • platform:该 apk 完成一些系统的核心功能。经过对系统中存在的文件夹的访问测试,这种方式编译出来的 APK 所在进程的 UID 为system
  • shared:该 apk 需要和 home/contacts 进程共享数据
  • media:该 apk 是 media/download 系统中的一环
  • PRESIGNED:表示 这个 apk 已经签过名了,系统不需要再次签名;

系统 App 能使用更多的权限

当 Android.bp 中的 privileged 被配置为 true 时,我们的系统 App 在添加特许权限许可名单后,能使用 signatureOrSystem 级别的权限,而普通 App 是不能使用这些权限的。

系统 App 能更轻松地实现进程保活

三方 App 为了不被杀掉,可以说是用尽了千方百计。保活对于系统 App 其实是非常简单的:

在 AndroidManifest.xml 中添加如下参数即可:

<application
    android:persistent="true">
android_app {
    
}

prebuits 目录下找要添加的库(aar文件),如果没找到那就只能参照过往章节的方法添加库。

cd prebuilts
find . -name "Android.bp" | xargs grep "lottie"

编译系统app的jar包。包含因此API的jar包

拷贝文件

PRODUCT_COPY_FILES 常用于产品的配置文件中,用于将源码的文件拷贝到 Android 文件系统中。

源码中的示例:

aosp/build/target/product/core_64_bit.mk 中有如下内容:

PRODUCT_COPY_FILES += system/core/rootdir/init.zygote64_32.rc:system/etc/init/hw/init.zygote64_32.rc

将源码中的 system/core/rootdir/init.zygote64_32.rc 拷贝到 Android 文件系统的 system/etc/init/hw/init.zygote64_32.rc 文件中。

init.zygote64_32.rc 是 init 程序使用的一个配置文件,当我们的程序需要配置文件时,也可以参考以上的方式来完成。

相关文章:

  • 记一次命令行启动springboot项目的问题 java -jar的问题
  • docker安装RabbitMq
  • 【力扣】2626. 数组归约运算——认识循环
  • WebSocket相关技术
  • 关系型数据库的技术思路
  • 代码随想录第16天|找树左下角的值、 路径总和
  • 嵌入式八股,Linux驱动三大基础类
  • Hive面试:行列转换
  • HTML/CSS/JS
  • XFeat:轻量级的深度学习图像特征匹配
  • leetcode 912. 排序数组
  • 【Eureka 缓存机制】
  • 【大模型+知识图谱】大模型与知识图谱融合:技术演进、实践应用与未来挑战
  • ue5 3dcesium中从本地配置文件读取路3dtilles的路径
  • Module-info.java文件
  • Java 接口与实现类:为什么接口不能直接创建对象?
  • 数据库导出
  • 【2】VS Code 新建上位机项目---C#面向对象编程
  • IDEA中.gitignore未忽略指定文件的问题排查与解决
  • J-LangChain,用Java实现LangChain编排!轻松加载PDF、切分文档、向量化存储,再到智能问答
  • 中国戏剧梅花奖终评结果公示,蓝天、朱洁静等15名演员入选
  • 以色列媒体:以总理称将接管整个加沙
  • 无人机企业从科技园区搬到乡村后,村子里变得不一样了
  • 以色列称“将立即允许恢复”人道主义物资进入加沙
  • 中科院合肥物质院迎来新一届领导班子:刘建国继续担任院长
  • 83岁山水花鸟画家、书法家吴静山离世,系岭南画派代表人物