ndk全称native develp kit,原生开发套件,起始它是提供java调用c/c++代码的工具链。
众所周知,java编译后的class二进制文件的运行效率比c/c++编译的so或dll文件效率低的多,所以,当我们对某种运算的运行效率要求高时,需要使用ndk编译好so文件,然后使用java进行调用,比如,视频编解码库。除了考虑运行效率问题,使用jni调用,还可以保护我们的核心算法不被轻易破解。我们一些重要的数据也可以放到里面去哦。
本文的以下内容都假设,ndk开发环境已经搭建完毕。
本节的目标是,通过android java代码调用jni接口暴露的getUrl和getSum接口,分别取得url字符串和两个整数相加的和。
那么,首先定义一个java接口文件吧,
(1)
创建一个Api.java文件
package cn.hugo.android.<span style="font-size:18px;">ndkdemo</span>; public class Api { public native int getSum(int a, int b); public static native String getUrl(); }
native关键字,是告诉编译器,我这个是给jni程序调用的,不要把我当成abstract方法,这样,我们在其它地方就可以new API() 这个类了,abstract是不可以new的。这个我们必须清楚。
(2)利用命令生成jni头文件
该.h头文件是连接c++程序和java程序的纽带,和Api这个类的native方法是一一对应的。
打开命令行窗口,cd到工程根目录的bin\classes下,执行如下命令
javah -jni cn.hugo.android.<span style="font-size:18px;">ndkdemo</span>.Api
执行完后,在classes文件夹就会出现一个名为 cn_hugo_android_ndkdemo_Api.h的文件
在eclipse中,右键该工程,选择如下,接下来会提示你jni编译后生成的so文件的名字(编译后的so文件名是"lib名字".so),你可以随便写一个,点ok,生成jni文件夹。该文件夹中会有一个cpp和一个mk文件。mk文件是编译脚本。
提示:如果打开xxx.cpp文件后,发现 #include <jni.h> 有下划波浪线提示:
Unresolved inclusion: <jni.h>
解决方法:
右键工程->property。。。。配置include头文件的库路径,由于我打算在arm架构和x86架构都编译一份so文件,所以同时包含了这两个库。
包含它们有什么用呢?ctrl+/的自动提示就靠它了~!!
话说回来,现在把生成的cn_hugo_android_ndkdemo_Api.h文件拷贝到jni文件夹中。
应该学过c++吧?.c文件一般是声明文件和存放常量的。我们现在需要把
JNIEXPORT jint JNICALL Java_cn_hugo_android_ndkdemo_Api_getSum (JNIEnv *, jobject, jint, jint); JNIEXPORT jstring JNICALL Java_cn_hugo_android_ndkdemo_Api_getUrl (JNIEnv *, jclass);
现在实现的方法如下:
JNIEXPORT jint JNICALL Java_cn_hugo_android_ndkdemo_Api_getSum(JNIEnv *env, jobject, jint a, jint b) { return a + b; } JNIEXPORT jstring JNICALL Java_cn_hugo_android_ndkdemo_Api_getUrl(JNIEnv *env, jclass) { return env->NewStringUTF("http://baidu.com"); }
调用JNI。。。。
作者在MainActivity.java中加载了两个按钮,使它们分别触发调用getSum和getUrl方法,并打印
private void init() { findViewById(R.id.btn_getAddResult).setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { System.out.println("..........url=" + Api.getUrl()); } }); findViewById(R.id.btn_getText).setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { Api api = new Api(); System.out.println("..........sum=" + api.getSum(120, 5)); } }); }
static { System.loadLibrary("ndk"); }
这时,运行该app,就可以正常工作了。如果之前没有编译so,这里将会对so进行编译,控制台信息如下:
[armeabi] Compile++ thumb: ndk <= ndk.cpp [armeabi] SharedLibrary : libndk.so [armeabi] Install : libndk.so => libs/armeabi/libndk.so
假如你的测试机子是arm架构的,app是可以成功运行的。
如果该so文件运行在x86内核的机子中,会爆出这样的error信息:
default: Ignoring feature request because could not acquire PhoneService之后会解决同时支持多种架构的问题。
前面是用命令生成一个头文件,但是当我们的工程多n多个jni接口需要生成.c头文件时,不可能一个个的使用命令行进行生成。此时,我们就要借助ant脚本了。
(1) 为了生成多个头文件,需要在 cn.hugo.android.ndkdemo 包下新建一个Security.java文件,声明一个native方法 public static native String getDesKey();
(2)在工程根目录下新建一个 build_headers.xml 文件,然后用ant editor编辑器打开该文件,在文件中使用alt+/键盘组合,让eclipse进行代码提示,生成一个模板。
编辑后的文件内容如下:
<?xml version="1.0" encoding="UTF-8"?> <project name="ndk_demo" default="buildHeaders"> <description> description </description> <!-- ================================= target: buildHeaders ================================= --> <target name="buildHeaders"> <antcall target="buildApiHeader"> </antcall> <antcall target="buildSecurityHeader"> </antcall> </target> <!-- ================================= destdir: 输出路径 class:要执行的类 ================================= --> <target name="buildApiHeader"> <javah destdir="./jni" classpath="./bin/classes/" class="cn.hugo.android.ndkdemo.Api"> </javah> </target> <target name="buildSecurityHeader"> <javah destdir="./jni" classpath="./bin/classes/" class="cn.hugo.android.ndkdemo.Security"> </javah> </target> </project>
打开eclipse工具右边的ant widget窗口,把该文件加入。如下图
双击该文件,现在在jni目录下生成了两个头文件。
(3)新建一个cpp文件security.cpp实现getDesKey方法:
#include <jni.h> #include "cn_hugo_android_ndkdemo_Security.h" JNIEXPORT jstring JNICALL Java_cn_hugo_android_ndkdemo_Security_getDesKey( JNIEnv *env, jclass) { return env->NewStringUTF("afvaihgbbrtjiweojvwer"); }
(4)在android.mk文件中加入新建的源文件,让工具链对该文件生成目标文件,并连接到同一个so文件中。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := ndk LOCAL_SRC_FILES := ndk.cpp <span style="color:#FF0000;">security.cpp</span> include $(BUILD_SHARED_LIBRARY)
在jni目录下,新建一个 Application.mk 文件,添加如下
<span style="font-size:18px;">APP_ABI := armeabi x86</span>
<span style="font-size:18px;">[armeabi] Compile++ thumb: mylibrary <= api.cpp [armeabi] Compile++ thumb: mylibrary <= security.cpp [armeabi] SharedLibrary : libmylibrary.so [armeabi] Install : libmylibrary.so => libs/armeabi/libmylibrary.so [x86] Compile++ : mylibrary <= api.cpp [x86] Compile++ : mylibrary <= security.cpp [x86] SharedLibrary : libmylibrary.so [x86] Install : libmylibrary.so => libs/x86/libmylibrary.so</span>