[Conceptos básicos de Android]: comprender en profundidad el sistema de compilación de Android

Visión de conjunto:

El sistema de compilación de Android es un marco para compilar el sistema Android, el SDK de Android y documentos relacionados. En el sistema Android, el código fuente de Android contiene muchos módulos. Los diferentes dispositivos de diferentes fabricantes tienen diferentes personalizaciones para el sistema Android. Cómo administrar estos módulos de manera unificada, cómo compilar en diferentes sistemas operativos, cómo admitir diferentes dispositivos de hardware y diferentes tipos de compilación al compilar, y también proporcionar extensiones personalizadas para cada paquete, sistema Android ¿Cómo resolver estos problemas? Este es el sistema de compilación de Android del que tenemos que hablar.

Estructura del directorio del código fuente de Android:


Comando make del sistema Linux

Antes de explicar el sistema de compilación de Android, primero debemos comprender el comando make del sistema Linux. En el sistema Linux, podemos compilar el código mediante el comando make. Cuando se ejecuta el comando Make, encontrará un Makefile en el directorio actual de forma predeterminada y luego compilará el código de acuerdo con las instrucciones del Makefile. Como gcc, el shell ordena cp, rm, etc. en sistemas Linux.

Al ver esto, algunos amigos pueden decir, ¿cuál es la diferencia entre el shell y los comandos make en un sistema Linux?

El comando make realmente realiza la tarea a través del comando de shell, pero su magia es que puede ayudarnos a lidiar con las dependencias entre archivos. Por ejemplo, hay un archivo T, que depende de otro archivo D, y se requiere regenerar el archivo T solo cuando el contenido del archivo D cambia.

¿Cómo sabe el comando Make que existe una dependencia entre dos archivos y cómo tratar con el archivo de destino cuando cambia el archivo dependiente? La respuesta es el Makefile mencionado anteriormente. El archivo Makefile es en realidad un archivo de secuencia de comandos, al igual que un archivo de secuencia de comandos de shell normal, excepto que sigue la sintaxis de Makefile. La función más básica del archivo Makefile es describir las dependencias entre archivos y cómo tratar con estas dependencias.

Núcleo del sistema de compilación de Android

El sistema de compilación de Android es parte del sistema de Android y se utiliza principalmente para compilar el sistema de Android, el SDK de Android y documentos relacionados. El sistema se compone principalmente de archivos Make, scripts de Shell y scripts de Python.

Categoría de compilación de Android:

  •  Archivos en el directorio build / core, que son el núcleo del marco del sistema de Android Build;
  • Los archivos del directorio de dispositivos almacenan archivos de configuración de productos específicos;
  • El archivo compilado de cada módulo: Android.mk, ubicado en el directorio de archivos fuente del módulo.

Núcleo del sistema de compilación de Android

El núcleo del sistema de compilación de Android se encuentra en el directorio build / core. Hay archivos mk, scripts de shell y scripts per en este directorio, que forman la base y la arquitectura del sistema de compilación de Android.

En el core build / core, el sistema hace principalmente tres cosas:

Comandos de uso común:

fuente build / envsetup.sh

comida

hacer

envsetup.sh

Para compilar el sistema Android, primero debe ejecutar el script envsetup.sh, que establecerá el entorno de compilación de Android. La implementación específica es crear un comando de shell y llamar al comando add_lunch_combo Este comando almacena los parámetros pasados ​​de la llamada a una variable de matriz global LUNCH_MENU_CHOICES.

Comandos de shell comunes definidos en el script envsetup.sh:

mando Descripción
botón de contacto Especifique el producto compilado actualmente
croot Cambie rápidamente al directorio raíz del código fuente para una fácil compilación
metro Compile todo el código fuente sin cambiar el directorio actual al directorio raíz del código fuente
mm Compile todos los módulos en el directorio actual, pero no compile sus dependencias
mmm Compile todos los módulos en el directorio especificado, pero no compile sus dependencias
cgrep Ejecute el comando grep en todos los archivos C / C ++ en el sistema
chillido Ejecute el comando grep en todos los archivos fuente del sistema

 

Compilar el sistema Android

El entorno de compilación del sistema Android actualmente solo admite dos sistemas operativos, Ubuntu y Mac OS. Antes de compilar el sistema Android, necesitamos obtener el código fuente completo de Android. Después de abrir la consola, vaya al directorio raíz del código fuente de Android y luego ejecute el siguiente comando:

