Android Runtime(ART)构建系统与编译配置文件详解(7)

374 阅读13分钟

一、ART构建系统概述

Android Runtime(ART)的构建系统是整个Android开源项目(AOSP)构建体系的重要组成部分,它负责将ART的源代码转换为可在Android设备上运行的二进制文件、库以及相关资源。ART构建系统基于AOSP的通用构建框架,采用模块化、层次化的设计,通过一系列编译配置文件来控制编译过程。理解ART构建系统与编译配置文件,对于开发者定制ART功能、优化编译流程以及解决编译相关问题具有重要意义。

ART构建系统的核心目标是确保ART在不同硬件架构(如ARM、x86)、不同编译模式(AOT、JIT)下都能正确编译和运行。它需要处理大量的源代码文件,涉及C++、Java等多种编程语言,同时还要管理复杂的依赖关系,包括对系统库、第三方库以及其他Android模块的依赖 。

二、AOSP构建系统基础

2.1 构建系统核心组件

AOSP构建系统的核心组件包括make工具和Blueprintbp)语言。make工具是传统的构建工具,通过解析Makefile文件来执行构建任务。而Blueprint是AOSP引入的新型构建描述语言,相比传统的Makefile,它具有更简洁的语法、更好的可读性和更强的表达能力,逐渐成为AOSP构建系统的主流方式。

在AOSP中,build/目录是构建系统的核心目录,包含了大量与构建相关的脚本和配置文件。其中,make/目录存放了make工具相关的配置和规则,tools/目录提供了一些辅助构建的工具,bp/目录则是Blueprint相关的实现。

2.2 构建系统工作流程

AOSP构建系统的工作流程大致如下:首先,构建系统读取各种编译配置文件,包括Android.bpAndroid.mk等,这些文件定义了模块的编译规则、依赖关系以及输出产物。然后,根据目标设备的硬件架构、编译模式等参数,构建系统对源代码进行预处理、编译、链接等操作。最后,将编译生成的二进制文件、库文件和资源文件打包成适合目标设备的格式,如APK、OAT文件等。

以编译一个简单的Android应用为例,构建系统会先解析应用的Android.bp文件,确定应用所依赖的库和资源。接着,对应用的Java源代码进行编译,生成DEX字节码。如果开启了AOT编译,还会将DEX字节码进一步编译成OAT文件。最后,将DEX文件、OAT文件以及其他资源文件打包成APK文件,供设备安装和运行。

2.3 构建系统变量与宏

AOSP构建系统定义了大量的变量和宏,用于控制编译过程。这些变量和宏可以在编译配置文件中使用,也可以在构建命令中通过参数传递进行覆盖。

例如,TARGET_ARCH变量表示目标设备的硬件架构,常见的值有armx86等;TARGET_BUILD_VARIANT变量表示编译变体,如useruserdebugeng等,不同的变体具有不同的功能和权限;LOCAL_SRC_FILES宏用于指定模块的源代码文件列表;LOCAL_SHARED_LIBRARIES宏用于指定模块依赖的共享库。

# 示例:在Android.mk文件中使用变量和宏
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := my_module
LOCAL_SRC_FILES := my_source_file.c
LOCAL_SHARED_LIBRARIES := libother
include $(BUILD_SHARED_LIBRARY)

在上述代码中,LOCAL_PATH变量通过my-dir函数获取当前目录路径,CLEAR_VARS宏用于清除之前设置的编译变量,BUILD_SHARED_LIBRARY宏表示将当前模块编译为共享库。

三、ART编译配置文件总览

3.1 主要编译配置文件

ART的编译配置文件主要分布在art/目录及其子目录中,包括Android.bpconfig.mk等文件。这些文件从不同角度对ART的编译过程进行配置,共同决定了ART的编译结果。

Android.bp文件是基于Blueprint语言的编译配置文件,用于定义ART模块的编译规则、依赖关系、编译选项等。在art/目录下的Android.bp文件定义了ART的核心模块编译规则,而在各个子目录(如runtime/compiler/)下的Android.bp文件则针对具体模块进行更细致的配置。

config.mk文件是传统的Makefile风格的配置文件,主要用于设置一些编译参数和选项,如编译器标志、优化级别、调试信息开关等。config.mk文件中的配置会影响整个ART模块的编译过程。

