一个应用程序有许多功能使用Java 无法解决或者说效率等其他因素往往又满足不了要求,所以这个时候就应该考虑开发基于JNI的应用程序来满足某些要求。
NDK给基于JNI的应用开发带来了极大的便利。步骤如下:
- 新建Android工程,并在根目录下建立 jni 目录,然后再 jni 目录加入 JNI 层的实现代码和对应的Android.mk 文件。
- 将项目复制到NDK samples 目录,运行 ndk-build 命令。NDK 会自动编译出共享库(so文件),并置于armeabi目录下。
- 将新生成的目录和文件从 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 规范。但是有以下缺点:
- 需要遵守繁琐的JNI 实现方法的命名规则(规则) 。比如刚才的show 方法:Java_com_heqing_jni_JniActivity_show,一旦出错,将无法调用到 JNI 层的实现方法。
- 如果采用应用层的JNI 使用方式,就需要在Java 层加入 System.loadLibrary("app_jni"); 这样的加载共享库的代码。而应用层会频繁调用,严重影响效率。
- 虚拟机在共享库中搜索定位 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 方法签名规则“ 部分。