第0步:下载工具

好了,让我们开始吧。你需要下载NDK。我们先开始下载,因为在下载的过程中你可以检查一下确保你所需要用到的其余工具的版本都正确。

从Android网站下载适合你的操作系统的NDK。

现在,对照下列检查你的工具版本:

如果在Windows下,Cygwin 1.7或更高版本

将awk升级到最新版本(我们使用的是20070501)

GNU Make 3.81或更高版本(我们使用的是3.81)

如果其中任何一个的版本太旧,请在继续之前先升级。

第1步:安装NDK

既然NDK已经下载完成(没错吧?),你就需要解压缩它。解压后将它放入合适的目录中。我们把它放在和Android SDK相同的目录下。记住你把它放在哪里了。

现在,你也许想要在路径设置中添加NDK工具。如果你在Mac或Linux下,你可以用你的原生路径设置来完成。如果你在Windows下的Cygwin,你就需要设置Cygwin的路径设置。

第2步:创建项目

创建一个常规的Android项目。为了避免日后的问题,你的项目的路径必须不包含空格。我们的项目有个叫做“com.mamlambo.sample.ndk1”的包,带有一个叫做“AndroidNDK1SampleActivity”的默认Activity——你之后还会看到它们。

在这个项目的顶层创建一个叫做“jni”的目录——这是你放置原生代码的地方。如果你很熟悉JNI,那你就会知道Android NDK很大程度上基于JNI的概念——它本质上是个只有有限的C语言编译头文件的JNI。

第3步:添加一些C语言代码

现在,在jni文件夹中,创建一个叫做native.c的文件。一开始将以下C语言代码写入该文件,我们以后再添加另一个函数:

它获取一个Java对象的字符串参数,将它转换为C-string,然后将它写入到LogCat中。

不过该函数的名字很重要。它遵循了以“Java”的特定字样开头,后面跟着包名称,然后类名称,然后方法名称,和Java中定义的一样。每一部分都由一根下划线隔开,而不是点。

该函数的头两个参数也很重要。第一个参数是JNI环境,它与helper函数会被频繁调用。第二个参数是该函数所属的Java对象。

第4步:从Java中调用原生代码

既然你已经写好了原生代码,让我们回头看看Java这边。在默认的Activity中,按照你的喜好创建一个按钮,并添加一个按钮处理器。从按钮处理器中,对helloLog作调用:

23.helloLog("This will log to LogCat via the native call.");
然后你必须在Java这边添加函数声明。在你的Activity类中添加如下声明:
24.private native void helloLog(String logThis);
它告诉编译和链接系统该方法将在原生代码中实现。
最后,你需要加载原生代码最终编译到的库。在Activity类中添加如下的静态初始化程序来根据名称加载库(库的名字随你决定,在下一步还会用到):
25.
26.
27.static {
28.
29.System.loadLibrary("ndk1");
30.
31.}
32.
第5步:添加原生代码的Make文件
在jni文件夹中,现在你需要添加在编译中要用到的makefile。该文件必须以“Android.mk”命名,如果你之前命名的文件为native.c,库为ndk1,那么Android.mk的内容就应该是这样:
33.
34.
35.LOCAL_PATH:= $(call my-dir)
36.
37.
38.
39.include $(CLEAR_VARS)
40.
41.
42.
43.LOCAL_LDLIBS:= -llog
44.
45.
46.
47.LOCAL_MODULE:=ndk1
48.
49.LOCAL_SRC_FILES:=native.c
50.
51.
52.
53.include $(BUILD_SHARED_LIBRARY)
54.

第6步:编译原生代码

既然你的原生代码已完成,make文件也已就绪,是时候编译原生代码了。在命令行下(Windows用户在Cygwin下),你需要在你的项目的根目录下运行ndk-build命令。ndk-build工具就在NDK工具目录中。将它添加到我们的路径中是最方便的办法。

在之后的编译中,如果你使用“ndk-build clean”命令,那么你可以确保所有的东西都被重新编译了。

第7步:运行代码

现在你已准备妥当可以运行代码了。在你最喜欢的模拟器或者手持设备中载入该项目,查看LogCat,然后点击按钮。

可能有两件事情会发生。首先,它可能正常工作了。这样的话,恭喜你!不过你可能还是想要继续阅读下去。你也可能在LogCat中得到类似“Could not execute method of activity”这样的错误。这很正常。这只是说明你漏掉了某个步骤罢了。用Eclipse很容易发生这种情况。通常,Eclipse被设置为自动重编译。如果它不知道有东西被修改了,它就不会自动重编译和重链接。在本例中,Eclipse不知道你编译了原生代码。所以,“清除(cleaning)”该项目(在Eclipse工具栏中点击项目(Project)->清除(Clean)),强制Eclipse重编译。

第8步:添加另一个原生函数

接下来的函数将不仅演示返回值的能力,还会演示返回例如字符串这样的对象的能力。在native.c中添加如下函数:

1.jstring Java_com_mamlambo_sample_ndk1_AndroidNDK1SampleActivity_getString(JNIEnv * env, jobject this, jint value1, jint value2)2.{
3.char *szFormat = "The sum of the two numbers is: %i";
4.char *szResult;
5.// add the two values
6.jlong sum = value1+value2;
7.// malloc room for the resulting string
8.szResult = malloc(sizeof(szFormat) + 20);
9.// standard sprintf
10.sprintf(szResult, szFormat, sum);
11.// get an object string
12.jstring result = (*env)->NewStringUTF(env, szResult);
13.// cleanup
14.free(szResult);
15.return result;
16.}
17.
18.
为了正常编译,你会需要添加一个include stdio.h的声明。而且,为了响应这个新的原生函数,请在你的Activity Java类中添加如下声明:
19.
20.private native String getString(int value1, int value2);
你现在可以随意设定其功能。我们使用如下两个调用和输出:
21.String result = getString(5,2);
22.Log.v(DEBUG_TAG, "Result: "+result);
23.result = getString(105, 1232);
24.Log.v(DEBUG_TAG, "Result2: "+result);

回到C语言函数中,你会注意到我们做了许多事情。首先,我们在使用malloc()函数中的sprintf()调用时需要创建一个缓冲器(buffer)。如果你不会忘记通过使用free()函数清理结果,那么这就很合理了。然后,为了传回结果,你可以使用一个叫作NewStringUTF()的JNI helper函数。该函数基本上就是获取一个C语言字符串,以之创建一个新的Java对象。这个新的字符串对象就可以在之后作为结果返回,你就可以在Java类中将它作为一个常规Java字符串对象使用了。

指令集、兼容性,等等

Android NDK需要Android SDK 1.5或更高版本。在新版本的NDK中,有些新的头文件可用于扩大对某些API的访问——特别是OpenGL ES库。

不过,那些都不是我们要谈论的兼容性。这是原生代码,在使用时由处理器构架编译。因此,你要问自己的一个问题会是它支持何种处理器构架?在目前的NDK中(在本文撰写时)它只支持ARMv5TE和ARMv7-A指令集。默认设置下,目标架构被设置为ARMv5TE,它可以在使用ARM芯片的Android设备上运行。

它预计未来将支持其他指令集(其中提到了x86)。这其中有很有意思的潜在状况:NDK解决方案无法适用于所有的设备。例如,市面上有使用x86指令集的英特尔(Intel)Atom处理器的Android平板设备。

那么NDK在模拟器上如何呢?模拟器运行的是真正的虚拟机,包括完整的处理器虚拟。没错,这意味着在虚拟机中运行Java就等于是在虚拟机中运行了一个虚拟机。