第十五章 本地开发工具集(NDK)
NDK,是帮助你集成本地代码的外加附件,所谓本地代码就是在Android应用中使用一些平台特性,而这些平台特性通常是通过C或C++暴漏的API。NDK允许你的Android应用调用一些本地代码乃至包含一些本地库。在Android的姜饼(Gingerbread)版,NDK对本地代码的支持更是提供了NativeActivity类。现在你可以用C或C++来写整个activity了。然而,NativeActivity并不是本章的主题。在这里,我们来看看如何在你的Java Android应用中集成本地C代码。
什么是和什么不是NDK要做的?
你在应用开发部分使用本地代码的主要动机是性能。就像你看到的那样,NDK很好的支持数学和图像库,也很好的支持一些系统库。所以,对于那些图形和计算要求很强的应用,NDK是最好的候选。近来逐渐繁荣的流行手机游戏以此来驱动就是一个不错的证明。需要注意的是,任何通过Java本地接口(JNI)访问你应用的本地代码仍然在应用程序的Dalvik虚拟机上运行。所以它服从与Android应用程序同样的安全沙箱规则。用C或C++来编写一部分也许Java并不适合的代码并不是一个很好的使用NDK的理由。要记住很多低级的硬件特性已经很优美的通过Android框架暴露出来给Java,你可以随意的使用它们。
NDK解决的问题
NDK解决了一些你在做本地开发不得不处理的重要问题。
工具链
Java通过JNI提供了访问本地代码的入口。为使其工作,你可能不得不在你的主机上为目标架构编译所有的东西,而这样需要你在你的开发机器上拥有完整的工具链。安装指定的跨平台编译器和其他工具可不是简单的事。NDK提供了完全的工具链,这样你就可以编译和构建本地代码使其运行在你的目标平台上了。这个构建系统做成了很容易安装你的环境并在项目中集成你的本地代码。
打包你的库
如果你有一个本地库并且你想要它在你的应用程序中可用,你一定要确定你的库的路径是系统加载库的路径。在Linux中,典型的路径是LD_LIBRARY_PATH。在Android设备上,只有/system/lib是系统加载的路径。这里有一个问题是因为整个/system区是只读的,不能用来安装库。
NDK通过输送你的本地库使其成为应用程序包(apk)的一部分的机制解决了这个问题。基本上,当用户安装一个包含本地库的APK时,系统就创建一个名为/data/data/你的包/lib/的路径。如果你回顾95页的“文件系统阐述”,这个路径(partition)是此应用程序私有的,这是个安全的地方存放你的库,它可以阻止其他应用程序加载和使用你的库。这个打包机制为发布应用程序给Android设备带来了激动人心的变化,同时它也是一件大事,因为此机制给游戏提供了巨大的复用范围和新的本地代码。
文档和标准头文件
NDK带来了帮助文档和示例程序来解释如何使本地代码工作。同时也标准化了一些C和C++的头文件,如:
• libc (C 库) 头文件
• libm (数学库) 头文件
• JNI接口头文件
• libz (Zlib压缩) 头文件
• liblog (Android日志) 头文件
• OpenGL ES 1.1和OpenGL ES2.0(3D图形库)头文件
• libjnigraphics (像素缓存存取) 头文件(Android2.2及以上)
• 支持C++的mini头文件集
• OpenSL ES 本地音频库
• Android 本地应用API
有了标准的头文件集,你可能会猜测(extrapolated)NDK对哪些更合适。在下一章节我们就会检验它。
一个NDK的例子:斐波那契(Fibonacci)
因为NDK特别适合高强度的计算类应用,我想寻找一个这样的例子,我们要能在本地代码和Java代码中实现一个相对简单的算法,并比较它们的相对速度。所以,我选择了斐波那契算法来做这个例子。这是一个简单的算法,可以很容易的在C和Java中实现。另外,我们还可以用递归(recursively)和迭代(iteratively)两种方式。
先快速回顾一下,斐波那契数列的定义如下:
fib(0)=0
fib(1)=1
fib(n)=fib(n-1)+fib(n-2)
斐波那契数列的排序类似于:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,等等
在这个例子中,我们将要:
• 创建一个java类来显示斐波那契库
• 创建本地代码头文件
• 用C来实现本地代码
• 编译并构建一个共享库
• 在Android activity中使用此本地代码
FibLib
FibLib是我们声明计算斐波那契数列算法的地方。我们总共有四个斐波那契算法的版本:
• Java 递归版本
• Java 迭代版本
• 本地递归版本
• 本地迭代版本
我们在Example 15-1中先写一个Java的实现,然后用C再写一个本地版本。
Example 15-1. FibLib.java
package com.marakana; public class FibLib { // Java implementation - recursive public static long fibJ(long n) { //注释一 if (n <= 0) return 0; if (n == 1) return 1; return fibJ(n - 1) + fibJ(n - 2); } // Java implementation - iterative public static long fibJI(long n) { //注释二 long previous = -1; long result = 1; for (long i = 0; i <= n; i++) { long sum = result + previous; previous = result; result = sum; } return result; } // Native implementation static { System.loadLibrary("fib"); //注释三 } // Native implementation - recursive public static native long fibN(int n); //注释四 // Native implementation - iterative public static native long fibNI(int n); //注释五 }
注释一:这是斐波那契递归算法的Java递归版本
注释二:这是相同Java递归算法的迭代版本。实现递归算法的任务都可以分解为迭代算法。
注释三:本地版本要实现一个共享库。这里,我们告诉Java虚拟机来加载这个库,当调用时这个函数就会被寻找到。
注释四:我们声明本地斐波那契方法,但并没有实现它。注意要在这里使用native关键字。它告诉Java虚拟机实现的这个方法在这个共享库中。这个库要在函数调用之前(prior to)加载。
注释五:前一个声明是为本地递归版本的,这个是为迭代版本的。
到这里,我们的Fiblib就完成了。但是我们还是需要回到用C来实现本地方法,首先,我们需要创建一个适当的(appropriate)JNI头文件。