在这篇博客中将探讨学习Android.mk文件中赋值操作、变量引用和函数的使用方法,静态库和可执行文件的示例。通过使用这些概念,可以更灵活地控制Android.mk文件中的变量和表达式,从而实现满足客户的功能。
学习参考:
Android.mk | Android NDK | Android Developers - Android 开发者.
1. 什么是Android.mk?
Android.mk 是一个用于描述Android项目构建过程的文件,它是基于Makefile语法的一种扩展。Android.mk 的作用与重要性在于:
- 可以定义项目中包含哪些源文件、资源文件、库文件等,以及它们之间的依赖关系。
- 可以指定项目的编译选项、链接选项、预处理器定义等,以及不同平台和架构的适配。
- 可以控制项目的输出类型,如可执行文件、静态库、动态库、APK等,以及它们的安装位置和权限。
- 可以调用一些内置的函数和变量,以实现一些复杂的逻辑和功能。
2. Android.mk 的基本组成
一个典型的Android.mk 文件由以下几个部分组成:
- 注释:以
#
开头的行是注释,不会被解析。 - 空白行:空白行会被忽略,不影响解析。
- 赋值操作:赋值操作用于给变量赋值,有多种赋值操作符可供选择。
- 变量引用:变量引用用于获取变量的值,有多种引用方法可供选择。
- 函数:函数是一种特殊的变量,它可以接受参数并返回结果,有多种内置函数和自定义函数可供使用。
- 条件语句:条件语句用于根据条件执行不同的操作,有多种条件判断符号可供选择。
- 控制语句:控制语句用于控制代码块的执行流程,有多种控制关键字可供选择。
- 打印操作:打印操作用于在终端输出信息,有多种打印函数可供使用。
- 模块定义:模块定义是Android.mk 文件中最重要的部分,它用于定义一个或多个构建模块,并指定它们的属性和行为。
这个Android.mkl系列将逐一介绍这些部分的具体内容和用法 请往后看。
3. 赋值操作
赋值操作是Android.mk文件中最常见的基础操作之一,它用于给变量赋予一个值,或者改变一个已有变量的值。在Makefile语法中,有四种赋值操作符可供选择,它们分别是:=
、+=
、?=
和=
。它们的含义和用法如下:
:=
:这个赋值操作符表示立即赋值,即在赋值语句被解析时就计算右边表达式的值,并将其赋给左边变量。例如:
# 使用:= 赋值操作符 file_name := source_file # 打印file_name的值 $(warning file_name is $(file_name)) # 输出 file_name is source_file # 改变file_name的值 file_name := header_file # 再次打印file_name的值 $(warning file_name is $(file_name)) # 输出 file_name is header_file
+=
:这个赋值操作符表示追加赋值,即在变量原有的值后面添加右边表达式的值,并将其赋给左边变量。例如:
# 使用:= 赋值操作符 file_extension := .txt # 打印file_extension的值 $(warning file_extension is $(file_extension)) # 输出 file_extension is .txt # 使用+= 追加赋值操作符 file_extension += .bak # 再次打印file_extension的值 $(warning file_extension is $(file_extension)) # 输出 file_extension is .txt.bak
?=
:这个赋值操作符表示条件赋值,即只有在变量没有被赋值过时才赋予等号后面的值。如果变量已经被赋值过,那么这里的赋值将不会生效。例如:
# 使用:= 赋值操作符 project_name := MyProject # 打印project_name的值 $(warning project_name is $(project_name)) # 输出 project_name is MyProject # 使用?= 条件赋值操作符 project_name ?= NewProject # 再次打印project_name的值 $(warning project_name is $(project_name)) # 输出 project_name is MyProject # 注意这里的结果没有改变,因为 project_name 已经被赋值过了
=
:这个赋值操作符表示延迟赋值,即在使用变量时才展开变量的值。这意味着如果变量的值在之后被改变了,那么之前使用这个变量的地方也会随之改变。例如:
# 使用= 延迟赋值操作符 output_dir = build # 打印output_dir的值 $(warning output_dir is $(output_dir)) # 输出 output_dir is build # 改变output_dir的值 output_dir = dist # 注意这里不是使用 := ,而是使用 = # 再次打印output_dir的值 $(warning output_dir is $(output_dir)) # 输出 output_dir is dist # 使用:= 赋值操作符 config := debug # 使用$(变量名) 引用变量的值 $(warning config is $(config)) # 输出 config is debug # 使用:= 赋值操作符 config := release # 使用${变量名} 引用变量的值 $(warning config is ${config}) # 输出 config is release
4. 变量引用
变量引用用于获取变量的值,有两种引用方法可供选择:
$(变量名)
:这种方法表示获取变量的值,并替换到引用位置。例如:
# 使用:= 赋值操作符 VAR := hello1 # 使用$(变量名) 引用变量的值 $(warning VAR is $(VAR)) # 输出 VAR is hello1
${变量名}
:这种方法与上一种方法等价,只是使用了不同的括号。例如:
# 使用:= 赋值操作符 VAR := hello2 # 使用${变量名} 引用变量的值 $(warning VAR is ${VAR}) # 输出 VAR is hello2
变量引用在Android.mk 文件中有很多实际应用,例如:
- 定义库文件列表:可以使用变量来存储库文件列表,然后在模块定义中引用该变量。例如:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 定义源文件列表 LOCAL_SRC_FILES_APP := \ src/main.cpp \ src/foo.cpp \ src/bar.cpp # 定义库文件列表 LOCAL_SHARED_LIBRARIES := libmylib # 构建libmylib动态库 include $(CLEAR_VARS) LOCAL_MODULE := libmylib LOCAL_SRC_FILES := libsrc/libmylib.cpp include $(BUILD_SHARED_LIBRARY)
对应的源码:
ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example01/libsrc$ cat libmylib.cpp #include <iostream> void libFunction() { std::cout << "This is from libmylib." << std::endl; } ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example01/libsrc$ cat libmylib.h #pragma once void libFunction();
- 定义源文件列表:可以使用变量来存储源文件列表,然后在模块定义中引用该变量。例如:
# 构建myapp应用程序 include $(CLEAR_VARS) LOCAL_MODULE := myapp LOCAL_SRC_FILES := $(LOCAL_SRC_FILES_APP) LOCAL_SHARED_LIBRARIES := libmylib LOCAL_LDLIBS := -llog LOCAL_CFLAGS := -Wall -Werror include $(BUILD_EXECUTABLE)
对应的源码:
ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example01/src$ cat bar.cpp #include "bar.h" #include <iostream> #include "../libsrc/libmylib.h" // 添加这一行 void barFunction() { libFunction(); std::cout << "Inside barFunction()" << std::endl; // TODO: Implement barFunction } ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example01/src$ cat bar.h #ifndef BAR_H #define BAR_H void barFunction(); #endif // BAR_H ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example01/src$ cat foo.cpp #include "foo.h" #include <iostream> void fooFunction() { std::cout << "Inside fooFunction()" << std::endl; // TODO: Implement fooFunction } ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example01/src$ cat foo.h #ifndef FOO_H #define FOO_H void fooFunction(); #endif // FOO_H ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example01/src$ cat main.cpp #include "foo.h" #include "bar.h" #include <iostream> int main() { std::cout << "Starting main()" << std::endl; fooFunction(); barFunction(); std::cout << "Ending main()" << std::endl; return 0; }
5. 测试和代码
完整的目录结构:
ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example01$ tree . ├── Android.mk ├── libsrc │ ├── libmylib.cpp │ └── libmylib.h └── src ├── bar.cpp ├── bar.h ├── foo.cpp ├── foo.h └── main.cpp 2 directories, 8 files
Android.mk
完整代码:
$(warning ====== 测试开始 ======) # 使用:= 赋值操作符 file_name := source_file # 打印file_name的值 $(warning file_name is $(file_name)) # 输出 file_name is source_file # 改变file_name的值 file_name := header_file # 再次打印file_name的值 $(warning file_name is $(file_name)) # 输出 file_name is header_file # 使用:= 赋值操作符 file_extension := .txt # 打印file_extension的值 $(warning file_extension is $(file_extension)) # 输出 file_extension is .txt # 使用+= 追加赋值操作符 file_extension += .bak # 再次打印file_extension的值 $(warning file_extension is $(file_extension)) # 输出 file_extension is .txt.bak # 使用:= 赋值操作符 project_name := MyProject # 打印project_name的值 $(warning project_name is $(project_name)) # 输出 project_name is MyProject # 使用?= 条件赋值操作符 project_name ?= NewProject # 再次打印project_name的值 $(warning project_name is $(project_name)) # 输出 project_name is MyProject # 注意这里的结果没有改变,因为 project_name 已经被赋值过了 # 使用= 延迟赋值操作符 output_dir = build # 打印output_dir的值 $(warning output_dir is $(output_dir)) # 输出 output_dir is build # 改变output_dir的值 output_dir = dist # 注意这里不是使用 := ,而是使用 = # 再次打印output_dir的值 $(warning output_dir is $(output_dir)) # 输出 output_dir is dist # 使用:= 赋值操作符 config := debug # 使用$(变量名) 引用变量的值 $(warning config is $(config)) # 输出 config is debug # 使用:= 赋值操作符 config := release # 使用${变量名} 引用变量的值 $(warning config is ${config}) # 输出 config is release LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 定义源文件列表 LOCAL_SRC_FILES := \ src/main.cpp \ src/foo.cpp \ src/bar.cpp # 定义一个可执行模块 LOCAL_MODULE := myapp # 明确指定这是一个可执行文件 LOCAL_MODULE_CLASS := EXECUTABLES # 编译选项 LOCAL_CFLAGS := -Wall -Werror # 链接选项 LOCAL_LDLIBS := -llog # 引用源文件列表变量 LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) include $(BUILD_EXECUTABLE) $(warning ====== 测试结束 ======) all: @echo "测试example01结束!"
执行mmm vendor/customize/demo/example01
打印
[ 79% 221/278] including vendor/customize/demo/example01/Android.mk ... vendor/customize/demo/example01/Android.mk:1: warning: ====== 测试开始 ====== vendor/customize/demo/example01/Android.mk:6: warning: file_name is source_file vendor/customize/demo/example01/Android.mk:10: warning: file_name is header_file vendor/customize/demo/example01/Android.mk:15: warning: file_extension is .txt vendor/customize/demo/example01/Android.mk:19: warning: file_extension is .txt .bak vendor/customize/demo/example01/Android.mk:24: warning: project_name is MyProject vendor/customize/demo/example01/Android.mk:28: warning: project_name is MyProject vendor/customize/demo/example01/Android.mk:33: warning: output_dir is build vendor/customize/demo/example01/Android.mk:37: warning: output_dir is dist vendor/customize/demo/example01/Android.mk:42: warning: config is debug vendor/customize/demo/example01/Android.mk:47: warning: config is release vendor/customize/demo/example01/Android.mk:52: warning: VAR is hello1 vendor/customize/demo/example01/Android.mk:57: warning: VAR is hello2 vendor/customize/demo/example01/Android.mk:87: warning: ====== 测试结束 ======
生成对应的libmylib.so
和myapp
文件
ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk$ cd out/target/product/rk3568_s$ find -name "libmylib*" ./obj_arm/SHARED_LIBRARIES/libmylib_intermediates ./obj_arm/SHARED_LIBRARIES/libmylib_intermediates/LINKED/libmylib.so ./obj_arm/SHARED_LIBRARIES/libmylib_intermediates/libsrc/libmylib.d ./obj_arm/SHARED_LIBRARIES/libmylib_intermediates/libsrc/libmylib.o ./obj_arm/SHARED_LIBRARIES/libmylib_intermediates/libmylib.so.strip.d ./obj_arm/SHARED_LIBRARIES/libmylib_intermediates/libmylib.so ./obj_arm/SHARED_LIBRARIES/libmylib_intermediates/libmylib.so.toc ./symbols/system/lib/libmylib.so ./symbols/system/lib64/libmylib.so ./system/lib/libmylib.so ./system/lib64/libmylib.so ./obj/SHARED_LIBRARIES/libmylib_intermediates ./obj/SHARED_LIBRARIES/libmylib_intermediates/LINKED/libmylib.so ./obj/SHARED_LIBRARIES/libmylib_intermediates/libsrc/libmylib.d ./obj/SHARED_LIBRARIES/libmylib_intermediates/libsrc/libmylib.o ./obj/SHARED_LIBRARIES/libmylib_intermediates/libmylib.so.strip.d ./obj/SHARED_LIBRARIES/libmylib_intermediates/libmylib.so ./obj/SHARED_LIBRARIES/libmylib_intermediates/libmylib.so.toc ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk$ cd out/target/product/rk3568_s$ find -name "myapp" ./symbols/system/bin/myapp ./system/bin/myapp ./obj/EXECUTABLES/myapp_intermediates/myapp ./obj/EXECUTABLES/myapp_intermediates/LINKED/myapp
推送文件到开发板Android系统中,准备测试
C:\Users\Administrator>adb root adbd is already running as root C:\Users\Administrator>adb remount remount succeeded C:\Users\Administrator>adb push "Z:\rk_android12.0_sdk\out\target\product\rk3568_s\system\bin\myapp" /system/bin/ 615 KB/s (11720 bytes in 0.018s) C:\Users\Administrator>adb push "Z:\rk_android12.0_sdk\out\target\product\rk3568_s\system\lib\libmylib.so" /system/lib/ 703 KB/s (5080 bytes in 0.007s) C:\Users\Administrator>adb push "Z:\rk_android12.0_sdk\out\target\product\rk3568_s\system\lib64\libmylib.so" /system/lib 64/ 1071 KB/s (11376 bytes in 0.010s)
执行myapp
可执行程序
总结
本文介绍了Android.mk文件中赋值操作、变量引用和函数的使用方法,静态库和可执行文件的示例。通过使用这些概念,可以更灵活地控制Android.mk文件中的变量和表达式,从而实现满足客户的功能。Android.mk文件中还有很多其他的变量和函数,有时间会继续测试补充。