Android 很早就支持 NDK, 官方教程上说你可以把 native 代码编译成一个 .so, 在 java 代码中通过 jni 调用这个 .so.
现在在做一个 C++ 跨平台的库. 需要支持 Android. 为了在 Android 上测试, 自然可以按照教程里写 java, jni. 但其实也可以抛开 java. 直接将 c++ 代码编译成可执行文件. 下面将整个过程记录下来, 备忘, 也希望能帮助用这样的需求的人.
先下载 sdk, ndk. 解压缩就好, 然后设置好环境变量 ANDROID_SDKROOT, NDKROOT 指向这两个目录. 并将 $NDKROOT, $ANDROID_SDKROOT/platform-tools, $ANDROID_SDKROOT/tools 加入 PATH. 下面的操作使用的环境是 NDK r8, OS X 10.8.
开始啦:
#include <stdio.h>
int main(){printf("hello world.
");return 0;}
把这个文件保存为 /tmp/hello.cpp. 要编译这个程序, 需要写一个 Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS) # mk 文件前面两句都这样, 不解释
LOCAL_MODULE := hello-world # 模块名, 必须唯一
LOCAL_SRC_FILES := ../hello.cpp # 要编译的源文件
include $(BUILD_EXECUTABLE) # 编译成可执行文件. 这个是关键.
# 其他的选项是 BUILD_SHARED_LIBRARY, BUILD_STATIC_LIBRARY 编译成 .so, .a
貌似还只能把这个 .mk 文件放到一个 jni 的文件夹里. 然后在 /tmp 目录执行 ndk-build. 如果环境都设置好了, 这个能够编译成功. 放到模拟器上试试吧.
执行
android - avd
打开 Android Virtual Device Manager (注意 – avd 之间有一个空格), 创建一个虚拟机, 然后启动它. 确保一切 ok. 然后执行
adb push libs/armeabi/hello-world /data/ && adb shell /data/hello-world
1372 KB/s (299852 bytes in 0.213s)
hello world.
push 到模拟器, 然后执行 hello-world.
继续. 修改一下代码, 试试 c++ 的库文件.
#include <iostream>
int main(){std::cout<<"hello world"; return 0;}
用 ndk-build 编译会发现有错误. 因为没有 stl 的支持.
nickx:/tmp $ ndk-build
Compile++ thumb : hello-world <= hello.cpp
jni/../hello.cpp:1:20: error: iostream: No such file or directory
jni/../hello.cpp: In function 'int main()':
jni/../hello.cpp:4: error: 'cout' is not a member of 'std'
make: *** [obj/local/armeabi/objs/hello-world/__/hello.o] Error 1
在 /tmp/jni 下建立 Application.mk, 加上下面一句:
APP_STL := gnustl_static # 另一个选项是 gnustl_shared
链接 gnustl. 再编译就 ok 了.
因为项目要链接 boost, 加上 boost header 试试吧.
#include <iostream>
#include <boost/thread.hpp>
void proc(){std::cout << "hello world
";}
int main()
{
boost::thread thrd(proc);
thrd.join();
return 0;
}
首先是找不到 boost 头文件. 这里要做一些准备工作. 将需要编译的 boost 库在 android 编译好. 这里就不赘述了. 得到 libboost_thread.a, libboost_system.a, 将这些文件这样组织
/vendor /boost Android.mk /boost thread.hpp .. /lib libboost_thread.a libboost_system.a
其中的 Android.mk 的内容是这样的:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= boost_thread
LOCAL_SRC_FILES:= lib/android/lib$(LOCAL_MODULE).a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) # 导出头文件, 因为有这句, 在我们的程序中的 Android.mk 文件中就不需要 LOCAL_C_INCLUDES 来指定 boost 头文件所在位置了.
include $(PREBUILT_STATIC_LIBRARY) # 所谓的编译好的静态库
include $(CLEAR_VARS)
LOCAL_MODULE:= boost_system
LOCAL_SRC_FILES:= lib/android/lib$(LOCAL_MODULE).a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
boost 库就准备好了. 现在修改程序的 Android.mk 文件:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-world
LOCAL_SRC_FILES := ../hello.cpp
LOCAL_CPP_FEATURES := exceptions rtti # boost thread 库需要 exceptions 的支持. rtti 后面也会用到
LOCAL_STATIC_LIBRARIES := boost_thread boost_system # 要的就是这两个库.
include $(BUILD_EXECUTABLE)
$(call import-module,boost) # 导入 boost 库.
再次编译会提示 import-module 找不到 boost, 需要定义 NDK_MODULE_PATH
nickx:/tmp $ export NDK_MODULE_PATH=/vendor
nickx:/tmp $ ndk-build
Compile++ thumb : hello-world <= hello.cpp
Prebuilt : libboost_thread.a <= /vendor/boost/lib/android/
Prebuilt : libboost_system.a <= /vendor/boost/lib/android/
Executable : hello-world
Install : hello-world => libs/armeabi/hello-world
到这里差不多了.
文中提到的 .mk 文件中的宏 (LOCAL_..) 的具体含义参见 $NDKROOT/documentation.html