8步教你打开Android之门 NDK入门教程
·8步教你打开Android之门NDK入门教程
这是一篇AndroidNDK开发的入门教程,在这一教程结束后,你将创建你自己的项目,从Java代码简单地调用原生C语言代码。
前不久我们为大家介绍过在MyEclipse8.6上搭建Android开发环境,本文为一篇外文翻译,我们将介绍如何学习安装AndroidNDK并开始使用它。在这一教程结束后,你将创建你自己的项目,从Java代码简单地调用原生C语言代码。
教程细节
技术:AndroidSDK、NDK、C语言
难度:进阶
预计完成时间:60-90分钟
先决经验
在我们开始之前,我们需要先花点时间了解一下这一教程的难度。它的标记是“进阶”。之所以标为“进阶”是因为我们这些作者想要确保你符合以下要求:
你有Java和C语言经验。
你能适应命令行操作。
你知道如何了解你的Cygwin、awk和其他工具的版本。
你能适应AndroidDevelopment。
你有一个有效的Android开发环境(本文撰写时,笔者使用的是Android2.2)
你使用Eclipse或者可以将Eclipse的指导步骤轻松应用于你自己的IDE上。
就算你并不满足这些条件,我们当然也欢迎你阅读这一教程,不过你可能在某些步骤遇到困难,如果你满足了以上条件这些困难就会轻易解除。也就是说,即使你认为自己是个移动开发老手,使用NDK依然很容易碰到困难和麻烦。请注意你可能要自行排查故障才能让一切正常运转于你的开发系统中。
本教程提供完整的样例项目的开源代码下载。
何时使用NDK的说明
好,如果你正在阅读这篇教程,你也许已经在考虑在你的Android项目中使用NDK了。不过,我们想要花点时间讨论一下NDK为什么那么重要、何时该使用它,以及——同等重要的,何时不该使用它。
总的来说,只有当你的应用程序真的是个处理器杀手的时候你才需要使用NDK。也就是说,你设计的算法要利用DalvikVM中所有的处理器资源,而且原生运行较为有利。还有,别忘了在Android2.2中,JIT编译器会提高类似代码的效率。
另一个使用NDK的原因是方便移植。如果你在现有的应用程序中有大量的C语言代码,那么使用NDK不仅可以加速你的项目的开发进程,也能在你的Android和非Android项目中保持修改的同步。这一点对于那些为其他平台而写的OpenGLES应用程序来说尤为如此。
别以为只要用了原生代码就能提高你的应用程序的效率。Java与原生C语言之间的转换会增加一些资源开销,因此只有你有一些集中消耗处理器资源的任务时才真正有必要这么做。
第0步:下载工具
好了,让我们开始吧。你需要下载NDK。我们先开始下载,因为在下载的过程中你可以检查一下确保你所需要用到的其余工具的版本都正确。
从Android网站下载适合你的操作系统的NDK。
现在,对照下列检查你的工具版本:
如果在Windows下,Cygwin1.7或更高版本
将awk升级到最新版本(我们使用的是20070501)
GNUMake3.81或更高版本(我们使用的是3.81)
如果其中任何一个的版本太旧,请在继续之前先升级。
第1步:安装NDK
既然NDK已经下载完成(没错吧?),你就需要解压缩它。解压后将它放入合适的目录中。我们把它放在和AndroidSDK相同的目录下。记住你把它放在哪里了。
现在,你也许想要在路径设置中添加NDK工具。如果你在Mac或Linux下,你可以用你的原生路径设置来完成。如果你在Windows下的Cygwin,你就需要设置Cygwin的路径设置。
第2步:创建项目
创建一个常规的Android项目。为了避免日后的问题,你的项目的路径必须不包含空格。我们的项目有个叫做“com.mamlambo.sample.ndk1”的包,带有一个叫做“AndroidNDK1SampleActivity”的默认Activity——你之后还会看到它们。
在这个项目的顶层创建一个叫做“jni”的目录——这是你放置原生代码的地方。如果你很熟悉JNI,那你就会知道AndroidNDK很大程度上基于JNI的概念——它本质上是个只有有限的C语言编译头文件的JNI。
第3步:添加一些C语言代码
现在,在jni文件夹中,创建一个叫做native.c的文件。一开始将以下C语言代码写入该文件,我们以后再添加另一个函数:
1.#include
2.
3.#include
4.
5.#include
6.
7.#defineDEBUG_TAG"NDK_AndroidNDK1SampleActivity"
8.
9.voidJava_com_mamlambo_sample_ndk1_AndroidNDK1SampleActivity_helloLog(JNIEnv*env,jobjectthis,jstringlogThis)
10.
11.{
12.
13.jbooleanisCopy;
14.
15.constchar*szLogThis=(*env)->GetStringUTFChars(env,logThis,&isCopy);
16.
17.__android_log_print(ANDROID_LOG_DEBUG,DEBUG_TAG,"NDK:LC:[%s]",szLogThis);
18.
19.(*env)->ReleaseStringUTFChars(env,logThis,szLogThis);
20.
21.}
22.
这个函数实际上非常浅显。它获取一个Java对象的字符串参数,将它转换为C-string,然后将它写入到LogCat中。
不过该函数的名字很重要。它遵循了以“Java”的特定字样开头,后面跟着包名称,然后类名称,然后方法名称,和Java中定义的一样。每一部分都由一根下划线隔开,而不是点。
该函数的头两个参数也很重要。第一个参数是JNI环境,它与helper函数会被频繁调用。第二个参数是该函数所属的Java对象。
第4步:从Java中调用原生代码
既然你已经写好了原生代码,让我们回头看看Java这边。在默认的Activity中,按照你的喜好创建一个按钮,并添加一个按钮处理器。从按钮处理器中,对helloLog作调用:
23.helloLog("ThiswilllogtoLogCatviathenativecall.");
然后你必须在Java这边添加函数声明。在你的Activity类中添加如下声明:
24.privatenativevoidhelloLog(StringlogThis);
它告诉编译和链接系统该方法将在原生代码中实现。
最后,你需要加载原生代码最终编译到的库。在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:=$(callmy-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-buildclean”命令,那么你可以确保所有的东西都被重新编译了。
第7步:运行代码
现在你已准备妥当可以运行代码了。在你最喜欢的模拟器或者手持设备中载入该项目,查看LogCat,然后点击按钮。
可能有两件事情会发生。首先,它可能正常工作了。这样的话,恭喜你!不过你可能还是想要继续阅读下去。你也可能在LogCat中得到类似“Couldnotexecutemethodofactivity”这样的错误。这很正常。这只是说明你漏掉了某个步骤罢了。用Eclipse很容易发生这种情况。通常,Eclipse被设置为自动重编译。如果它不知道有东西被修改了,它就不会自动重编译和重链接。在本例中,Eclipse不知道你编译了原生代码。所以,“清除(cleaning)”该项目(在Eclipse工具栏中点击项目(Project)->清除(Clean)),强制Eclipse重编译。
第8步:添加另一个原生函数
接下来的函数将不仅演示返回值的能力,还会演示返回例如字符串这样的对象的能力。在native.c中添加如下函数:
1.jstringJava_com_mamlambo_sample_ndk1_AndroidNDK1SampleActivity_getString(JNIEnv*env,jobjectthis,jintvalue1,jintvalue2)
2.{
3.char*szFormat="Thesumofthetwonumbersis:%i";
4.char*szResult;
5.//addthetwovalues
6.jlongsum=value1+value2;
7.//mallocroomfortheresultingstring
8.szResult=malloc(sizeof(szFormat)+20);
9.//standardsprintf
10.sprintf(szResult,szFormat,sum);
11.//getanobjectstring
12.jstringresult=(*env)->NewStringUTF(env,szResult);
13.//cleanup
14.free(szResult);
15.returnresult;
16.}
17.
18.
为了正常编译,你会需要添加一个includestdio.h的声明。而且,为了响应这个新的原生函数,请在你的ActivityJava类中添加如下声明:
19.
20.privatenativeStringgetString(intvalue1,intvalue2);
你现在可以随意设定其功能。我们使用如下两个调用和输出:
21.Stringresult=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()的JNIhelper函数。该函数基本上就是获取一个C语言字符串,以之创建一个新的Java对象。这个新的字符串对象就可以在之后作为结果返回,你就可以在Java类中将它作为一个常规Java字符串对象使用了。
指令集、兼容性,等等
AndroidNDK需要AndroidSDK1.5或更高版本。在新版本的NDK中,有些新的头文件可用于扩大对某些API的访问——特别是OpenGLES库。
不过,那些都不是我们要谈论的兼容性。这是原生代码,在使用时由处理器构架编译。因此,你要问自己的一个问题会是它支持何种处理器构架?在目前的NDK中(在本文撰写时)它只支持ARMv5TE和ARMv7-A指令集。默认设置下,目标架构被设置为ARMv5TE,它可以在使用ARM芯片的Android设备上运行。
它预计未来将支持其他指令集(其中提到了x86)。这其中有很有意思的潜在状况:NDK解决方案无法适用于所有的设备。例如,市面上有使用x86指令集的英特尔(Intel)Atom处理器的Android平板设备。
那么NDK在模拟器上如何呢?模拟器运行的是真正的虚拟机,包括完整的处理器虚拟。没错,这意味着在虚拟机中运行Java就等于是在虚拟机中运行了一个虚拟机。
更多相关文章
- Android音乐播放器开发
- Android数据库 之 SQLite数据库
- Android高手进阶教程(十七)之---Android中Intent传递对象的两种
- Android的DrawerLayout全屏滑动显示
- 写给Android开发者的Kotlin入门
- 箭头函数的基础使用
- NPM 和webpack 的基础使用
- Python技巧匿名函数、回调函数和高阶函数
- Python list sort方法的具体使用