(在使用 NDK 之前,应该先确定一定以及肯定 C/C++ 能更好地提升程序性能,如果 Java 也能做得很好的事,讲道理的话是不应该用。当然,隐藏代码细节的除外。)
关于 NDK 编译的文章已经烂大街了。这里只是简单总结一下在 AS 中怎么做,方便博主日后查看。有必要提供这篇文章作为参考Android NDK and OpenCV development with Android Studio,作者不仅认真负责,与时俱进,还富有情调,为广大程序员所不及也。这篇文章主要讲了怎么用 AS 来更快捷地使用 javah
, ndk-build
等命令,如何在 gradle 里面配置 task,当然也说了一下怎么来编译 opencv。
我觉得这里面比较复杂的是编译 ndk 这个过程,所以就只是简单描述一下这个流程^_^
首先,要先确定 java 和 C/C++ 的交互接口,说白了就是 java 要调用哪些 C/C++ 函数,假设是以下这个:
1 | public class NdkJniUtils { |
这里面加载外部依赖库的代码要放在 static 里面,这样会先于 onCreate 等方法执行,并且只加载一次依赖库。依赖库的名称也比较重要,下面会提到。
之后就要开始实现 C/C++ 代码了。由于 NDK 对 C/C++ 的函数名要求比较严格,新手容易出错,这个时候便可以借助 javah
这个工具了,javah
可以根据你的 native 函数,自动生成本地头文件。我这里使用 AS 的 External Tools(如何在 External Tools 中使用 javah
,请看前面那篇文章),右键 NdkJniUtils.java 使用 javah
,这时会在 jni 目录下生成 your_package_NdkJniUtils.h 这个头文件。打开这个头文件,可以在里面看到函数声明:
1 | /* |
有了这个函数声明,我们可以新建一个对应的 your_package_NdkJniUtils.cpp 或 c 文件,然后实现这个函数
1 |
|
写完代码,接下来就要准备编译了,编译的方法有两种。
方法一:使用gradle
这种方法不需要编码 Android.mk ,gradle 会自动帮我们生成。我们要做的是修改 gradle 的配置文件,在 defaultConfig 下面添加 ndk 配置:
1 | defaultConfig { |
ndk
里面有一个 moduleName
,它就是我们前面在 Java 代码中添加的依赖库的名称。
为了让 gradle 知道 ndk 放在哪,需要在 local.properties
文件中添加 ndk 目录:
1 | ndk.dir=/your-dir-path/android-ndk-r10e |
这时再 build 一下工程,gradle 会自动调用 ndk-build
命令,并且自动生成 Android.mk
,进入到你的工程目录,可以在 app/build/intermediates/ndk/debug 下面看到 Android.mk
以及 lib/<abi>/*.so
,run 之后这些 so 依赖库都会打包到 apk 文件中。
方法二:自己使用ndk-build
对于一些习惯 eclipse 的朋友,可能这种方式会更亲切一点。如果是自己在命令行编译代码的话,需要在 jni 目录下编写 Android.mk
文件( Application.mk
貌似可有可无),然后进入jni这个目录用 ndk-build
进行编译。博主也喜欢这样的方式,但博主直接用 AS 的 External Tools 调用 ndk-build
(如何在 External Tools 中使用 ndk-build
,请看前面那篇文章)。
首先需要自己配置 Android.mk(关于这个文件如何配置的,之后再学习):
1 | LOCAL_PATH := $(call my-dir) |
看到里面有一个 LOCAL_MODULE
,它就是我们在 java 代码中需要的依赖库名称。
如果想生成各个平台的依赖库,可以在 Application.mk
中这样写:
1 | APP_ABI := armeabi armeabi-v7a x86 |
之后,右键刚才创建的 your_package_NdkJniUtils.cpp/c 文件,执行 ndk-build,这样会在 jniLibs 目录下生成那些 .so 文件。接下来用 gradle 编译整个项目,注意要现在 gradle 配置文件中添加一句:
1 | android { |
这样 Android 的 build 系统才会根据我们自己的 Android.mk 寻找依赖库,然后链接各个模块,最终生成 apk 文件。
依赖其他第三库
当然啦,如果你没有依赖其他第三方的 .so 库,那么这两种方法都可以帮你完成编译,但如果用到第三方依赖库怎么办?对于第一种方法,需要你在 gradle 的配置文件中添加 task,声明 ndk-build
的参数,同时要自己声明 Android.mk。(这也是为什么我喜欢第二种方法的原因,既然都会用到 Android.mk,何必在 gradle 中写那么多配置)。由于配置的过程比较麻烦,这里不细说,具体可以参考最开始给出的那篇文章。
重点说说第二种方法。以编译 opencv 库为例吧。
由于需要引入 opencv 库,所以要修改我们的 Android.mk 文件
1 | LOCAL_PATH := $(call my-dir) |
然后我们在原来 cpp 文件中引入 opencv 头文件:
1 |
|
右键跑一下ndk-build
,正常的话是可以编译成功的。但如果要run这个项目,需要在gradle配置文件中添加一句:
1 | android { |
这条语句的目的是让 gradle 使用我们自己定义的 Android.mk 文件,而不是像之前的方法一一样,自己去寻找依赖然后编译。
好了,整个操作流程就讲这么多,之后有时间再看看 Android.mk 以及 jni 具体该怎么使用。