初识Android NDK

本文介绍Windows环境下搭建Android NDK开发环境,并创建一个简单的使用Native代码的Android Application。

一、环境搭建

二、JNI函数绑定

三、例子


一、环境搭建

1. 操作系统:Windows7 64位

2. 安装Java,最新的JDK8貌似还不支持,敢于折腾的同学可以试试,下载JDK7安装即可,别忘了添加JDK的bin目录到PATH环境变量。http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html。"jdk-7u71-windows-x64.exe"

3. 下载Android ADT Bundle,https://developer.android.com/sdk/index.html。"adt-bundle-windows-x86_64-20140702.zip"

4. [可选]使用SDK Manager更新到最新,本文编辑之时API Level已经更新到21了。

5. 下载NDK开发包,解压即可,路径要固定不要轻易改动,因为Eclipse的NDK插件会配置这个路径。https://developer.android.com/tools/sdk/ndk/index.html。"android-ndk-r10b"

6. 安装NDK插件,在Eclipse中,打开菜单Help>Install New Software,Work with里用下拉菜单选择Android Developer Tools Update Site,下面就会出现Developer Tools的分组,展开选择Android Native Development Tool,点击Finish安装。不用选择Developer Tools里面其他的插件,它们已经安装好了。

从NDK r7开始就不需要使用cygwin了,因此以上就是所有的环境搭建步骤。

二、JNI函数绑定

先在Eclipse中创建一个Android Project。然后添加Native支持,在Project Explorer中右键选择新建的工程,在菜单中选择Android Tools>Add Native Support,会自动创建好JNI目录和Android.mk,以及配置好C\C++ Include路径。

JNI中Java方法调用Native方法的绑定有两种方法:一种是自动绑定,只需要C函数名和Java方法名称按照一定的规则能匹配上就能自动绑定。可以通过javah工具将绑定的规则都处理好,生成头文件方便使用,也可以根据规则自己定义函数,不使用生成头文件的方法。具体的规则是:

1. C函数要加上前缀"Java_"。

2. Java中package路径中的圆点要转换成下划线,类名和方法名都保持原样,用下划线隔开。

3. C方法中比Java方法多了两个参数放在最前面,JNIEnv*和jobject。前者是起到C代码和Java代码交互作用的一个结构体指针,后者是调用这个函数的Java对象在C代码中的代表。

4. Java调用Native方法还涉及到参数和返回值的传递,Java中的类型会转换为C中的对应类型,可以精确对应的类型有基本数据类型(如jint, jdouble)、数组(如jintArray)、String(jstring),其他类型一律当做Object(jobject)。

例如Java中定义的一个Activity的类:

package com.example.myhellojni;
public class MyHelloJniActivity extends Activity {
private native String getNativeString();
}

在C文件中的对应函数:

jstring Java_com_example_myhellojni_MyHelloJniActivity_getNativeString(JNIEnv* env, jobject thiz) {
return ...;
}

还有一种动态注册的方法,使得C代码定义的函数不必遵守命名规则也可以与Java中的方法绑定起来。这种绑定是在C代码中实现的,需要在加载这个动态链接库的时候的回调函数JNI_OnLoad中注册。注册方法见代码:

#include <jni.h>
#include <stdlib.h>

// 要绑定到Java的native方法
jstring ajey_getNativeString(JNIEnv* env, jobject thiz) {
return (*env)->NewStringUTF(env, "what? this is a native string!!!!");
} // 计算数组大小的小技巧,只能用于数组,不能用于指针(因为指针的sizeof得不到指针指向内存的大小)
#define SIZE_OF_ARRAY(array) (sizeof(array)/sizeof(array[0])) static JNINativeMethod gMethods[] = { // 这个结构体数组是传给register函数的参数,定义了java方法和C函数的对应关系
// 成员依次是java方法名称、参数和返回值签名字符串、对应的C函数名
{ "getNativeString", "()Ljava/lang/String;", (void*)ajey_getNativeString },
}; static int registerNatives(JNIEnv* env) {
// 先找到java的class。注意gMethods中并没有定义是哪个类的方法,需要在注册的时候指明。
jclass clazz = (*env)->FindClass(env, "com/ajeyone/myhellojni/MyHelloJniActivity");
if (clazz == NULL) {
return JNI_FALSE;
}
// 这一步是真正注册的地方
if ((*env)->RegisterNatives(env, clazz, gMethods, SIZE_OF_ARRAY(gMethods)) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
} jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
// 第一步获取JNIEnv
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
// 第二步注册
if (!registerNatives(env)) {
return -1;
}
// 成功返回表示JNI version的常量,失败返回-1
return JNI_VERSION_1_4;
}

以上代码有一个奇怪的地方:env指针(vm指针也是这样)使用这种方式来调用函数:(*env)->function(env, ...),原因是JNIEnv实际上是typedef定义的指针类型,那么JNIEnv* env就是二级指针,因此需要(*env)->的方式来使用;而且有点像C++中对象成员函数的调用,但这些代码不是用C++编译的而是用C编译的,实际上是结构体中定义了函数指针,所以需要将"this"指针显式地传递过去,也就是env本身。jni.h中也为C++定义了基于对象的访问JNIEnv的方式,实际上是对C函数的包装,使其在C++中更容易使用,如果你的代码是写在cpp文件中的,那么使用的就是C++对象的方式,这时JNIEnv不再是一个typedef的指针,而是一个类类型,直接使用env->function(...)就行了,而且也不需要将env当做第一个参数传递给function。

三、例子

使用Android Tools>Add Native Development Support的时候,将Library名称定义成MyHelloJni。需要注意的是不要忘记写加载动态库的代码,否则肯定是找不到Native方法的。在Eclipse中C\C++代码不会在保存时自动编译,需要自己在菜单中选择Build或者在运行App时触发编译。

前面Java代码中Activity的全部内容:

package com.ajeyone.myhellojni;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView; public class MyHelloJniActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView text = new TextView(this);
text.setText(getNativeString());
setContentView(text); // 保持简单,未使用layout文件
} private native String getNativeString(); static { // 放在这里是一般的做法,如果有需求可以在其他地方加载
System.loadLibrary("MyHelloJni"); // 千万不要忘了加载动态库
}
}
上一篇:c语言——知识点


下一篇:使用java.util.Timer来周期性的执行制定的任务