fuente build / envsetup.sh

almuerzo full-eng

hacer -j8

En cuanto al significado de estos comandos, lo mencionamos anteriormente.

El comando de primer paso "source build / envsetup.sh" introduce el script build / envsetup.sh, que se utiliza para inicializar el entorno de compilación e introducir algunas funciones auxiliares de Shell;

El comando del segundo paso "lunch full-eng" es llamar a la función lunch y especificar el parámetro como "full-eng". Los parámetros de la función lunch se utilizan para especificar el dispositivo de destino para esta compilación y el tipo de compilación.

El tercer paso es ordenar "make -j8" para comenzar a compilar. El parámetro "-j" de make especifica el número de trabajos que se compilarán al mismo tiempo. Este es un número entero. El valor suele ser 1 o 2 veces el número total de subprocesos simultáneos admitidos por la CPU del host de compilación. El tiempo de compilación completo depende de la configuración del host de compilación.

 

Resultado de la construcción

El más importante de los productos de compilación son tres archivos de imagen, todos los cuales se encuentran en el directorio / out / target / product //:

  • system.img: contiene los archivos del sistema, bibliotecas, archivos ejecutables y aplicaciones preestablecidas del sistema operativo Android, que se montarán como la partición raíz.
  • ramdisk.img: El kernel de Linux lo montará como una partición de solo lectura al inicio, y contiene el archivo / init y algunos archivos de configuración. Se utiliza para montar otras imágenes del sistema e iniciar el proceso de inicio.
  • userdata.img: se montará como / data, que contiene datos relacionados con la aplicación y datos relacionados con el usuario.

Hacer archivo

El archivo de entrada de todo el sistema de compilación es un archivo llamado "Makefile" en el directorio raíz del árbol del código fuente. Cuando se invoca el comando make en el directorio raíz del código fuente, el comando make primero leerá el archivo.

El contenido del archivo Makefile tiene solo una línea: "include build / core / main.mk". El efecto de esta línea de código es obvio: contiene el archivo build / core / main.mk. El archivo main.mk contendrá otros archivos, y otros archivos contendrán más archivos, introduciendo así todo el sistema de compilación.

En todo el sistema Build, la relación entre los archivos Make es bastante complicada. Mira el diagrama de relación principal de un archivo make:

 

Crea archivos comunes:

nombre del archivo Descripción
main.mk

El archivo make principal, en el que se comprueba primero el entorno de compilación, y otros archivos Make se introducen al mismo tiempo. Además, en este archivo se definen varios objetivos principales de Make, como droid, sdk, etc. (consulte "Descripción de Make Target" a continuación).

help.mk

Contiene la definición de un objetivo de Make llamado help, que enumerará los principales objetivos de Make y sus descripciones.

envsetup.mk Configure las variables de entorno requeridas por el sistema de compilación, como: TARGET_PRODUCT, TARGET_BUILD_VARIANT, HOST_OS, HOST_ARCH, etc. La información de la plataforma de host actualmente compilada (como el sistema operativo, el tipo de CPU, etc.) se determina en este archivo. Además, el archivo también especifica la ruta de salida de varios resultados de compilación.
pathmap.mk Defina la ruta de muchos archivos de encabezado como una tabla de mapeo por nombre y valor, y proporcione la función include-path-for para obtener
combo / select.mk

Seleccione el archivo Make relacionado con la plataforma de acuerdo con la plataforma del compilador actual.

dumpvar.mk

Antes de que comience la compilación, se muestra la información de configuración de esta compilación.

config.mk El archivo de configuración de todo el sistema Build, uno de los archivos Make más importantes. El archivo contiene principalmente el siguiente contenido: Se definen muchas constantes para ser responsables de la compilación de diferentes tipos de módulos. Defina los parámetros del compilador y los sufijos de archivos comunes, como .zip, .jar, .apk. Configure los parámetros relacionados con el producto de acuerdo con el archivo BoardConfig.mk. Establezca la ruta de algunas herramientas comunes, como flex, e2fsck, dx.
definiciones.mk Uno de los archivos Make más importantes, en el que se definen una gran cantidad de funciones. Estas funciones las utilizan otros archivos del sistema de compilación. Por ejemplo: my-dri, all-subdir-makefiles, find-subdir-files, sign-package, etc. Para la descripción de estas funciones, consulte los comentarios de código de cada función.
distdir.mk Definición del objetivo dist. El destino dist se utiliza para copiar archivos en la ruta especificada.
dex_preopt.mk

