这节主要介绍的内容是Android NDK开发的核心内容和开发总结(包括不少常见问题的解决方案),本节主要分为三部分:
* JNI技术和javah命令
* Android NDK Dev Guide
* NDK开发中常见的问题html
NDK开发的核心之一即是JNI,在Oracle官方的JNI相关文档中重要的是里面的第3-4部分(数据类型和函数),本文不会介绍这些,若是想快速入手能够查看这位做者的几篇关于JNI的文章,讲得深刻浅出,另外推荐一篇IBM DeveloperWorks上的文章:JNI 对象在函数调用中的生命周期,讲得有点深奥哟。java
javah produces C header files and C source files from a Java class. These files provide the connective glue that allow your Java and C code to interact.android
(1)在External Tools Configurations
中新建Program
c++
(2)Location
设置为/usr/bin/javah
[你可能不是这个位置,试试${system_path:javah}
]shell
(3)Working Directory
设置为${project_loc}/bin/classes
[适用于Android项目开发]windows
(4)Arguments
设置为-jni -verbose -d "${project_loc}${system_property:file.separator}jni" ${java_type_name}
数组
(5)OK,之后只要选中要进行"反编译"的Java Class,而后运行这个External Tool就能够了!
注意由于个人Arguments
设置为导出的头文件是放在项目的jni目录中,若是不是Android NDK开发的话,请自行修改输出路径,还有Working Directory
设置为${project_loc}/bin
,不要包含后面的/classes
。若是还有问题的话,推荐看下这位做者的JNI相关配置浏览器
在ndk的根目录下有一个html文件document.html
,这个就是Android NDK Dev Guide,用浏览器打开能够看到里面介绍了NDK开发中的不少配置问题,不一样版本的NDK差异仍是蛮大的,并且NDK开发中问题会不少,不像SDK开发那么简单,因此,一旦出现了问题,运气好可以Google解决,RP弱的时候只能啃这些Guide来找答案了。这几篇文章的简单介绍能够查看Android Developer上的解释。对于这部分的内容,能够阅读下这位做者的几篇NDK Dev Guide的翻译版本,虽然略有过期,可是看后确定会很受用的,下面我简单介绍下这里的几个内容:架构
这篇文章介绍了NDK的目标和NDK开发的简易实践过程,后面的那些文章基本上都是围绕这个核心内容展开的,很是建议阅读。须要注意的是,NDK只支持Android 1.5版本以上的设备。oracle
Android.mk文件是用来描述源代码是如何进行编译的,ndk-build命令实际上对GNU Make命令的一个封装,因此,Android.mk文件的写法就相似Makefile的写法[关于Make的详细内容能够看这本书,[GNU Make的中文手册],虽然是今年读的,可是我记得的也很少了,老了老了…]
Android.mk文件能够生成一个动态连接库或者一个静态连接库,可是只有动态连接库是会复制到应用的安装包中的,静态库通常是用来生成其余的动态连接库的。你能够在一个Android.mk文件定义一个或者多个module,不一样的module可使用相同的source file进行编译获得。你不须要列出头文件,也不须要显示指明要生成的目标文件之间的依赖关系(这些内容在GNU Make中是很重要的,虽然GNU Make中的隐式规则也能够作到)。下面以hello-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)
①LOCAL_PATH := $(call my-dir)
:Android.mk文件的第一行必需要指明LOCAL_PATH
,my-dir
是编译系统提供的一个宏函数,这个宏函数会返回当前Android.mk文件所在的目录
②include $(CLEAR_VARS)
:CLEAR_VARS
是编译系统提供的一个变量,这个变量指向一个特殊的Makefile文件,它会清除全部除了LOCAL_PATH
以外的其余的LOCAL_XXX
变量。
③LOCAL_MODULE := hello-jni
:必需要指定LOCAL_MODULE
,它是指这个Android.mk要生成的目标,这个名称是一个不包含空格的惟一的字符串,编译系统会自动根据名称进行必定的修改,例如foo.so
和libfoo.so
获得的都是libfoo.so
!在Java代码中进行加载的时候使用的是没有lib
的module名。
④LOCAL_SRC_FILES := hello-jni.c
:指定C/C++源文件列表,不要包含头文件。若是须要自定义C++源文件的后缀,能够配置LOCAL_CPP_EXTENSION
参数。注意写法,我给个例子,必定要记住每行后面加上一个反斜线符,而且反斜线符后面不能再有任何内容,不然编译会报错!
LOCAL_SRC_FILES := hello-jni.c \ foo.c \ boo.cpp
⑤include $(BUILD_SHARED_LIBRARY)
:BUILD_SHARED_LIBRARY
是编译系统提供的一个Makefile文件,它会根据你前面提供的参数来生成动态连接库,同理,若是是BUILD_STATIC_LIBRARY
的话,即是生成静态连接库。
最佳实践:通常来讲,LOCAL_
做为前缀的通常定义LOCAL Module的变量,PRIVATE_
或者NDK_
或者APP_
通常定义内部使用的变量,lower-case
小写字母的名称通常也是定义内部使用的变量或者函数。若是你要在Android.mk文件定义本身的变量,建议使用MY_
做为前缀!
MY_SOURCES := foo.c ifneq ($(MY_CONFIG_BAR),) MY_SOURCES += bar.c endif LOCAL_SRC_FILES += $(MY_SOURCES)
Android.mk这篇文章中后面详细介绍了不少编译系统内置的变量和函数,以及该文件内能够设置的变量,此处就再也不赘述了。
Application.mk文件描述的是你的应用须要使用哪些native modules,这个文件不是必须的,小项目能够不用编写这个文件。这个文件能够放在两个不一样的位置,最经常使用的是放在jni目录下,和Android.mk文件放在一块,也能够放在$NDK/apps/<myapp>/
目录下(不推荐使用后者,若是使用的是后者,那么必需要显示指定APP_PROJECT_PATH
)
①APP_MODULES
:这个参数在NDK r4以前是必定要指定的,以后即是可选的,默认状况下,NDK将编译Android.mk文件中定义的全部的modules。
②APP_CFLAGS
:这个参数用来指定编译C/C++文件选项参数,例如-frtti -fexceptions
等等,而APP_CPPFLAGS
是专门用来指定编译C++源文件的选项参数。
③APP_ABI
:这个参数很重要,默认状况下,ndk-build将生成对应armeabi
CPU架构的库文件,你能够指定其余的CPU架构,或者同时指定多个(自从NDK r7以后,设置为all
能够生成全部CPU架构的库文件)!关于不一样CPU架构的介绍在CPU Arch ABIs
中介绍了,我不是很懂,此文不细讲。若是想要查看某个android设备是什么CPU架构,能够上网查设备的资料,或者经过执行adb shell getprop ro.product.cpu.abi
获得,下面这段摘自OpenCV for Android SDK
armeabi, armv7a-neon, arm7a-neon-android8, mips and x86 stand forplatform targets: * armeabi is for ARM v5 and ARM v6 architectures with Android API 8+, * armv7a-neon is for NEON-optimized ARM v7 with Android API 9+, * arm7a-neon-android8 is for NEON-optimized ARM v7 with Android API 8, * mips is for MIPS architecture with Android API 9+, * x86 is for Intel x86 CPUs with Android API 9+. If using hardware device for testing/debugging, run the following command to learnits CPU architecture: *** adb shell getprop ro.product.cpu.abi *** If you’re using an AVD emulator, go Window > AVD Manager to see thelist of availible devices. Click Edit in the context menu of theselected device. In the window, which then pop-ups, find the CPU field.
④APP_STL
:指定STL,默认状况下ndk编译系统使用最精简的C++运行时库/system/lib/libstdc++.so
,可是你能够指定其余的。详细的内容能够查看$NDK/docs/CPLUSPLUS-SUPPORT.html
文件,这个文件可能并无列出在document.html中!
system -> Use the default minimal system C++ runtime library. gabi++_static -> Use the GAbi++ runtime as a static library. gabi++_shared -> Use the GAbi++ runtime as a shared library. stlport_static -> Use the STLport runtime as a static library. stlport_shared -> Use the STLport runtime as a shared library. gnustl_static -> Use the GNU STL as a static library. gnustl_shared -> Use the GNU STL as a shared library.
咱们能够从下面的表格中看出它们对C++语言特性的支持程度:
C++ C++ Standard Exceptions RTTI Library system no no no gabi++ yes yes no stlport yes yes yes gnustl yes yes yes
从中咱们能够看出gnustl很不错,因此通常会配置为gnustl_static。若是选用的是gnustl的话,通常还须要在C/C++ General
下的Paths and Symbols
中的GNU C
和GNU C++
配置里添加${NDKROOT}/sources/cxx-stl/gnu-libstdc++/4.6/include
和 ${NDKROOT}/sources/cxx-stl/gnu-libstdc++/4.6/libs/armeabi-v7a/include
这两项。
另外须要注意的是,若是你指定的是xxx_shared
,想要在运行时加载它,而且其余的库是基于xxx_shared
的话,必定记得要先加载xxx_shared
,而后再去加载其余的库。
⑤APP_PLATFORM
:指定目标android系统版本,注意,指定的是API level
,通常状况下,这里可能会与AndroidManifest.xml
文件中定义的minSdkVersion
冲突而报错,处理办法是相似上一节中提到的修改APP_PLATFORM
保证两个不冲突就好了。
build system会自动加载C库,Math库以及C++支持库,因此你不须要经过LOCAL_LDLIBS
指定加载他们。Android系统下有多个API level
,每一个API level
都对应了一个Android的发布系统,对应关系以下所示。其中android-6
,android-7
和android-5
是同样的NDK,也就是说他们提供的是相同的native ABIs。对应API level
的头文件都放在了$NDK/platforms/android-<level>/arch-arm/usr/include
目录下,这正是上一节中导入的项目中在C/C++ General
下的Paths and Symbols
中的GNU C
和GNU C++
配置。
Note that the build system automatically links the C library, the Math library and the C++ support library to your native code, there is no need to list them in a LOCAL_LDLIBS line. There are several "API Levels" defined. Each API level corresponds to a given Android system platform release. The following levels are currently supported: android-3 -> Official Android 1.5 system images android-4 -> Official Android 1.6 system images android-5 -> Official Android 2.0 system images android-6 -> Official Android 2.0.1 system images android-7 -> Official Android 2.1 system images android-8 -> Official Android 2.2 system images android-9 -> Official Android 2.3 system images android-14 -> Official Android 4.0 system images Note that android-6 and android-7 are the same as android-5 for the NDK, i.e. they provide exactly the same native ABIs! IMPORTANT: The headers corresponding to a given API level are now located under $NDK/platforms/android-<level>/arch-arm/usr/include
介绍几个比较重要的库:
(1)C库(libc):不须要指定 –lpthread –lrt,也就是说它会自动连接
(2)C++库(lstdc++):不须要指定 –lstdc++
(3)Math库(libm):不须要指定 –lm
(4)动态连接器库(libdl):不须要指定 –ldl
(5)Android log(liblog):须要指定 –llog
(6)Jnigraphics库(libjnigraphics):这个C语言库提供了对Java中Bitmap的操做,须要指定 –ljnigraphics,这个库是android-8
新增长的内容,典型的使用方式是:
Briefly, typical usage should look like: 1/ Use AndroidBitmap_getInfo() to retrieve information about a given bitmap handle from JNI (e.g. its width/height/pixel format) 2/ Use AndroidBitmap_lockPixels() to lock the pixel buffer and retrieve a pointer to it. This ensures the pixels will not move until AndroidBitmap_unlockPixels() is called. 3/ Modify the pixel buffer, according to its pixel format, width, stride, etc.., in native code. 4/ Call AndroidBitmap_unlockPixels() to unlock the buffer.
(7)The Android native application APIs:android-9
新增长的内容,这些API使得你能够彻底使用native code编写android app,可是通常状况下仍是须要经过jni的,相关API以下:
The following headers correspond to these new native APIs (see comments inside them for more details): <android/native_activity.h> Activity lifecycle management (and general entry point) <android/looper.h> <android/input.h> <android/keycodes.h> <android/sensor.h> To Listen to input events and sensors directly from native code. <android/rect.h> <android/window.h> <android/native_window.h> <android/native_window_jni.h> Window management, including the ability to lock/unlock the pixel buffer to draw directly into it. <android/configuration.h> <android/asset_manager.h> <android/storage_manager.h> <android/obb.h> Direct (read-only) access to assets embedded in your .apk. or the Opaque Binary Blob (OBB) files, a new feature of Android X.X that allows one to distribute large amount of application data outside of the .apk (useful for game assets, for example). All the corresponding functions are provided by the "libandroid.so" library version that comes with API level 9. To use it, use the following: LOCAL_LDLIBS += -landroid
使用ndk-build
命令(ndk r4以后引入的)其实是GNU Make的封装,它等价于make -f $NDK/build/core/build-local.mk [参数]
命令。系统必需要安装GNU Make 3.81以上版本,不然编译将报错!若是你安装了GNU Make 3.81,可是默认的make命令没有启动,那么能够在执行ndk-build
以前定义GNUMAKE这个变量,例如GNUMAKE=/usr/local/bin/gmake ndk-build
。
注意 在Windows下进行NDK开发的话,通常使用的是Cygwin自带的Make工具,可是默认是使用NDK的awk工具,因此可能会报一个错误Android NDK: Host 'awk' tool is outdated. Please define HOST_AWK to point to Gawk or Nawk !
解决方案就是删除NDK自带的awk工具(参考网址),这也就是第一节中使用ndk-build -v
命令获得的GNU Make信息输出不一样了,嘿嘿,我这伏笔埋的够深吧!其实,也可使用下面的方式直接覆盖系统的环境变量
NDK_HOST_AWK=<path-to-awk> NDK_HOST_ECHO=<path-to-echo> NDK_HOST_CMP=<path-to-cmp>
若是仍是不行的话,参见StackOverflow上的解答
在Windows先开发还有一个须要注意的是,若是是使用Cygwin对native code进行编译,那么须要在使用ndk-build
以前调用NDK_USE_CYGPATH=1
!(不过不用每次都使用)
下面是ndk-build命令的可用参数,比较经常使用的是 ndk-build NDK_DEBUG=1
或者 ndk-build V=1
ndk-build --> rebuild required machine code. ndk-build clean --> clean all generated binaries. ndk-build NDK_DEBUG=1 --> generate debuggable native code. ndk-build V=1 --> launch build, displaying build commands. ndk-build -B --> force a complete rebuild. ndk-build -B V=1 --> force a complete rebuild and display build commands. ndk-build NDK_LOG=1 --> display internal NDK log messages (used for debugging the NDK itself). ndk-build NDK_DEBUG=1 --> force a debuggable build (see below) ndk-build NDK_DEBUG=0 --> force a release build (see below) ndk-build NDK_HOST_32BIT=1 --> Always use toolchain in 32-bit (see below) ndk-build NDK_APPLICATION_MK=<file> --> rebuild, using a specific Application.mk pointed to by the NDK_APPLICATION_MK command-line variable. ndk-build -C <project> --> build the native code for the project path located at <project>. Useful if you don't want to 'cd' to it in your terminal.
[6]NDK GDB,Import Module,Prebuilts,Standalone Toolchains以及和CPU相关的三个内容由于我没有涉及过,本身也不是很了解,因此此处暂时搁置了,之后若是用到之后补充。关于NDK调试环境的搭建能够参见这位做者的实践博文
在Windows下修改hosts文件:C:\Windows\System32\drivers\etc
增长以下一行配置:74.125.237.1 dl-ssl.google.com
Fatal signal 11 (SIGSEGV) at 0x00000004 (code=1), thread 23487 (mple)
错误缘由是由于访问了非法访问的内存地址,具体的缘由多是访问了null对象或者数组,颇有多是Java层传给Native层的对象是null,致使Native层访问了非法访问的地址。参考网址1 参考网址2
默认状况下avd对应的目录是只读的,去掉只读就行了。参考网址
add Native Support
报错使用add Native Support
时必定要记住项目不能有jni目录!若是有的话,那就只能先删除(或者备份重要内容),而后再执行add Native Support
。
使用自定义的将jstring转换成char*的函数,内容以下:
c++ static char* jstringToString(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = env->FindClass("java/lang/String"); jstring strencode = env->NewStringUTF("utf-8"); //"gbk");// jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode); jsize alen = env->GetArrayLength(barr); jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE); if (alen > 0) { rtn = (char*) malloc(alen + 1); memcpy(rtn, ba, alen); rtn[alen] = '\0'; } env->ReleaseByteArrayElements(barr, ba, 0); return rtn; }