原文网址:http://hualang.iteye.com/blog/1141315
Android build system就是编译系统的意思
在我们需要向自己编译的源代码中增加模块的时候,需要一些规则,当然这个规则都是类似的。
Android.mk文件解析
让我们来看一个 Android.mk 文件的样子
- 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
- #include <stdio.h>
- int main(void)
- {
- printf("Hello world!\n");
- return 0;
- }
Android.mk
- LOCAL_PATH := $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_SRC_FILES :=hello.c
- LOCAL_MODULE_TAGS :=optional
- LOCAL_MODULE :=test
- include $(BUILD_EXECUTABLE)
首先退出到mydroid目录下,执行
- . 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后,执行
- ./test
- 显示:Hello world!
2、增加静态库(.a)
Android.mk文件
- 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
- 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
- #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 文件
- 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 文件
然后将 hello-jni 导入到 eclipse 中,运行模拟器,即可显示
下面再看一个例子 native-activity
1、 进入目录后执行 $NDK/ndk-build
2、 生成 libnative_activity.so 并存于当前目录的 lib/armeabi 目录下,另外由源代码生成的还有静态库,存放于obj/local/armeabi/ libandroid_native_app_glue.a
这里,由于 main.c 比较大,就不贴上了
Android.mk
- LOCAL_PATH := $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE := native-activity
- LOCAL_SRC_FILES := main.c
- LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv1_CM
- LOCAL_STATIC_LIBRARIES := android_native_app_glue
- include $(BUILD_SHARED_LIBRARY)
- $(call import-module,android/native_app_glue)
Application.mk
- APP_PLATFORM := android-10<span style="white-space: normal;"> </span>
运行结果:颜色会逐渐变浅,然后再次从这个颜色变浅
4、增加apk文件(有源代码)
如果将 android 程序的源代码加入到 build system 中呢
(1) 在 eclipse 开发环境中创建你的 android 工程,比如叫做 Success
(2) 将工程拷贝到源代码的 package/apps 目录下
(3) 进入 Success 目录下,创建一个 Android.mk 文件,内容如下
- LOCAL_PATH :=$(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE_TAGS :=optional
- LOCAL_SRC_FILES :=$(call all-java-files-under, src)
- LOCAL_PACKAGE_NAME :=(工程名字)
- LOCAL_CERTIFICATE :=platform
- include $(BUILD_PACKAGE)
(4) 退回到 android 源代码目录下,执行
#. build/envsetup.sh
#mmm packages/apps/Success
编译成功之后,会在 out/target/product/generic/system/app/Success.apk
(5) 如果要在真机上测试, system 的目录是在 out/target/product/crespo 目录下,编译的时候需要设置一些参数。为了测试,将 crespo 中的 system 记其他内核等文件放入到一个叫做 samsung 的文件夹中,再将Success.apk 放到 system/app 中
(6) 用 #zip –r update.zip . 命令将其打包为 zip ,也可以用 zip 直接压缩
(7) 用 #java –jar testsign.jar Samsung/update.zip update.zip 将 zip 包签名
(8) 打开手机的 usb 调试,连接到电脑上,在终端输入 #adb push update.zip /sdcard/update.zip ,将 zip包上传到设备的 sdcard 目录下
(9) 输入 #adb reboot bootloader 进入 bootloader 界面
(10) 输入 #fastboot flash recovery recovery.img 刷 recovery, 我刷的是 Recovery 3.0
(11) 进入 Recovery 选项,刷机,重启后就可以见到 Success.apk 程序了
注意:修改 AndroidManifest.xml ,在 manifest 标签中加入 android:sharedUserId=”media” ,当然这个 media只是个 id ,它的名字随便一般类似包名。我们知道,在不同的 apk 包中默认是不能相互访问数据的,但是如果在 AndroidManifest.xml 中设置 sharedUserId 并且相同的话,那么这两个包就可以相互访问数据。由于我写的只是个测试程序,所以没有加入这条
5、增加apk文件(无源代码)
(1) 这种方式最简单,就是将 ***.apk 文件拷贝到编译 android 源代码时候生成的out/target/product/crespo/system/app 中,执行 make snod 后就可以把 apk 添加到 system.img 中了,然后将system 目录及其他的几个文件打包成 zip 并签名后即可,刷机后可以看到这个内置的系统程序。
(2) 上一种方式在 make clean 之后,再次 make 以后才能执行上述的操作,比较麻烦
① 新建一个目录,在 packages/apps 下面,专门用来存放 apk 文件
#mkdir packages/apps/apks
#cd packages/apps/apks
在这个目录中新建一个 Android.mk 文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_POST_PROCESS_COMMAND := (shell cp –r $(LOCAL_PATH)/*.apk $(TARGET_OUT)/app/)
保存退出
② 把需要编译的 apk 拷贝到 apks 目录中,执行 make ,在 apks 中的 apk 就会被拷贝到out/target/product/generic/system/app 中
③ 执行 make snod 即可
这样,在执行 make clean 之后,再次 make ,只需要 make snod 即可了