Pre-optimizado para iniciar el paquete jar.

pdk_config.mk Como sugiere el nombre, es un archivo de configuración para pdk (Kit de desarrollo de plataforma).
post_clean.mk Verifique la configuración de compilación actual basada en la compilación anterior y realice el trabajo de limpieza necesario.
legacy_prebuilts.mk Solo la variable GRANDFATHERED_ALL_PREBUILT está definida en este archivo.
Makefile Incluido por main.mk, el contenido de este archivo es contenido adicional que ayuda a main.mk.

El código fuente de Android contiene muchos módulos, y hay muchos tipos de módulos, como: bibliotecas Java, bibliotecas C / C ++, aplicaciones APK, archivos ejecutables, etc. Además, las bibliotecas Java o C / C ++ se pueden clasificar como estáticas o dinámicas. La biblioteca o el archivo ejecutable pueden ser específicos del dispositivo (en este artículo, "dispositivo" se refiere al dispositivo donde se instalará el sistema Android, como un dispositivo móvil teléfono con una determinada señal. O tableta) también puede ser para el host (el "host" en este artículo se refiere al desarrollo de la máquina del sistema Android, como una PC con sistema operativo Ubuntu o una iMac o Macbook con MacOS). Los pasos y métodos de compilación de los diferentes tipos de módulos son diferentes. Para poder realizar la compilación de varios tipos de módulos de una manera coherente y conveniente, se definen muchas constantes en config.mk, cada una de las cuales describe un tipo de módulo El método de compilación. Los más comunes son:

  • BUILD_HOST_STATIC_LIBRARY
  • BUILD_HOST_SHARED_LIBRARY
  • BUILD_STATIC_LIBRARY
  • BUILD_SHARED_LIBRARY
  • BUILD_EXECUTABLE
  • BUILD_BUILD_HOST_EXECUTABLE
  • BUILD_PACKAGE
  • BUILD_PREBUILT
  • BUILD_MULTI_PREBUILT
  • BUILD_HOST_PREBUILT
  • BUILD_JAVA_LIBRARY
  • BUILD_STATIC_JAVA_LIBRARY
  • BUILD_HOST_JAVA_LIBRARY

不同类型的模块的编译过程会有一些相同的步骤,例如:编译一个 Java 库和编译一个 APK 文件都需要定义如何编译 Java 文件。为了减少代码冗余,需要将共同的代码复用起来,复用的方式是将共同代码放到专门的文件中,然后在其他文件中包含这些文件的方式来实现的。模块的编译方式定义文件包含关系:

Make 编译镜像

make/make droid

如果在源码树的根目录直接调用 “make” 命令而不指定任何目标,则会选择默认目标:“driod”(在 main.mk 中定义)。因此,这和执行 “make droid” 效果是一样的。droid 目标将编译出整个系统的镜像。从源代码到编译出系统镜像,整个编译过程非常复杂。这个过程并不是在 droid 一个目标中定义的,而是 droid 目标会依赖许多其他的目标,这些目标的互相配合导致了整个系统的编译。那么需要编译出系统镜像,需要哪些依赖呢?

droid 所依赖的其他 Make 目标说明:

名称 说明
apps_only 该目标将编译出当前配置下不包含 user,userdebug,eng 标签(关于标签,请参见后文 “添加新的模块”)的应用程序。
droidcore 该目标仅仅是所依赖的几个目标的组合,其本身不做更多的处理。
dist_files 该目标用来拷贝文件到 /out/dist 目录。
files 该目标仅仅是所依赖的几个目标的组合,其本身不做跟多的处理。
prebuilt 该目标依赖于(ALL_PREBUILT),(ALL_PREBUILT)的作用就是处理所有已编译好的文件。
$(modules_to_install) modules_to_install 变量包好了当前配置下所有会被安装的模块(一个模块是否会被安装依赖于该产品的配置文件,模块的标签等信息),因此该目标将导致所有会被安装的模块的编译。
$(modules_to_check)

该目标用来确保我们定义的构建模块是没用冗余的。

$(INSTALLED_ANDROID_INFO_TXT_TARGET) 该目标会生成一个关于当前 Build 配置的设备信息的文件,该文件的生成路径是:out/target/product/android-info.txt。
systemimage 生成 system.img。

