Android编译环境

(1) - 编译Native C的helloworld模块

Android 编译环境 本身比较复杂,且不像普通的编译环境:只有顶层目录下才有 Makefile 文件,而其他的每个 component 都使用统一标准的 Android.mk . Android.mk 文件本身是比较简单的,不过它并不是我们熟悉的 Makefile ,而是经过了 Android 自身编译系统的很多处理,因此要真正理清楚其中的联系还比较复杂,不过这种方式的好处在于,编写一个新的 Android.mk 来给 Android 增加一个新的 Component 会比较简单。

编译 Java 程序可以直接采用 Eclipse 的集成环境来完成,这里就不重复了。我们主要针对 C/C++ 来说明,下面通过一个小例子来说明,如何在 Android 中增加一个 C 程序的 Hello World

1. $(YOUR_ANDROID)/ development 目录下创建 hello 目录,其中 $(YOUR_ANDROID) Android 源代码所在的目录。
- # mkdir $(YOUR_ANDROID)/development/hello

2. $(YOUR_ANDROID)/development /hello/ 目录编写 hello.c 文件, hello.c 的内容当然就是经典的 HelloWorld 程序:

#include <stdio.h>

int main()
{
printf("Hello World!/n");

return 0;
}

3. $(YOUR_ANDROID)/development /hello/ 目录编写 Android.mk 文件。这是 Android Makefile 的标准命名,不要更改。 Android.mk 文件的格式和内容可以参考其他已有的 Android.mk 文件的写法,针对 helloworld 程序的 Android.mk 文件内容如下:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES:= /

hello.c

LOCAL_MODULE := helloworld

include $(BUILD_EXECUTABLE)

注意上面 LOCAL_SRC_FILES 用来指定源文件;, LOCAL_MODULE 指定要编译的模块的名字,下一步骤编译时就要用到; include $(BUILD_EXECUTABLE) 表示要编译成一个可执行文件,如果想编译成动态库则可用 BUILD_SHARED_LIBRARY ,这些可以在 $(YOUR_ANDROID)/build/core/config.mk 查到。

4. 回到 Android 源代码顶层目录进行编译:

# cd $(YOUR_ANDROID) && make helloworld

注意 make helloworld 中的目标名 helloworld 就是上面 Android.mk 文件中由 LOCAL_MODULE 指定的模块名。编译结果如下:

target thumb C: helloworld <= development/hello/hello.c

target Executable: helloworld (out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/LINKED/helloworld)

target Non-prelinked: helloworld (out/target/product/generic/symbols/system/bin/helloworld)

target Strip: helloworld (out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/helloworld)

Install: out/target/product/generic/system/bin/helloworld

5 .如上面的编译结果所示,编译后的可执行文件存放在 out/target/product/generic/system/bin/helloworld ,通过 ”adb push” 将它传送到模拟器上,再通过 ”adb shell” 登录到模拟器终端,就可以执行了

Android编译环境(2) - 手工编译C模块

我们来试试如何直接运用 gcc 命令行来编译,从而了解 Android 编译 环境的细节。

Android 编译 环境提供了 ”showcommands” 选项来显示编译命令行, 我们可以通过打开这个选项来查看一些编译时的细节。当然,在这之前要把上一篇中的 helloworld 模块 clean:

# make clean-helloworld

上面的 “make clean-$(LOCAL_MODULE)” Android 编译 环境提供的 make clean 的方式。

接下来使用 showcommands 选项 重新编译 helloworld:

# make helloworld showcommands

build/core/product_config.mk:229: WARNING: adding test OTA key

target thumb C: helloworld <= development/hello/hello.c

prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/arm-eabi-gcc -I system/core/include -I hardware/libhardware/include -I hardware/ril/include -I dalvik/libnativehelper/include -I frameworks/base/include -I external/skia/include -I out/target/product/generic/obj/include -I bionic/libc/arch-arm/include -I bionic/libc/include -I bionic/libstdc++/include -I bionic/libc/kernel/common -I bionic/libc/kernel/arch-arm -I bionic/libm/include -I bionic/libm/include/arch/arm -I bionic/libthread_db/include -I development/hello -I out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates -c -fno-exceptions -Wno-multichar -march=armv5te -mtune=xscale -msoft-float -fpic -mthumb-interwork -ffunction-sections -funwind-tables -fstack-protector -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ -include system/core/include/arch/linux-arm/AndroidConfig.h -DANDROID -fmessage-length=0 -W -Wall -Wno-unused -DSK_RELEASE -DNDEBUG -O2 -g -Wstrict-aliasing=2 -finline-functions -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers -DNDEBUG -UDEBUG -mthumb -Os -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 -MD -o out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/hello.o development/hello/hello.c

target Executable: helloworld (out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/LINKED/helloworld)

prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/arm-eabi-g++ -nostdlib -Bdynamic -Wl,-T,build/core/armelf.x -Wl,-dynamic-linker,/system/bin/linker -Wl,--gc-sections -Wl,-z,nocopyreloc -o out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/LINKED/helloworld -Lout/target/product/generic/obj/lib -Wl,-rpath-link=out/target/product/generic/obj/lib -lc -lstdc++ -lm out/target/product/generic/obj/lib/crtbegin_dynamic.o out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/hello.o -Wl,--no-undefined prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/interwork/libgcc.a out/target/product/generic/obj/lib/crtend_android.o

target Non-prelinked: helloworld (out/target/product/generic/symbols/system/bin/helloworld)

out/host/linux-x86/bin/acp -fpt out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/LINKED/helloworld out/target/product/generic/symbols/system/bin/helloworld

target Strip: helloworld (out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/helloworld)

out/host/linux-x86/bin/soslim --strip --shady --quiet out/target/product/generic/symbols/system/bin/helloworld --outfile out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/helloworld

Install: out/target/product/generic/system/bin/helloworld

out/host/linux-x86/bin/acp -fpt out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/helloworld out/target/product/generic/system/bin/helloworld

从上面的命令行可以看到, Android 编译 环境所用的交叉编译工具链是 prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/arm-eabi-gcc -I -L 参数指定了所用的 C 库头文件和动态库文件路径分别是 bionic/libc/include out/target/product/generic/obj/lib ,其他还包括很多编译选项以及 -D 所定义的预编译宏。

我们可以利用上面的编译命令,稍加简化来手工编译 helloworld 程序。先手工删除上次编译得到的 helloworld 程序:

# rm out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/hello.o

# rm out/target/product/generic/system/bin/helloworld

再用 gcc 编译,生成目标文件:

# prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/arm-eabi-gcc -I bionic/libc/arch-arm/include -I bionic/libc/include -I bionic/libc/kernel/common -I bionic/libc/kernel/arch-arm -c -fno-exceptions -Wno-multichar -march=armv5te -mtune=xscale -msoft-float -fpic -mthumb-interwork -ffunction-sections -funwind-tables -fstack-protector -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ -include system/core/include/arch/linux-arm/AndroidConfig.h -DANDROID -fmessage-length=0 -W -Wall -Wno-unused -DSK_RELEASE -DNDEBUG -O2 -g -Wstrict-aliasing=2 -finline-functions -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers -DNDEBUG -UDEBUG -mthumb -Os -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 -MD -o out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/hello.o development/hello/hello.c

Android.mk 编译参数比较,上面主要减少了不必要的 -I 参数。

接下来生成可执行文件:

# prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/arm-eabi-gcc -nostdlib -Bdynamic -Wl,-T,build/core/armelf.x -Wl,-dynamic-linker,/system/bin/linker -Wl,--gc-sections -Wl,-z,nocopyreloc -o out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/LINKED/helloworld -Lout/target/product/generic/obj/lib -Wl,-rpath-link=out/target/product/generic/obj/lib -lc -lm out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/hello.o out/target/product/generic/obj/lib/crtbegin_dynamic.o -Wl,--no-undefined ./prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/../lib/gcc/arm-eabi/4.2.1/interwork/libgcc.a out/target/product/generic/obj/lib/crtend_android.o

