利用NDK开发基于JNI 的应用程序

一个应用程序有许多功能使用Java 无法解决或者说效率等其他因素往往又满足不了要求,所以这个时候就应该考虑开发基于JNI的应用程序来满足某些要求。

NDK给基于JNI的应用开发带来了极大的便利。步骤如下:

  1. 新建Android工程,并在根目录下建立 jni 目录,然后再 jni 目录加入 JNI 层的实现代码和对应的Android.mk 文件。
  2. 将项目复制到NDK samples 目录,运行 ndk-build 命令。NDK 会自动编译出共享库(so文件),并置于armeabi目录下。
  3. 将新生成的目录和文件从 NDK 中导入编辑器。

Java层:

新建Android 工程,生成一个启动activity名为JniActivity。内容如下:

public class JniActivity extends Activity{

	private static final String TAG = "JniActivity";
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.jni_layout);
		String msg = show();
		Log.i(TAG, msg);
	}

	/**
	 * 声明一个native 方法,需要在JNI 中实现
	 * @return
	 */
	private native String show();
	
	/**
	 * JNI 中调用callback 函数,用来演示在JNI 中如何操作Java类。
	 * 为了演示JNI 函数调用过程如何捕获Java异常,专门在callback 
	 * 函数中抛出NullPointerException,而且没有捕获这个异常。
	 */
	private void callback(){
		Log.i(TAG, "本地代码调用Java方法");
		throw new NullPointerException();
	}
	/**
	 * 在静态代码库中加载libapp_jni.so 共享库,这个共享库在安装应用程序的时候,
	 * 由PackageManager 从apk中解压输出到文件:/data/data/包名/lib/libapp_jni.so
	 */
	static{
		System.loadLibrary("app_jni");//不需要带共享库的前缀lib和后缀so
	}
}
JNI层代码和异常处理:

#includes<string.h>
#includes<jni.h>//引入jni.h 头文件,这里定义了所有JNI 函数和数据类型
Jstring Java_com_heqing_jni_JniActivity_show(JNIEnv* env,jobject thiz)
{
	/*通过JNI 函数GetObjectClass 得到传入对象的类信息,
	*这里传入的对象就是调用Native 方法的那个对象。
	*/
	jclass jcls = (*env)->GetObjectClass(env,thiz);
	//根据类信息得到callback方法的 jmethodID
	jmethodID jmID = (*env)->GetMethodID(env,jcls,"callback","()V");
	//调用callback方法
	(*env)->CallVoidMethod(env,thiz,jmID);
	/*因为在java层的callback 方法中抛出了未捕获的异常,所以上面的JNI 
	*函数调用必然出现异常,这里必须检查并处理异常,否则异常将抛给Java层
	*的callback 方法而此时Java层又没有捕获异常,此时,进程将崩掉。
	*/
	if((*env)->ExceptionCheck(env))
	{
		(*env)->ExceptionDescribe(env);//异常堆栈信息
		(*env)->ExceptionClear(env);//清除异常
	}
	//处理异常后响应Java曾的调用
	return (*env)->NewString(env,"这条消息是调用 JNI 函数返回的。");
}

如果在以上代码中不处理异常,进程将会终止。

所以在 JNI 编程中,一定要好好处理 JNI 函数调用过程中可能出现的异常。

以上使用JNI 的方式是:遵守JNI 规范的函数命名,进而建立声明函数和实现函数之间的对应关系。JNI框架针对应用层JNI 编程和应用框架层JNI 编程提供了两套编程机制。刚刚上面使用的是传统的 JNI 编程方式,符合 JNI 规范。但是有以下缺点:

  1. 需要遵守繁琐的JNI 实现方法的命名规则(规则) 。比如刚才的show 方法:Java_com_heqing_jni_JniActivity_show,一旦出错,将无法调用到 JNI 层的实现方法。
  2. 如果采用应用层的JNI 使用方式,就需要在Java 层加入 System.loadLibrary("app_jni"); 这样的加载共享库的代码。而应用层会频繁调用,严重影响效率。
  3. 虚拟机在共享库中搜索定位 JNI 实现方法效率也受影响。Android应用框架层采用函数注册的方法来回避这些问题。

当然也可以在应用层采用函数注册的方法,接下来改造一下:

<pre name="code" class="cpp">#includes<string.h>
#includes<jni.h>//引入jni.h 头文件,这里定义了所有JNI 函数和数据类型
//这里可以不用再遵守 JNI 的函数命名规范了,因为我们采用的是函数注册的方法
Jstring Java_com_heqing_jni_JniActivity_show(JNIEnv* env,jobject thiz)
{
	/*通过JNI 函数GetObjectClass 得到传入对象的类信息,
	*这里传入的对象就是调用Native 方法的那个对象。
	*/
	jclass jcls = (*env)->GetObjectClass(env,thiz);
	//根据类信息得到callback方法的 jmethodID
	jmethodID jmID = (*env)->GetMethodID(env,jcls,"callback","()V");
	//调用callback方法
	(*env)->CallVoidMethod(env,thiz,jmID);
	/*因为在java层的callback 方法中抛出了未捕获的异常,所以上面的JNI 
	*函数调用必然出现异常,这里必须检查并处理异常,否则异常将抛给Java层
	*的callback 方法而此时Java层又没有捕获异常,此时,进程将崩掉。
	*/
	if((*env)->ExceptionCheck(env))
	{
		(*env)->ExceptionDescribe(env);//异常堆栈信息
		(*env)->ExceptionClear(env);//清除异常
	}
	//处理异常后响应Java曾的调用
	return (*env)->NewString(env,"这条消息是调用 JNI 函数返回的。");
}

//下面是为了完成函数注册添加的代码,这里是Java层方法和JNI层方法的映射
static JNINativeMethod gMethods[] = {
	{"show","()Ljava/lang/String;",(void*)Java_com_heqing_jni_JniActivity_show},
};

static int registerNativeMethods(JNIEnv* env,const char* className,JNINativeMethod* gMethods,int numMethods)
{
	jclass clazz;
	clazz = (*env)->FindClass(env,className);
	if(clazz == NULL){
		return JNI_FALSE;
	}
	//调用JNIEnv 提供的注册函数向虚拟机注册
	if((*env)->RegisterNatives(env,clazz,gMethods,numMethods) < 0){
		return JNI_FALSE;
	}
	return JNI_TRUE;
}

static int registerNatives(JNIEnv* env){
	if(!registerNativeMethods(env,"com/heqing/jni/JniActivity")
		,gMethods,sizeof(gMethods) / sizeof(gMethods[0])){
			return JNI_FALSE;
		}
		return JNI_TRUE;
}
/*虚拟机执行System.loadLibrary("app_jni")后,进入libapp_jni.so
后会首先执行这个方法,所以要在这里添加注册的代码*/
jint JNI_OnLoad(JavaVM* vm,void* reserved)
{
	jint result = -1;
	JNIEnv* env = NULL;
	if((*vm)->GetEnv(vm,(void **)&env,JNI_VERSION_1_4))
	{
		goto fail;
	}
	//最终调用(*env)->RegisterNatives
	if(registerNatives(env) != JNI_TRUE){
		goto fail;
	}
	result = JNI_VERSION_1_4;
	fail:
		return result;
}

 以上便完成了函数注册方式的改造。解释: 
static JNINativeMethod gMethods[] = {
	{"show","()Ljava/lang/String;",(void*)Java_com_heqing_jni_JniActivity_show},
};

为什么要这样写请看:http://blog.csdn.net/hqocshheqing/article/details/49303855 的 ”JNI 方法签名规则“ 部分



  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值