Build 系统中抱哈的其他的一些 Make 目标:

Make 目标说明 说明
make clean 执行清理,等同于:rm -rf out/
make sdk 编译出 Android 的 SDK
Make 目标说明 说明
make clean-sdk 清理 SDK 的编译产物
make update-api 更新 API。在 framework API 改动之后,需要首先执行该命令来更新 API,公开的 API 记录在 framworks/base/api 目录下。
make dist 执行 Build,并将 MAKECMDGOALS 变量定义的输出文件拷贝到 /out/dist 目录。
make all 编译所有内容,不管当前产品的定义宏是否会包含。
make help 帮助信息
make snod 从已经编译出的包快速重建系统镜像
make libandroid_runtime 编译所有 JNI framework 内容
make  framework 编译所有 Java  framework 内容
make services 编译系统服务和相关内容
make 编译一个指定的模块,local_target 为模块的名称
make clean- 清理一个指定模块的编译结果
make dump-products 显示所有产品的编译配置信息,例如:产品名,产品支持的地区语言,产品中会包含的模块等信息
make PRODUCT-xxx-yyy 编译某个指定的产品
make bootimage 生成 boot.img

定制 Build 系统中内容

当我们要开发一款新的 Andorid 产品的时候,我们首先就需要在 Build 系统中添加对于该产品的定义。在 Android Build 系统中对产品定义的文件通常位于 device 目录下,device 目录下可以公司名以及产品名分为二级目录,然后加入到系统中,如以前小米等基于 Android 深度定制的系统。通常,对于一个产品的定义通常至少会包括四个文件:

  • AndroidProducts.mk;
  • 产品版本定义文件;
  • BoardConfig.mk;
  • verndorsetup.sh;

AndroidProducts.mk:

该文件只需要定义一个变量,名称为 "PRODUCT_MAKEFILES"。

PRODUCT_MAKEFILES := \
    $(LOCAL_DIR)/full_string.mk \
    $(LOCAL_DIR)/stringray_emu.mk \
    $(LOCAL_DIR)/generic_stringray.mk

产品版本定义文件:

该文件中包含了对于特定产品版本的定义。该文件可能不止一个,因为同一个产品可能会有多种版本。通常情况下我们并不需要定义所有这些变量。Build 系统的已经预先定义好了一些组合,它们都位于 /build/target/product 下,每个文件定义了一个组合,我们只要集成这些预置的定义,然后再覆盖自己想要的变量定义即可。

# 继承 full_base.mk 文件中的定义
$(call inherit-product, $(SRC_TARGET_DIR)/product/full_base.mk)
#覆盖其中已经定义的一些变量
PRODUCT_NAME := full_lt26
PRODUCT_DEIVCE := lt26
PRODUCT_BRAND := Android
PRODUCT_MODEL := Full Android on LT26

BoardConfig.mk:

该文件用来配置硬件主板,它其中定义的都是设备底层的硬件特性。例如:该设备的主板相关信息,WiFi 相关信息,还有 BootLoader,内核,radioimage 等信息。

vendorsetup.sh:

该文件中作用是通过 add_lunch_combo 函数在 lunch 函数中添加一个菜单选项。该函数的参数是产品名称加上编译类型,中间以 "-" 连接,例如:

add_lunch_combo full_lt26-userdebug

/build/envsetup.sh 会扫描所有 device 和 vendor 二级目录下的名称为 "vendorsetup.sh" 文件,并根据其中的内容来确定 lunch 函数的菜单选项。在配置了以上的文件之后,便可以编译出我们新添加的设备的系统镜像了。我们可以使用命令:

source build/envsetup.sh

来查看 Build 系统已经引入了刚刚添加的 vendorsetup.sh 文件。

添加新模块在源码树中

一个模块的所有文件通常都位于同一个文件夹中。为了将当前模块添加到整个 Build 系统中,每个模块都需要一个专门的 Make 文件,该文件的名称为 "Android.mk"。Build 系统会扫描名称为 "Android.mk" 的文件,并根据该文件中内容编译出相应的产物。

注:在 Android Build 系统中,编译是以模块(而不是文件)作为单位的,每个模块都有一个唯一的名称,一个模块的依赖对象只能是另外一个模块,而不能是其他类型的对象。