这里值得留意的是参数“ -Wl,-dynamic-linker,/system/bin/linker ”,它指定了 Android 专用的动态链接器 /system/bin/linker ,而不是通常所用的 ld.so

生成的可执行程序可用 file readelf 命令来查看一下:

# file out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/LINKED/helloworld

out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/LINKED/helloworld: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), not stripped

# readelf -d out/target/product/generic/obj/EXECUTABLES/helloworld_intermediates/LINKED/helloworld |grep NEEDED

0x00000001 (NEEDED) Shared library: [libc.so]

0x00000001 (NEEDED) Shared library: [libm.so]

这是 ARM 格式的动态链接可执行文件,运行时需要 libc.so libm.so 。“ not stripped ”表示它还没被 STRIP 嵌入式系统 中为节省空间通常将编译完成的可执行文件或动态库进行 STRIP ,即去掉其中多余的符号表信息。在前面“ make helloworld showcommands ”命令的最后我们也可以看到, Android 编译 环境中使用了 out/host/linux-x86/bin/soslim 工具进行 STRIP

有关 Android Toolchain 的其他一些内容可参考: Android Toolchain Bionic Libc

AndroidJNI 的调试

Android SDK 中没有包括 JNI 的支持,而且对如何支持 JNI 也没有任何文档说明。不过既然整个 Android 平台是开源的,我们可以通过 Google 发布的源代码来找到一些线索(比如 frameworks/base/media/jni / 目录),依葫芦画瓢的实现上层 JAVA 程序通过 JNI 来调用 Native C 程序中的函数。

依照下面的步骤可以实现一个非常简单的 JNI 的实例程序:

1. 首先编写 C 模块,实现动态库。(关于如何在 Android 中编译 C 模块的更多细节,请参考《 Android 编译环境 (1) - 编译 Native C helloworld模块 》。)

development 目录下添加新目录 hellolib ,并添加 hellolib.c Android .mk 文件。 hellolib.c 的内容如下:

#include <jni .h>

#define LOG_TAG "TestLib"

#undef LOG

#include <utils/Log.h>

JNIEXPORT void JNICALL Java_com_test_TestHelloLib_printHello(JNIEnv * env, jobject jobj)

{

LOGD("Hello LIB!/n");

}

注意这里的函数名需要按照 JNI 的规范(因此也可以用 javah -jni 工具来生成头文件,来保证函数名的正确性), Java_com_test_TestHelloLib_printHello 的命名对应后面在 java 代码中, package 名字是 com.test ,类名是 TestHelloLib native 函数名是 printHello

另外, LOGD #define LOG_TAG "TestLib" 等打印 log 的方式是采用了 Android 所提供的 LOG 机制,这样才能通过 Android logcat 工具看到 log

用于编译 C 模块的 Android .mk 文件内容如下:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES:= /

hellolib.c

LOCAL_C_INCLUDES := /

$(JNI_H_INCLUDE)

LOCAL_SHARED_LIBRARIES := /

libutils

LOCAL_PRELINK_MODULE := false

LOCAL_MODULE := libhello

include $(BUILD_SHARED_LIBRARY)

该文件中的一些变量分别对应的含义如下:

LOCAL_SRC_FILES 编译的源文件

LOCAL_C_INCLUDES 需要包含的头文件目录

LOCAL_SHARED_LIBRARIES 链接时需要的外部库