3.2 配置文件层次结构

ART编译配置文件具有明显的层次结构。最上层是art/Android.bp文件,它对ART整体进行配置,包括定义ART的核心模块、设置全局编译选项等。然后,各个子模块目录下的Android.bp文件继承并细化上层的配置,针对具体模块的特点进行定制。

例如,art/runtime/Android.bp文件在继承art/Android.bp文件的基础上,专门配置了runtime模块的编译规则,包括该模块的源代码文件列表、依赖的其他模块等;art/compiler/Android.bp文件则专注于compiler模块的编译配置,如编译优化选项、与其他编译相关模块的依赖关系。

3.3 配置文件间的依赖与传递

ART编译配置文件之间存在着复杂的依赖与传递关系。一个模块的编译配置可能依赖于其他模块的配置,同时也会影响到依赖它的模块的编译。

例如,compiler模块的编译可能依赖于runtime模块中定义的一些数据结构和接口,因此在compiler/Android.bp文件中需要指定对runtime模块的依赖。当runtime模块的编译配置发生变化时,可能会影响到compiler模块的编译,构建系统会根据依赖关系自动处理这种变化,重新编译受影响的模块。

此外,一些全局的编译配置选项会在各个配置文件间传递。比如,在art/Android.bp文件中设置的优化级别,会通过配置文件的继承关系传递到各个子模块的Android.bp文件中,影响每个模块的编译过程。

四、Android.bp文件详解

4.1 Android.bp文件语法基础

Blueprint语言采用简洁的语法来描述模块的编译配置。一个基本的Android.bp文件由一系列模块定义组成,每个模块定义包含模块类型、模块名称、属性设置等部分。

模块类型用于指定模块的性质,常见的模块类型有cc_library(C/C++静态库)、cc_library_shared(C/C++共享库)、java_library(Java库)等。模块名称是模块的唯一标识,用于在构建系统中引用该模块。属性设置则用于描述模块的具体编译规则和特性。

// 示例:定义一个C++静态库模块
cc_library {
    name: "libart_base",
    srcs: [
        "base/file_utils.cc",
        "base/macros.cc",
        // 其他源文件...
    ],
    cflags: [
        "-Wall",
        "-Werror",
    ],
    shared_libs: [
        "libcutils",
    ],
}

在上述代码中,cc_library表示模块类型为C++静态库;name属性指定模块名称为libart_basesrcs属性列出了该模块的源代码文件;cflags属性设置了编译C++代码时的编译器标志;shared_libs属性指定了该模块依赖的共享库。

4.2 ART核心模块的Android.bp配置

art/Android.bp文件中,定义了ART的核心模块,这些模块是ART运行的基础。例如,libart模块是ART的核心库,包含了运行时的核心功能。

cc_library_shared {
    name: "libart",
    srcs: [
        "runtime/art_method.cc",
        "runtime/thread.cc",
        "runtime/class_linker.cc",
        // 更多源文件...
    ],
    cflags: [
        "-DART_ENABLE_JIT",
    ],
    shared_libs: [
        "libandroid_runtime",
        "libcutils",
        "liblog",
    ],
    export_include_dirs: [
        "include",
    ],
}

上述代码中,cc_library_shared表明libart是一个C++共享库。srcs属性包含了runtime模块中实现核心功能的源文件;cflags属性定义了编译时的宏DART_ENABLE_JIT,表示启用JIT编译功能;shared_libs属性列出了该模块依赖的其他共享库;export_include_dirs属性指定了该模块对外导出的头文件目录。

4.3 子模块的Android.bp定制

各个子模块目录下的Android.bp文件对核心模块进行细化和扩展。以compiler模块为例,art/compiler/Android.bp文件配置了编译优化相关的功能。

cc_library {
    name: "libart_compiler",
    srcs: [
        "driver/compilation_driver.cc",
        "optimizing/optimizing_compiler.cc",
        "utils/graph_utils.cc",
        // 更多源文件...
    ],
    cflags: [
        "-O3",  // 优化级别设置为O3
        "-fno-strict-aliasing",
    ],
    shared_libs: [
        "libart",
        "libcompiler_rt",
    ],
    static_libs: [
        "libbase",
    ],
}