对于已经编译好的库使用 BUILD_PREBUILT 或 BUILD_MULTI_PREBUILT。例如:当编译某个 Java 库需要依赖一些 Jar 包时,并不能直接指定 Jar 包的路径作为依赖,而必须先将这些 Jar 包定义为一个模块,然后在编译 Java 库的时候通过模块的名称来依赖这些 Jar 包。那么怎么编写 Android.mk 文件呢?Android.mk 文件通常以以下两行代码作为开头:

LOCAL_PATH := $(call my-dir) //设置单枪模块的编译路径为当前文件夹路径
    include $(CLEAR_VARS) //清理编译环境中用到的变量

为了方便模块的编译,Build 系统设置了很多的编译环境变量。要编译一个模块,只要在编译之前根据需要设置这些变量然后执行编译即可。常见的如:

  • LOCAL_SRC_FILES:当前模块包含的所有源代码文件。
  • LOCAL_MODULE:当前模块的名称,这个名称应当是唯一的,模块间的依赖关系就是通过这个名称来引用的。
  • LOCAL_C_INCLUDES:C 或 C++ 语言需要的头文件的路径。
  • LOCAL_STATIC_LIBRARIES:当前模块在静态链接时需要的库的名称。
  • LOCAL_SHARED_LIBRARIES:当前模块在运行时依赖的动态库的名称。
  • LOCAL_CFLAGS:提供给 C/C++ 编译器的额外的编译参数。
  • LOCAL_JAVA_LIBRARIES:当前模块依赖的 Java 共享库。
  • LOCAL_STATIC_JAVA_LIBRARIES:当前模块依赖的 Java 静态库。
  • LOCAL_PACKAGE_NAME:当前 APK 应用的名称。
  • LOCAL_CERTIFICATE:签署当前应用的证书名称。
  • LOCAL_MODULE_TAGS:当前模块所包含的标签,一个模块可以包含多个标签。

标签的值可能是 debug,eng,user,development 或者 optional。其中,optional 是默认标签。标签是提供给编译类型使用的,不同的编译类型会安装包含不同标签的模块。编译类型说明:

名称 说明
eng 默认类型,该编译类型适用于开发阶段。当选择这种类型时,编译结果将:安装包含 eng,debug,user,development 标签的模块;安装所有没有标签的非 APK 模块;安装所有产品定义文件中指定的 APK 模块。
user

该编译类型适合用于最终发布阶段。当选择这种类型时,编译结果将:安装所有带有 user 标签的模块;安装所有没有标签的非 APK 模块;安装所有产品定义文件中指定的 APK 模块,APK 模块的标签将被忽略。

userdebug 该编译类型适合用于 debug 阶段。该类型和 user 一样,除了:会安装包含 debug 标签的模块,编译出的系统具有 root 访问权限。

根据上表各种类型模块的编译方式,要执行编译,只需要引入表3 中对应的 Make 文件即可。例如,要编译一个 APK 文件,只需要在 Android.mk 文件中,加入 “include $(BUILD_PACKAGE)”。除此以外,Build 系统中还定义了一些便捷的函数以便在 Android.mk 中使用,包括:

  • $(call my-dir):获取当前文件夹路径。
  • $(call all-java-files-under):获取指定目录下的所有 Java 语言文件。
  • $(call all-c-files-under):获取指定目录下的所有 C 语言文件。
  • $(call all-laidl-files-under):获取指定目录下的所有 AIDL 文件。
  • $(call all-makefile-under):获取指定目录下的所有 Make 文件。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#获取所有子目录中的 Java 文件
LOCAL_SRC_FILES := $(call all-subdir-java-files)
#当前模块依赖的静态 Java 库,如果有多个以空格分隔
LOCAL_STATIC_JAVA_LIBRARIES := static-library
#当前模块的名称
LOCAL_PACKAGE_NAME := LocalPackage
#编译 APK 文件
include $(BUILD_PACKAGE)

编译一个 Java 的静态库:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

#获取所有子目录中的 Java 文件
LOCAL_SRC_FILES := $(call all-subdir-java-files)

#当前模块依赖的动态 Java 库名称
LOCAL_JAVA_LIBRARIES := android.test.runner

#当前模块的名称
LOCAL_MODULE := sample

#将当前模块编译成一个静态的 Java 库
include $(BUILD_STATIC_JAVA_LIBRARY)

 

Supongo que te gusta

Origin blog.csdn.net/u014674293/article/details/105158562
Recomendado
Clasificación