Android build system就是编译系统的意思
在我们需要向自己编译的源代码中增加模块的时候,需要一些规则,当然这个规则都是类似的。
Android.mk文件解析 让我们来看一个 Android.mk 文件的样子
Java代码
- LOCAL_PATH := $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE :=Hello
- LOCAL_SRC_FILES := hello.c
- include $(BUILD_SHARED_LIBRARY) 复制代码
复制代码 ① LOCAL_PATH :=$(call my-dir)
固定写法, LOCAL_PATH 表示此时位于工程目录的根目录中, (call my-dir) 的功能由编译器提供,被用来返回当前目录的地址(包含 Android.mk 本身)
② include $(CLEAR_VARS)
固定写法, CLEAR_VARS 这个变量由编译系统提供,并且要执行一个 GNU makefile 文件,这个功能会清理掉所有以 LOCAL_ 开头的内容(比如 LOCAL_MODULE 、 LOCAL_SRC_FILES 等),除了 LOCAL_PATH 。这句话也是必须的,因为如果所有变量都是全局变量的话,所有的可控的编译文件都需要在一个单独的 GNU 中被解析并执行
③ LOCAL_MODULE :=Hello
LOCAL_MODLE 变量必须被定义,用来区分 Android.mk 中的每一个模块。文件名必须是唯一的,不能有空格。注意,编译器会为你自动加上一些前缀和后缀,来保证文件是一致的。比如:这里表明一个动态链接库模块被命名为“ Hello ”,但是最后会生成“ libHello.so ”文件。但是在 java 中装载这个库的时候还要使用“ Hello ”名称。
④ LOCAL_SRC_FILES :=hello.c
LOCAL_SRC_FILES 变量必须包含一个 C 和 C++ 源文件的列表,这些会被编译并聚合到一个模块中
注意:这里并不需要列头文件和被包含的文件,因为编译系统会自动为你计算相关的属性,源代码的列表会直接传递给编译器
⑤ include $(BUILD_SHARED_LIBRARY)
BUILD_SHARED_LIBRARY 这个变量由系统提供,并且指定给 GNU makefile 的脚本,它可以收集所有你定义的”include $(CLEAR_VARS)” 中以 LOCAL_ 开头的变量,并且决定哪些要编译,哪些应该做的更加准确。我们同样可以使用 BUILD_STATIC_LIBRARY 来生成一个静态库,如果使用 BUILD_STATIC_LIBRARY 编译系统便会生成一个以“ lib$(LOCAL_MODULE).a ”为文件名的文件来提供动态库的调用
⑥ TARGET_ARCH
TARGET_ARCH 是指架构中 CPU 的名字已经被 Android 开源代码明确指出了,这里的 arm 包含了任何 ARM-独立结构的架构,以及每个独立的 CPU 版本
⑦ TARGET_PLATFORM
Android 平台的名字在 Android.mk 文件中被解析,比如 ”android-2.3”
⑧ TARGET_ROOT_OUT :表示根文件系统
用法: CAL_MODULE_PATH:=$(TARGET_ROOT_OUT)
⑨ TARGET_OUT: 表示 system 文件系统
⑩ TARGET_OUT_DATA: 表示 data 文件系统
? TARGET_ABI
TARGET_ABI 平台目标板和 abi 的链接,这里要定义 $(TARGET_PLATFORM)-$(TARGET_ARCH_ABI) ,它们都非常有用,特变是当你想测试一个具体的系统镜像在几个真实设备的时候
下面是 GNU 编译出来的宏,并且它们都必须使用“ $(call <function>) ”才能返回文字化的信息。
all-subdir-makefiles :返回一个 Android.mk 文件所在位置的列表,以及当前 my-dir 的路径。
比如: include $(call all-subdir-makefiles)
this-makefile :返回当前 makefile 的路径(就是哪个功能被调用了)
parent-makefile :返回 makefile 的包含树,也就是包含 makefile 的当前文件
Application.mk 要讲 C\C++ 编译为 so 文件,光有 Android.mk 文件是不行的,还需要 Application.mk 文件。
Application.mk 文件存放的位置是 NDK 工程的根目录, Application.mk 文件就是用来描述应用程序中所需要的原生的模块(也就是静态库和动态库),每一个 Application.mk 文件都必须放在应用目录下的子目录,例如$NDK/apps/HelloNdk/Application.mk ,作为 GNU makefile 的一部分, Application.mk 文件必须要定义以下部分
1、 APP_MODULES
APP_MODULES 变量是强制性的,并且会列出所有你所需要的模块(通过 Android.mk )
2、 APP_PROJECT_PATH
APP_PROJECT_PATH 变量也是强制性的,并且会给出应用程序工程的根目录一个绝对路径。这是用来复制或者安装一个没有任何版本限制的 JNI 库,从而给 APK 生成工具一个详细的路径。
例如: \HelloNDK\Application.mk
APP_PROJECT_PATH := $(call my-dir)/project
APP_MODULES := HelloNdk
这里定义了工程路径为 $(call my-dir)/project ,而要编译的模块则是 HelloNdk ,这样编译系统才会找到我们要编译的库和原文件
3、 APP_CFLAGS 则是当要编译模块中有任何 C 文件的时候, C 编译器的信号就会被发出
4、 APP_OPTIM
这个变量是可选的,可以定义为发布版或者测试版
------------------------------------------------------------------------------------------------------------------------
在Android.mk中:
include $(BUILD_EXECUTABLE)表示生成二进制可执行文件
include $(BUILD_STATIC_LIBRARY)表示生成静态库.a文件,名字叫做lib<工程名>.a
include $(BUILD_SHARED_LIBRARY)表示生成动态库.so文件,名字叫做lib<工程名>.so
另外需要注意的是,生成的文件需要放在手机的data/local目录下,才可以有执行的权限(未root),如果root了,则会有些目录是可以执行,但是某些目录依然不能执行,当然可以用umount命令解决。SD卡是没有执行权限的,所以当你生成的比如二进制可执行文件放到sdcard中时,是无法运行的。
可以这样测试一下哪些是有执行权限,哪些是没有的:
*将手机插入电脑,并打开USB调试
*在终端输入adb shell进入
*su(root了的手机)
*mount:可以看到一大堆的列表,如果对应的目录的信息中有noexec,说明这个目录就没有执行权限,剩下的都可以执行二进制等文件。
Android.mk文件的具体语法参见我的博客:http://hualang.iteye.com/blog/1140414
向Android源代码中增加模块主要有如下几种:
1、增加可执行文件 增加可执行文件,这些可执行文件一般都是由C/C++生成,下面简单的介绍一些如何向源代码中生成可执行文件
假设我的源代码在~/mydroid目录下
在external/loulijun/test目录下,创建两个文件,一个C,一个Android.mk
hello.c
C代码
- #include <stdio.h>
- int main(void)
- {
- printf("Hello world!\n");
- return 0;
- } 复制代码
复制代码 Android.mk
Java代码
- LOCAL_PATH := $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_SRC_FILES :=hello.c
- LOCAL_MODULE_TAGS :=optional
- LOCAL_MODULE :=test
- include $(BUILD_EXECUTABLE) 复制代码
复制代码 首先退出到mydroid目录下,执行
Java代码
- . build/envsetup.sh
- 或者
- source build/envsetup.sh 复制代码
复制代码 进行环境变量的配置
然后进入到test目录下,执行“mm”(mm表示编译当前项目),如果想重新执行,可以"mm -B"
这样,会在out/target/product/generic/obj/EXECUTABLES/test_intermediates/LINKED/目录下生成可执行文件test
然后将test文件用adb push test /data/local 到data/local目录下。
下面开始执行,你可以在手机中用terminal emulator来执行,也可以以adb shell后,执行
Java代码
- ./test
- 显示:Hello world! 复制代码
复制代码 2、增加静态库(.a) Android.mk文件
Java代码
- LOCAL_PATH :=$(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_SRC_FILES := \
- hello.c
- LOCAL_MODULE :=test
- include $(BUILD_STATIC_LIBRARY) 复制代码
复制代码 编译:
mydroid#. build/envsetup.sh
test#mm
生成的结果在out/target/product/generic/obj/STATIC_LIBRARY
目标文件夹{XXX}_static_intermediates下,XXX为你定义的模块名称test
假如这个静态库是由hello.c生成的,但是生成的静态库是不能直接使用的,而是由动态库调用它
3、增加动态库(.so) 编译动态库其实可以用NDK的,那样生成非常方便,但是有时候还是需要掌握其他的方法的
Android.mk
Java代码
- LOCAL_PATH :=$(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_SRC_FILES := \
- hello.c
- LOCAL_MODULE :=test
- include $(BUILD_SHARED_LIBRARY) 复制代码
复制代码 编译的放法都差不多,只不过Android.mk不同的是最后一句,如果比较一下就会发现那句话决定了生成的是什么
不过要想生成动态库,绝非是这么简单的,有时候只需要Android.mk和源文件即可,但是有时候还需要Application.mk文件。Application.mk文件的具体语法很快会在博客中更新
下面是使用 NDK 来生成 .so 文件的,环境是在 ubuntu11.04 下面
1、 下载 Android-NDK-r6 ,将其解压到 ~/android/android-ndk-r6 目录下
2、 配置 .bash_profile 文件,加入
NDK=~/android/android-ndk-r6
export NDK
3、 cd $NDK 后,进入 ndk 的目录,我以它自带的项目为例,进入 samples/hello-jni
在终端输入 $NDK/ndk-build
系统会自动编译这个文件,并将生成的 libhello-jni.so 文件存放在当前目录的 libs/armeabi 目录下
4、 我们可以将这个生成的 libhello-jni.so 放在 Android 源代码的适当的位置即可使用了
下面是相应的文件
hello-jni.c
Java代码
- #include <string.h>
- #include <jni.h>
- jstring
- Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
- jobject thiz )
- {
- return (*env)->NewStringUTF(env, "Hello from JNI !");
- } 复制代码
复制代码 Android.mk 文件
Java代码
- LOCAL_PATH := $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE := hello-jni
- LOCAL_SRC_FILES := hello-jni.c
- include $(BUILD_SHARED_LIBRARY) 复制代码
复制代码 由于这里只是使用了一个简单的 C 文件,所以没用用到 Application.mk 文件
然后将