在这个配置中,libart_compiler模块是一个C++静态库。srcs属性包含了编译驱动、优化编译器等相关源文件;cflags属性设置了较高的优化级别O3以及一些编译器选项;shared_libs属性指定了对libart等共享库的依赖;static_libs属性指定了对libbase等静态库的依赖。这些配置使得compiler模块能够按照特定的要求进行编译,实现高效的编译优化功能。

五、config.mk文件详解

5.1 编译参数设置

config.mk文件主要用于设置编译参数,这些参数会影响整个ART模块的编译过程。其中,最常见的是编译器相关参数的设置。

# 设置C++编译器
CXX := $(TARGET_TOOLS_PREFIX)g++
# 设置C编译器
CC := $(TARGET_TOOLS_PREFIX)gcc

# C++编译器标志
CXXFLAGS += -std=c++11
CXXFLAGS += -fexceptions
CXXFLAGS += -frtti

# C编译器标志
CFLAGS += -Wall
CFLAGS += -Werror

上述代码中,CXXCC变量分别指定了C++编译器和C编译器。CXXFLAGSCFLAGS变量用于设置编译器标志,例如-std=c++11表示使用C++11标准,-fexceptions表示启用异常处理,-frtti表示启用运行时类型信息;-Wall表示开启所有警告,-Werror表示将警告视为错误。

5.2 优化与调试选项配置

config.mk文件还可以配置优化与调试相关的选项。在优化方面,可以设置编译优化级别,以提高生成代码的执行效率。

# 优化级别设置
ifeq ($(TARGET_BUILD_VARIANT),user)
    CXXFLAGS += -O3
    CFLAGS += -O3
else
    CXXFLAGS += -O2
    CFLAGS += -O2
endif

上述代码根据TARGET_BUILD_VARIANT变量的值来设置不同的优化级别。当编译变体为user时,使用较高的优化级别O3;其他情况下使用O2级别。

在调试方面,可以设置是否生成调试信息、启用哪些调试功能等。

# 调试信息生成
ifeq ($(TARGET_BUILD_VARIANT),userdebug)
    CXXFLAGS += -g
    CFLAGS += -g
    WITH_DEBUG_INFO := true
endif

当编译变体为userdebug时,通过-g标志生成调试信息,并且设置WITH_DEBUG_INFO变量为true,以便在后续的调试过程中使用这些调试信息。

5.3 宏定义与条件编译

config.mk文件支持宏定义和条件编译,通过宏定义可以定义一些编译时使用的常量,条件编译则可以根据不同的条件选择不同的编译配置。

# 宏定义
ART_VERSION := "1.0"
DEBUG_MODE := false

# 条件编译
ifeq ($(TARGET_ARCH),arm)
    CFLAGS += -march=armv7-a
    CXXFLAGS += -march=armv7-a
else ifeq ($(TARGET_ARCH),x86)
    CFLAGS += -march=i686
    CXXFLAGS += -march=i686
endif

在上述代码中,定义了ART_VERSIONDEBUG_MODE两个宏。然后,通过ifeq条件语句,根据TARGET_ARCH变量的值(即目标设备的硬件架构),为不同架构设置相应的编译器标志,实现针对不同架构的定制编译。

六、编译目标与产物

6.1 常见编译目标

ART的编译过程会生成多个编译目标,这些目标根据功能和用途的不同可以分为库文件、可执行文件和工具等。

库文件是ART编译的重要产物,包括核心库libart以及各个子模块生成的库。例如,libart_runtime库包含了运行时相关的功能实现,libart_compiler库包含了编译优化的功能实现。这些库文件为ART的运行和功能扩展提供了基础支持。

可执行文件主要用于一些特定的任务,如dex2oat工具,它用于将DEX字节码编译成OAT文件;oatdump工具用于查看OAT文件的内容。这些可执行文件在ART的运行和维护过程中发挥着重要作用。

6.2 编译产物生成过程

libart库的生成过程为例,首先,构建系统根据art/Android.bp文件和config.mk文件中的配置,确定编译libart库所需的源代码文件、编译器标志、依赖库等信息。然后,构建系统调用C++编译器对源代码文件进行编译,生成目标文件(.o文件)。

