四、安卓源码分析之Android.mk

一、引言

在Android项目中的每个模块下,几乎都能见到一个Android.mk的文件,而我们通过查看项目编译的log也可知,每个模块都是通过mk文件编译来供系统使用,所以mk文件对于其非常重要,所以今天我们通过一个安卓项目中实际使用的Android.mk,来详细的分析一下。

二、Android.mk的定义及作用

Android.mk的作用

Android.mk是Android提供的一种makefile文件,属于GUN makefile的一部分,会被编译系统解析一次或多次。
可以将源文件分组为模块。指定用来引用的头文件目录、需要编译的*.c/.cpp文件、jni源文件、指定编译生成.so共享库文件或者*.a静态库文件,可以多个模块中使用同一个源文件。
Android.mk文件语法允许我们将Source打包成一个"modules". modules可以是:
静态库
动态库

只有动态库可以被 install/copy到应用程序包(APK). 静态库则可以被链接入动态库。
可以在一个Android.mk中定义一个或多个modules. 也可以将同一份source 加进多个modules.
Build System帮我们处理了很多细节而不需要我们再关心。例如:你不需要在Android.mk中列出头文件和外部依赖文件。
NDK Build System自动帮我们提供这些信息。这也意味着,当用户升级NDK后,你将可以受益于新的toolchain/platform而不必再去修改Android.mk.

可以生成的目标文件类型

  • APK程序,一般的Android应用程序,系统级别的直接push即可;
  • JAVA库,java类库,编译打包生成jar文件;
  • C\C++应用程序,可执行的C\C++应用程序;
  • C\C++静态库,编译生成C\C++静态库,并打包成.a文件;
  • C\C++共享库, 编译生成共享库(动态链接库),并打包成.so, 有且只有共享库才能被安装/复制到您的应用软件(APK)包中;

最后根据自己的需求使用对应的目标文件(常用的是so和jar);

三、Android.mk语法

在编译源码时,无论是直接使用make全编,还是使用mm\mmm命令全编译单个模块,我们所遵循的原则和GNU Makefile是一模一样的,最终都是使用相同的make命令,所有GNU Makefile规则在Android源码里照样适用,只不过Android封装了宠大且复杂的编译系统,使得我们可以清晰简便的做修改。
下面我们以Android源码里一个系统APP的Android.mk(为讲解做了部分添加)文件作为例子:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := eng
LOCAL_MODULE := libphone_jni

#实际不存在,此处用作讲解
LOCAL_PACKAGE_NAME := test	
LOCAL_MODULE := libvideocontrol

LOCAL_SRC_FILES := $(call all-java-files-under, src) \
  						PhoneJNI.cpp
  
LOCAL_C_INCLUDES := \
     $(JNI_H_INCLUDE)

#libcutils
#LOCAL_STATIC_LIBRARIES := libnvram
LOCAL_SHARED_LIBRARIES := \
        liblog  \
        libgs_phone \
        libgsmedia \
        
  #实际不存在,此处用作讲解      
LOCAL_PROGUARD_FLAG_FILES := proguard.flags

LOCAL_LDLIBS := -llog -ljnigraphics -lz -landroid -lpthread

LOCAL_PREBUILT_LIBS :=  lib/$(TARGET_ARCH_ABI)/libopenh264.so

LOCAL_STATIC_LIBRARIES := libcommon libencoder libprocessing libdecoder
 # to add this library to the prelink map and set this to true.
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)

