导读
为了在Android OS系统上开发应用程序,Google提供了两种开发包:SDK和 NDK。你可以从Google官方查阅到有许多关于SDK的优秀的书籍、文章作为参考,但Google没有提供足够的NDK资料。在现有的书籍中,我认为 Cinar O.写于2012年的”Pro Android C++ with the NDK”值得一读。
本文旨在帮助那些缺乏Android NDK经验但又想扩充这方面知识的人们。我所关注的是JNI(本地编程接口,简称JNI)。本文分上下两篇,在上篇中,会从JNI为接口开始讲起;下篇会进行回顾,并给出带两个文件读写功能的实例。
ImportNew注:如果你也对Java技术翻译分享感兴趣,欢迎加入我们的 Android开发 小组。参与方式请查看小组简介。
JNI函数:
JNI接口不仅有自己的数据集(dataset)也有自己的函数。回顾这些数据集和函数需要花费我们很多时间。可以从官方文档中找到更多信息:
http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html
JNI函数使用示例
下面会通过一个简短的示例确保你对这些资料所讲的内容有了正确的理解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include <jni.h>
...
JavaVM *jvm;
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption* options =
new
JavaVMOption[1];
options[0].optionString =
"-Djava.class.path=/usr/lib/java"
;
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized =
false
;
JNI_CreateJavaVM(&jvm, &env, &vm_args);
delete
options;
jclass cls = env->FindClass(
"Main"
);
jmethodID mid = env->GetStaticMethodID(cls,
"test"
,
"(I)V"
);
env->CallStaticVoidMethod(cls, mid, 100);
jvm->DestroyJavaVM();
|
让我们来逐个分析字符串:
- JavaVM — 提供了一个接口,可以调用函数创建、删除Java虚拟机。
- JNIEnv — 确保了大多数的JNI函数。
- JavaVMlnitArgs — Java虚拟机参数。
- JavaVMOption — Java虚拟机选项。
JNI的_CreateJavaVM()方法初始化Java虚拟机并向JNI接口返回一个指针。
JNI_DestroyJavaVM()方法可以载入创建好的Java虚拟机。
线程
内核负责管理所有在Linux上运行的线程;线程通过AttachCurrentThread和AttachCurrentThreadAsDaemon函数附加到Java虚拟机。如果线程没有被添加成功,则不能访问JNIEnv。 Android系统不能停止JNI创建的线程,即使GC(Garbage Collection)在运行释放内存时也不行。直到调用DetachCurrentThread方法,该线程才会从Java虚拟机脱离。
第一步
你的项目结构应该如图3所示:
![图3—工程结构](https://i-blog.csdnimg.cn/blog_migrate/bc76dfc1071150cecee71dada551582b.png)
在图3中,所有本地代码都存储到一个jni的文件夹。在新建一个工程后,Libs文件夹会被分为四个子文件夹。这意味着一个子目录对应一种处理器架构,库的数量取决于处理器架构的数量。
要创建一个本地项目和一个Android项目可以参照以下面的步骤:
- 创建一个jni文件夹 — 包含本地代码的项目源代码根目录。
- 创建一个Android.mk文件用来构建项目。
- 创建一个Application.mk文件用来存储编译参数。虽然这不是必须的配置,但是推荐你这么做。这样会使得编译设置更加灵活。
- 创建一个ndk-build文件以此来显示编译过程(同样这一步也不是必须的)。
Android.mk
就像前面提到的,Android.mk是编译本地项目的makefile。Android.mk把代码按照模块进行了划分,把静态库(static library)拷贝到项目的libs文件夹,生成共享库(shared library)和独立的可执行文件。
最精简的配置示例:
1
2
3
4
5
|
LOCAL_PATH := $(call my-
dir
)
include $(CLEAR_VARS)
LOCAL_MODULE := NDKBegining
LOCAL_SRC_FILES := ndkBegining.c
include $(BUILD_SHARED_LIBRARY)
|
让我们来仔细看看:
- LOCAL_PATH:-$(call my-dir) — 调用函数宏my-dir返回当前文件所在路径。
- include $(CLEAR_VARS) — 清除所有LOCAL_PATH以外的变量。这是必须的步骤,考虑到所有编译控制文件都位于同一个GNU MAKE执行环境中,所有变量都是全局的。
- LOCAL_MODULE — 输出模块名称。在上述例子中,输出模块叫做NDKBegining。但是在生成以后,会在libs文件夹中创建libNDKbegining库。同时,Android系统会为其添加一个前缀名lib,例如一个被命名为”foo”的共享库模块,将会生成”libfoo.so”文件。 但是在Java代码中使用库时应该忽略前缀名(也就是说,名称应该和makefile一样)。
- LOCAL_SRC_FILE — 列出编译所需要的源文件。
- include $(BUILD_SHARED_LIBARY) — 输出模块的类型。
你可以在Android.mk文件中设置自定义变量;但是必须遵守语法命名规则:LOCAL_、PRIVATE_、NDK_、APP_、my-dir。Google建议自定义示例前缀使用MY_,例如:
1
|
MY_SOURCE := NDKBegining.c
|
这样就调用了一个变量$(MY_SOURCE)。变量同样也可以被连接起来,例如:
1
|
LOCAL_SRC_FILES += $(MY_SOURCE)
|
Application.mk
这个makefile中定义了好几种变量让编译更加灵活:
- APP_OPTM — 这个变量是可选的,用于指定程序是“release”还是“debug”。在构建应用程序模块时,该变量用来优化构建过程。你可以在调试中指定“release”,不过“debug”支持的配置选项更多。
- APP_BUILD_SCRI为Android.mk定义了另一条路径。
- APP_ABI — 最重要的变量之一。它指定了编译模块时使用的目标处理器架构。默认情况下,APP_ABI会设置为“armeabi”,对应于ARMv5TE架构。例如,如果要支持 ARMv7,就需要设置为“armeabi-v7a”。对于IA-32-x86和MIPS-mips这样支持多体系架构的系统,应该把 APP_ABI设置为“armeabi armeabi-v7a x86 mips”。在NDK修订版本7或更高的版本中,可以简单的设置APP_ABI := “all rather enumerating all the architectures”。
- APP_PLATFORM — 为目标平台名称;
- APP_STL — Android提供了一个最精简的libstdc c++运行库,因此开发人员使用的c++功能是非常有限的。然而使用APP_STL变量就可以使这些库支持扩展功能。
- NDK_TOOLCHAIN_VERSION-GCC — 选择的GCC编译器版本(默认情况下设置为4.6)。
NDK-BUILDS
NDK-build是一个GNU Make的包装容器。在NDK 4以后,ndk-build支持以下参数:
- clean — 清除所有已生成的二进制文件。
- NDK_DEBUG=1 — 生成可调式的代码。
- NDK_LOG=1 — 显示日志信息(用于调试)。
- NDK_HOST_32BIT=1 — 使Android系统支持64位版本(例如,NDK_PATH\toolchains\mipsel-linux-android-4.8\prebuilt\windows-x86_64,等等)。
- NDK_APPLICATION_MK=<file> — 指定Application.mk路径。
在 NDK v5中,引入了NDK_DEBUG。当NDK_DEBUG设置为“1”时,便会生成可调试版本。如果没有设置NDK_DEBUG,ndk-build会默认验证是否有在AndroidMainfest.xml文件中设置 android:debuggable=“true” 属性。如果你使用的是NDK v8以后的版本,Google不建议你在AndoirdMainfest.xml文件中使用 android:debuggable 属性(当你使用“ant debug”或ADT插件生成调试版本时,会自动添加“NDK_DEBUG=1”)。
默认情况下,设置了支持64位版本。你也可以通过设置“NDK_HOST_32BIT=1”强制使用一个32位的工具链来使用32位应用程序。不过,谷歌仍建议使用64位的应用程序来提升大型程序的性能。
如何建立一个项目?
这 是个令人头疼的步骤。你要安装CDT插件并下载cygwin或mingw编译器和Android NDK,在Eclipse设置里配置这些东西,但最后还 是不能运行。我第一次开始使用Android NDK时,配置这些东西花了我3天时间。最后发现问题出在Cygwin编译器身上:应该为项目文件夹设置读、写、可执行的所有权限。
现在可就简单多咯!只需要照着这个链接到网址:http://developer.android.com/sdk/index.html 下载ADT包,这里面有开始编译环节需要用到的所有东西。
从Java代码中调用本地方法
要从Java中调用本地代码,首先你要在Java类中定义本地方法。例如:
1
2
|
native
String nativeGetStringFromFile(String path)
throws
IOException;
native
void
nativeWriteByteArrayToFile(String path,
byte
[] b)
throws
IOException
|
你得在方法前使用“native”关键字。,这样编译器就知道这是JNI的入口点。这些方法会在C/C++文件中实现。Google建议用 “native+x”这样的命名方式,“x”代表着方法的实际名称。还有,在实现这些方法前你还得手动生成一个头文件。你可以手动执行此操作或者使用JDK的 javah工具生成头文件。然后让我们将进一步探讨如何不用控制台,直接使用标准的Eclipse开发环境:
- 打开Eclipse,选择Run -> External-tool-External -> External tools configurations。
- 新建配置。
- 指定javah.exe在jdk里的绝对路径(例如,C:\Program Files (x86)\Java\jdk1.6.0_35\bin\javah.exe)。
- 在工作目录中指定bin/class目录的路径(例如,«${workspace_loc:/NDKBegin/bin/classes}»)。
- 填入如下参数:“-jni ${java_type_name}” (注意,输入时不需要带引号)。
现在你可以运行了。你的头文件应该放在bin/classes目录下。下一步,复制这些文件到本地工程的jni目录。打开工程的配置菜单并选择 Andorid Tools这一项 — 添加本地库(Add Native Library)。这样我们就可以使用jni.h头文件中包含的函数了。在此之后,你还要创建一个.cpp的文件(有时候 Eclipse会默认生成),并且方法实现已经在头文件中定义。
考虑到文章长度和可读性,我并没有加入简单的代码示例,所以你在这里找不到。如果需要,请访问这个链接https://github.com/viacheslavtitov/NDKBegining。
原文链接: elekslabs 翻译: ImportNew.com - 陈强
译文链接: http://www.importnew.com/8052.html