$(TARGET_TOOLS_PREFIX)g++ -c -o art_method.o runtime/art_method.cc $(CXXFLAGS)

上述命令使用C++编译器将runtime/art_method.cc文件编译成目标文件art_method.o-c选项表示只进行编译不进行链接,-o选项指定输出的目标文件名称,$(CXXFLAGS)表示使用在config.mk文件中设置的C++编译器标志。

接着,构建系统将所有的目标文件进行链接,生成共享库文件libart.so

$(TARGET_TOOLS_PREFIX)g++ -shared -o libart.so art_method.o other_methods.o $(LDFLAGS) $(SHARED_LIBS)

-shared选项表示生成共享库,-o选项指定输出的共享库文件名称,$(LDFLAGS)表示链接器标志,$(SHARED_LIBS)表示依赖的共享库。

对于dex2oat工具的生成,过程类似。首先编译源代码生成目标文件,然后将目标文件链接成可执行文件。在链接过程中,需要链接到libart库以及其他相关库,确保dex2oat工具能够正常调用ART的功能。

6.3 产物的组织与部署

编译生成的产物会按照一定的规则进行组织和部署。库文件通常会被放置在系统的库目录中,如/system/lib/system/lib64,以便在运行时被系统和应用程序加载。

可执行文件会被放置在系统的可执行文件目录中,如/system/bin。在Android系统镜像构建过程中,这些编译产物会被打包到相应的镜像分区中,最终部署到Android设备上。

例如,在生成Android系统镜像时,构建系统会将libart.so库文件复制到系统镜像的/system/lib目录下,将dex2oat工具复制到/system/bin目录下。这样,当设备启动后,ART相关的库和工具就可以正常使用,为应用程序的运行提供支持。

七、多架构编译支持

7.1 架构相关配置变量

为了支持在不同硬件架构上编译ART,构建系统定义了一系列与架构相关的配置变量。其中,TARGET_ARCH变量是最关键的变量,它用于指定目标设备的硬件架构,常见的值有armarm64x86x86_64等。

除了TARGET_ARCH变量,还有一些与架构相关的编译器标志变量。例如,TARGET_CPU_ABI变量表示目标设备的CPU应用二进制接口,不同的架构有不同的ABI,如armeabi-v7aarm64-v8a

7.4 架构特定代码处理

在ART源代码中,存在大量针对不同硬件架构编写的特定代码。构建系统通过条件编译和宏定义机制,实现对这些架构特定代码的处理。例如,在处理ARM架构的指令生成代码时:

#if defined(TARGET_ARCH_ARM)
void GenerateArmSpecificInstruction(Instruction* insn) {
    // ARM架构下特定指令生成逻辑
    // 如根据ARM指令集编码规则设置操作码、操作数等
    insn->opcode = ARM_SPECIFIC_OPCODE;
    insn->operand1 = GetArmRegister();
    //...
}
#endif

上述代码通过#if defined(TARGET_ARCH_ARM)条件编译语句,当TARGET_ARCH被定义为arm时,才会编译这段ARM架构特定的指令生成代码。在config.mk文件中,会根据TARGET_ARCH变量的值定义相应的宏,如TARGET_ARCH_ARM

ifeq ($(TARGET_ARCH),arm)
    CFLAGS += -DTARGET_ARCH_ARM
    CXXFLAGS += -DTARGET_ARCH_ARM
endif

这样,在编译过程中,编译器就能识别并处理这些架构特定代码,生成对应架构的目标代码。

对于x86架构也类似,在代码中会有针对x86架构的条件编译部分:

#if defined(TARGET_ARCH_X86)
void GenerateX86SpecificInstruction(Instruction* insn) {
    // x86架构下特定指令生成逻辑
    insn->opcode = X86_SPECIFIC_OPCODE;
    insn->operand2 = GetX86Register();
    //...
}
#endif

并且在config.mk中配置相应的宏定义:

ifeq ($(TARGET_ARCH),x86)
    CFLAGS += -DTARGET_ARCH_X86
    CXXFLAGS += -DTARGET_ARCH_X86
endif

通过这种方式,ART能够针对不同硬件架构,准确处理各自的特定代码,生成适配的二进制文件 。

7.5 交叉编译机制