LOCAL_PRELINK_MODULE 是否需要 prelink 处理(参考 prelink 的详细介绍:《 动态库优化 ——Prelink(预连接)技术 》, Android Toolchain, prelink 工具:《 Android Toolchain Bionic Libc

LOCAL_MODULE 编译的目标对象

BUILD_SHARED_LIBRARY 指明要编译成动态库。

接下来回到 Android 顶层目录,并执行 make libhello 来编译:

# cd $(YOUR_ANDROID) && make libhello

target thumb C: libhello <= development/hellolib/hellolib.c

target SharedLib: libhello (out/target/product/generic/obj/SHARED_LIBRARIES/libhello_intermediates/LINKED/libhello.so)

target Non-prelinked: libhello (out/target/product/generic/symbols/system/lib/libhello.so)

target Strip: libhello (out/target/product/generic/obj/lib/libhello.so)

Install: out/target/product/generic/system/lib/libhello.so

编译结果可得到位于 out/target/product/generic/system/lib/ 目录的动态共享库 libhello.so

2 .编写 Java 模块,来通过 JNI 方式调用 C 接口。具体 Eclipse 环境的搭建请参考 Android SDK 文档中的详细说明,及 Hello Android 程序的创建过程,这里仅给出我们需要修改的 TestHelloLib.java 文件:

package com.test;

import android .app.Activity;

import android .os.Bundle;

public class TestHelloLib extends Activity {

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super .onCreate(savedInstanceState);

setContentView(R.layout.main );

printHello() ;

}

static {

System.loadLibrary( "hello" );

}

private native void printHello() ;

}

注意上面代码中粗体字部分: private native void printHello() 用来声明一个 native 接口, static { System.loadLibrary("hello"); } 用来加载上面步骤中生成 libhello.so (注意 loadLibrary 方法的参数不是 ”libhello.so” ,而是去掉前缀和后缀之后的 ”hello” ), onCreate() 方法中则调用了 printHello() 接口。

通过这一步骤可生成 Android 开发者所熟悉的 apk 文件: TestHelloLib.apk

3 .集成测试 TestHelloLib.apk libhello.so 。先运行 emulator 并将 TestHelloLib.apk libhello.so 上传至 emulator 中。注意要将 libhello.so 上传到 emulator /system/lib 目录,由于该目录是只读的,上传之前先要执行 adb remount

# adb remount

# adb push out/target/product/generic/system/lib/libhello.so /system/lib

# adb install TestHelloLib.apk

接下来在模拟器菜单中可以看到已经安装的 TestHelloLib 程序,运行即可。

由于 JNI 接口 printHello() 并没有作界面上的改动,要验证其效果需要用 Android logcat 工具来查看。运行 ”adb logcat” 可以找到下面的 log 片断:

I/ActivityManager( 48): Starting activity: Intent { action=android .intent.action.MAIN categories={android .intent.category.LAUNCHER} flags=0x10200000 comp={com.test/com.test.TestHelloLib} }

I/ActivityManager( 48): Start proc com.test for activity com.test/.TestHelloLib: pid=174 uid=10024 gids={}

D/dalvikvm( 174): Trying to load lib /system/lib/libhello.so 0x43481c58

D/dalvikvm( 174): Added shared lib /system/lib/libhello.so 0x43481c58

D/dalvikvm( 174): No JNI_OnLoad found in /system/lib/libhello.so 0x43481c58

D/dalvikvm( 174): +++ not scanning '/system/lib/libwebcore.so' for 'printHello' (wrong CL)

D/dalvikvm( 174): +++ not scanning '/system/lib/libmedia_jni.so' for 'printHello' (wrong CL)

D/TestLib ( 174): Hello LIB!

I/ActivityManager( 48): Displayed activity com.test/.TestHelloLib: 806 ms

这里包含了调用 printHello() 接口的 log 信息,其中 D/TestLib ( 174): Hello LIB!” 就是 printHello() 所打印的信息。至此成功完成 Android JNI 的实例验证。

更多相关文章

  1. Ubuntu Android开发环境配置
  2. Android:apk文件结构
  3. Android Layout文件的属性说明
  4. Android 源代码目录结构1 - bionic
  5. Fedora 下配置 Android 开发环境
  6. 在Android中建立Android project没有R.java文件

随机推荐

  1. android makefile(android.mk)分析(序)
  2. Android架构组件四 Android(安卓)Archite
  3. android,内存优化详解
  4. Android电话监听器
  5. 【Android开发】Android(安卓)Host详解(翻
  6. Android(安卓)Studio属性学习(三)——四种
  7. Lifehacker 2011 最佳Android应用软件
  8. Android原生程序与Flutter交互具体实现
  9. 风行网android
  10. Android系统