Android 源代码中增加新apk
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 文件 然后将 hello-jni 导入到 eclipse 中,运行模拟器,即可显示 [local]1[/local] 下面再看一个例子 native-activity 1、 进入目录后执行 $NDK/ndk-build 2、 生成 libnative_activity.so 并存于当前目录的 lib/armeabi 目录下,另外由源代码生成的还有静态库,存放于obj/local/armeabi/ libandroid_native_app_glue.a 这里,由于 main.c 比较大,就不贴上了 Android.mk Java代码 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 Java代码 APP_PLATFORM := android-10<span style="white-space: normal;"> </span> 复制代码 复制代码 运行结果:颜色会逐渐变浅,然后再次从这个颜色变浅 [local]2[/local] 4、增加apk文件(有源代码) 如果将 android 程序的源代码加入到 build system 中呢 (1) 在 eclipse 开发环境中创建你的 android 工程,比如叫做 Success (2) 将工程拷贝到源代码的 package/apps 目录下 (3) 进入 Success 目录下,创建一个 Android.mk 文件,内容如下 Java代码 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 即可了