由于Android设备的硬件架构多样,而开发环境通常是在PC上,因此交叉编译在ART构建中十分关键。交叉编译是指在一种架构的机器上编译出另一种架构的可执行代码或库。

在ART构建系统中,交叉编译工具链由TARGET_TOOLS_PREFIX变量指定。例如,对于ARM架构的交叉编译,TARGET_TOOLS_PREFIX可能被设置为arm-linux-androideabi-。在config.mk文件中可以看到相关配置:

ifeq ($(TARGET_ARCH),arm)
    TARGET_TOOLS_PREFIX := arm-linux-androideabi-
    CXX := $(TARGET_TOOLS_PREFIX)g++
    CC := $(TARGET_TOOLS_PREFIX)gcc
endif

当进行ARM架构的编译时,构建系统会使用arm-linux-androideabi-g++arm-linux-androideabi-gcc等交叉编译工具。这些工具包含了针对ARM架构的编译器、汇编器和链接器等组件。

在编译过程中,交叉编译器会根据目标架构的特性进行代码生成和优化。例如,在生成ARM架构的机器码时,会使用ARM指令集相关的编译选项和优化策略。同时,链接器也会按照目标架构的要求,处理库文件的链接,确保生成的二进制文件能够在ARM设备上正确运行。

通过交叉编译机制,开发者可以在PC开发环境中,为不同硬件架构的Android设备编译ART,极大地提高了开发效率和灵活性。

7.6 多架构产物管理

ART构建系统在生成多架构编译产物后,需要对这些产物进行有效的管理。不同架构的编译产物会被放置在特定的目录结构中,以便在系统镜像构建和设备部署时能够准确识别和使用。

在AOSP的构建输出目录中,会为每个架构创建对应的子目录。例如,对于ARM架构的编译产物,可能会放置在out/target/product/device_name/obj/arm/目录下;对于x86架构的产物,则在out/target/product/device_name/obj/x86/目录。

Android.bp文件中,可以通过配置选项来指定产物的输出路径和命名规则。以libart库为例:

cc_library_shared {
    name: "libart",
    srcs: [...],
    arch: {
        arm: {
            output_dir: "out/target/product/device_name/obj/arm/system/lib",
            soname: "libart.so",
        },
        x86: {
            output_dir: "out/target/product/device_name/obj/x86/system/lib",
            soname: "libart.so",
        },
    },
    // 其他配置...
}

上述代码中,针对ARM和x86架构,分别指定了libart库的输出目录和共享库名称。这样,构建系统在编译完成后,会将不同架构的libart库放置到相应的目录中。

在系统镜像构建阶段,构建脚本会根据设备的架构信息,从对应的目录中选取正确的ART编译产物,打包到系统镜像中。例如,如果设备是ARM架构,就会从out/target/product/device_name/obj/arm/目录中选取相关的库文件和可执行文件,确保设备能够运行适配自身架构的ART。

八、编译模式与选项配置

8.1 AOT与JIT编译模式配置

ART支持AOT(Ahead - Of - Time)和JIT(Just - In - Time)两种编译模式,这两种模式在性能和资源占用上各有特点。在编译配置文件中,可以对这两种模式进行配置。

config.mk文件中,通过宏定义来控制AOT和JIT编译的开启与关闭。例如,开启JIT编译可以通过定义ART_ENABLE_JIT宏来实现:

# 开启JIT编译
ART_ENABLE_JIT := true
ifeq ($(ART_ENABLE_JIT),true)
    CXXFLAGS += -DART_ENABLE_JIT
    CFLAGS += -DART_ENABLE_JIT
endif

Android.bp文件中,对于一些与JIT编译相关的模块,可以进一步细化配置。比如libart_compiler模块中与JIT编译优化相关的部分:

cc_library {
    name: "libart_compiler",
    srcs: [...],
    cflags: [
        // JIT编译相关优化选项
        "-fno-strict-aliasing",
        "-march=native",
    ],
    // 其他配置...
}

对于AOT编译,在dex2oat工具的编译配置中会涉及相关选项。dex2oat负责将DEX字节码编译成OAT文件,在art/dex2oat/Android.bp文件中:

cc_binary {
    name: "dex2oat",
    srcs: [...],
    cflags: [
        // AOT编译相关选项
        "-DENABLE_AOT_COMPILE",
    ],
    // 其他配置...
}