解析如下:
  • LOCAL_PATH:每一个Androi.mk文件都必须以定义LOCAL_PATH变量开头,my-dir是由build系统定义的函数,作用是返回当前Android.mk在源码中目录
    这个宏返回最后包含的 makefile 的路径,通常是当前 Android.mk 的目录,因此,include其他文件后就不应调用 my-dir

  • CLEAR_VARS:CLEARVARS是由build系统所定义的一个变量,它的值是build/core/clear_vars.mk,作用是清除很多LOCAL开头的变量,但是不清理LOCAL_PATH.
    所以你可以当include $(CLEAR_VARS)作为每个模块编译的开始。

  • LOCAL_MODULE_TAGS:LOCAL_MODULE_TAGS用于定义当前模块在什么编译模式中被编译,它的值有eng, user, tests, optional。
    user:指该模块只在user版本下才编译
    eng: 指该模块只在eng版本下才编译
    tests::指该模块只在tests版本下才编译
    optional:指该模块在所有版本下都编译

  • LOCAL_PACKAGE_NAME/LOCAL_MODULE:
    LOCAL_PACKAGE_NAME变量指明了编译出apk的名字,只有当前模块是一个应用(APP)才使用LOCAL_PACKAGE_NAME; 其余情况,无论so或jar包,全部都使用LOCAL_MODULE变量

  • LOCAL_SRC_FILES:LOCAL_SRC_FILES变量指明编译使用的源码文件。
    all-java-files-under是由build系统定义的函数,作用是列出指定目录下所有的java文件。

  • LOCAL_C_INCLUDES:一个可选的path列表。相对于NDK ROOT 目录。编译时,将会把这些目录附上。

  • LOCAL_SHARED_LIBRARIES: 要链接到本模块的动态库。

  • LOCAL_PROGUARD_FLAG_FILES:LOCAL_PROGUARD_FLAG_FILES指定混淆文件,上例中表明当前目录下proguard.flags作为混淆文件。

  • LOCAL_LDLIBS: 可以用它来添加系统库

  • LOCAL_PREBUILT_LIBS:用来加载动态库路径

  • LOCAL_STATIC_LIBRARIES:用来加载静态库

  • LOCAL_PRELINK_MODULE: Prelink技术
    Prelink技术:利用事先链接代替运行时链接的方法来加速共享库的加载,它不仅可以加快起动速度,还可以减少部分内存开销,是各种Linux架构上用于减少程序加载时间、缩短系统启动时间和加快应用程序启动的很受欢迎的一个工具

  • BUILD_JAVA_LIBRARY也是由build系统定义的一个变量,值是build/core/java_library.mk, 表示编译出一个jar包。里面是DEX格式的文件,一般来讲, 类似include $(BUILD_JAVA_LIBRARY)可以作为一个模块编译的结束
    和BUILD_JAVA_LIBRARY变量类似的还有好几个,这里列出其中常见的一部分:
    BUILD_PACKAGE:build/core/package.mk, 表示编译出一个apk文件
    BUILD_STATIC_JAVA_LIBRARY:build/core/static_java_library.mk; 也编译一个jar包,但是里面每个java文件所对应的class文件都存在
    BUILD_STATIC_LIBRARY:编译为静态库
    BUILD_SHARED_LIBRARY:编译为动态库
    BUILD_EXECUTABLE:build/core/executable.mk; 编译一个可执行的bin程序
    BUILD_PREBUILT:build/core/prebuilt.mk;用于集成第三方的jar包或者so库等

调试手段

我们调试代码的时候,最常用的手段就是打log, 在不明白的地方,或者不知道走了哪个if分支,或者想看看变量的值是什么,都可以打log看。
Android build系统里也可以打Log,使用函数warning / info / error。
需要注意的是,error函数会让编译直接停下来,所以一般用warning和info
例子:
$(warning hello world):打印普通字符串(hello world):
$(warning $(LOCAL_PACKAGE_NAME)):打印变量的值:
$(warning this apk is $( LOCAL_PACKAGE_NAME)):变量和字符串组合打印

常用目标信息变量

编译系统会根据 APP_ABI 变量所指定的每个 ABI 解析 Android.mk 一次,该变量通常在 Application.mk 文件中定义。如果 APP_ABI 为 all,则编译系统会根据 NDK 支持的每个 ABI 解析 Android.mk 一次。本部分介绍了编译系统每次解析 Android.mk 时定义的变量。

  • TARGET_ARCH:编译系统解析此 Android.mk 文件时面向的 CPU 系列。此变量是 arm、arm64、x86 或 x86_64 之一
  • TARGET_PLATFORM:编译系统解析此 Android.mk 文件时面向的 Android API 级别编号。例如,Android 5.1 系统映像对应于 Android API 级别 22:android-22。如需平台名称和对应 Android 系统映像的完整列表,请参阅 Android NDK 原生 API
  • TARGET_ARCH_ABI:编译系统解析此 Android.mk 文件时面向的 ABI。表 1 显示用于每个受支持 CPU 和架构的 ABI 设置
CPU 和架构 ABI设置
ARMv7 armeabi-v7a
ARMv8 AArch64 arm64-v8a
i686 x86
x86-64 x86_64

以下示例演示了如何检查 ARMv8 AArch64 是否为目标 CPU 与 ABI 的组合:

ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
      # ... do something ...
    endif
  • TARGET_ABI
    目标 Android API 级别与 ABI 的连接,特别适用于要针对实际设备测试特定目标系统映像的情况。例如,要检查搭载 Android API 级别 22 的 64 位 ARM 设备。
发布了7 篇原创文章 · 获赞 5 · 访问量 2545

猜你喜欢

转载自blog.csdn.net/weixin_38019025/article/details/103966217