通过这些配置,构建系统能够根据需求,准确地生成支持AOT或JIT编译模式的ART运行环境,以满足不同应用场景下的性能需求。

8.2 优化选项配置

编译优化选项对于提高ART的运行性能至关重要。在config.mkAndroid.bp文件中,可以对各种优化选项进行配置。

config.mk文件中,通常会设置整体的优化级别。如前文所述,根据不同的编译变体设置不同的优化级别:

ifeq ($(TARGET_BUILD_VARIANT),user)
    CXXFLAGS += -O3
    CFLAGS += -O3
else
    CXXFLAGS += -O2
    CFLAGS += -O2
endif

除了优化级别,还可以配置其他具体的优化选项。例如,启用函数内联优化:

CXXFLAGS += -finline-functions
CFLAGS += -finline-functions

Android.bp文件中,针对具体模块可以进行更细致的优化配置。以libart_runtime模块为例:

cc_library_shared {
    name: "libart_runtime",
    srcs: [...],
    cflags: [
        // 针对运行时模块的优化
        "-fno-tree-vectorize",
        "-fno-slp-vectorize",
    ],
    // 其他配置...
}

这些优化选项的配置会影响编译器对源代码的处理方式,通过合理设置优化选项,能够在代码大小和执行效率之间取得平衡,提升ART在设备上的运行性能。

8.3 调试选项配置

为了方便开发者调试ART,在编译配置文件中提供了丰富的调试选项配置。在config.mk文件中,可以设置生成调试信息的选项:

ifeq ($(TARGET_BUILD_VARIANT),userdebug)
    CXXFLAGS += -g
    CFLAGS += -g
    WITH_DEBUG_INFO := true
endif

上述代码在编译变体为userdebug时,通过-g标志生成调试信息,这些调试信息包含了源代码与目标代码之间的映射关系等内容,方便使用调试工具进行调试。

此外,还可以配置一些特定的调试功能。例如,开启内存调试功能,检测内存泄漏和非法内存访问:

ifeq ($(DEBUG_MEMORY),true)
    CXXFLAGS += -DDEBUG_MEMORY
    CFLAGS += -DDEBUG_MEMORY
    LDFLAGS += -lmcheck
endif

Android.bp文件中,对于一些调试相关的模块,可以进一步细化配置。比如libart_debug模块:

cc_library {
    name: "libart_debug",
    srcs: [...],
    cflags: [
        // 调试模块专用选项
        "-DDEBUG_LOG_ENABLED",
    ],
    // 其他配置...
}

通过这些调试选项配置,开发者在遇到问题时,可以编译带有调试信息和特定调试功能的ART版本,利用调试工具深入分析问题根源,提高问题排查和修复的效率。

九、依赖管理与模块构建顺序

9.1 模块依赖关系定义

在ART构建系统中,模块之间存在着复杂的依赖关系,这些依赖关系通过Android.bp文件进行定义。模块依赖关系确保了在编译过程中,各个模块能够按照正确的顺序进行构建,并且依赖的库和资源能够被正确引入。

libart_compiler模块为例,它依赖于libart核心库以及libcompiler_rt等库,在art/compiler/Android.bp文件中定义如下:

cc_library {
    name: "libart_compiler",
    srcs: [...],
    shared_libs: [
        "libart",
        "libcompiler_rt",
    ],
    static_libs: [
        "libbase",
    ],
    // 其他配置...
}

上述代码中,shared_libs属性指定了该模块依赖的共享库,static_libs属性指定了依赖的静态库。这意味着在编译libart_compiler模块之前,构建系统需要先确保libartlibcompiler_rtlibbase等库已经被正确编译。

对于一些Java模块,同样会定义依赖关系。例如,art/runtime/java/Android.bp中的Java库模块:

java_library {
    name: "libart_runtime_java",
    srcs: [...],
    deps: [
        "libcore_java",
    ],
    // 其他配置...
}

这里的deps属性用于指定该Java库模块依赖的其他模块,表明在编译此模块时,需要先编译好libcore_java模块。

9.2 构建系统的依赖解析

构建系统在读取Android.bp文件后,会对模块的依赖关系进行解析,构建出一个依赖图。依赖图以模块为节点,以依赖关系为边,清晰地展示了各个模块之间的依赖顺序。

构建系统采用拓扑排序算法对依赖图进行处理,以确定模块的编译顺序。拓扑排序会确保在编译某个模块时,其所有依赖的模块都已经被编译完成。

例如,假设有三个模块ABCA依赖于BCB依赖于C。构建系统生成的依赖图中,节点C没有入边(即没有其他模块依赖它),节点B有一条来自C的入边,节点A有两条分别来自BC的入边。通过拓扑排序,得到的编译顺序为CBA,这样就能保证在编译A时,BC都已准备好。

在实际的ART构建中,依赖关系更加复杂,涉及大量的C++模块和Java模块。构建系统通过高效的依赖解析和拓扑排序算法,能够准确地确定数千个模块的编译顺序,确保整个ART构建过程的顺利进行。

9.3 增量编译与依赖更新处理

增量编译是ART构建系统的重要特性,它能够在代码发生局部修改时,只重新编译受影响的模块,而不是重新编译整个ART,从而大大提高编译效率。

构建系统通过记录模块的依赖关系和文件修改时间等信息来实现增量编译。当开发者修改了某个源文件后,构建系统会检查该文件所属模块以及依赖该模块的其他模块。如果只有该模块及其直接或间接依赖的模块受到影响,那么只重新编译这些受影响的模块。

例如,若修改了libart_compiler模块中的一个源文件,构建系统会检测到libart_compiler模块发生变化,由于其他模块可能依赖libart_compiler,构建系统会进一步检查依赖它的模块,然后只重新编译libart_compiler及其受影响的依赖模块。

当模块的依赖关系发生更新时,构建系统也能够正确处理。比如,若在libart_compiler模块的Android.bp文件中新增了一个依赖库,构建系统会重新解析依赖关系,更新依赖图,并根据新的依赖关系确定哪些模块需要重新编译,确保编译结果的正确性和一致性。

十、与其他Android模块的集成编译

10.1 与系统库的集成

ART作为Android系统的运行时环境,需要与众多系统库进行集成。在编译配置文件中,通过指定依赖关系来实现与系统库的集成。

例如,ART依赖于libandroid_runtime库,它提供了Android运行时的一些基础功能。在art/Android.bp文件中,libart模块对libandroid_runtime的依赖定义如下:

cc_library_shared {
    name: "libart",
    srcs: [...],
    shared_libs: [
        "libandroid_runtime",
        // 其他依赖库...
    ],
    // 其他配置...
}

在编译过程中,构建系统会根据这个依赖关系,确保在编译libart之前,libandroid_runtime库已经被正确编译,并将其链接到libart中。

对于一些C标准库和数学库等系统基础库,同样会在编译配置中体现依赖关系。如libart可能依赖libc库和libm库:

cc_library_shared {
    name: "libart",
    srcs: [...],
    shared_libs: [
        "libc",
        "libm",
        // 其他依赖库...
    ],
    // 其他配置...
}

通过这种方式,ART能够与系统库紧密集成,使用系统库提供的功能,实现自身的运行和功能扩展。

10.2 与应用框架层的协作编译

应用框架层为Android应用提供了大量的API和服务,ART需要与应用框架层协作,确保应用能够正确运行。在编译过程中,ART会依赖应用框架层的一些模块和库。

例如,在处理Java类加载和方法调用时,ART需要与应用框架层中定义的类和接口进行交互。在art/runtime/class_linker.cc文件中,会涉及到对应用框架层类的加载逻辑:

bool ClassLinker::LoadFrameworkClass(const char* class_name, mirror::ClassLoader* class_loader) {
    // 从应用框架层加载类的逻辑
    // 可能会涉及到查找框架层的类定义文件等操作
    //...
}

在编译配置上,虽然不会直接在ART的Android.bp文件中体现对整个应用框架层的依赖,但在实际运行时,ART会依赖应用框架层编译生成的类文件和资源。

同时,应用框架层在开发过程中,也需要考虑与ART的兼容性。例如,当应用框架层新增或修改API时,需要确保ART能够正确处理这些变化,保证应用在运行时能够正确调用这些API。在编译应用框架层相关模块时,也会配置一些与ART交互相关的选项,如设置类加载路径等,以实现两者的协作。