Android NDK开发之三: JNI_OnLoad使用方法(c++版本)

这一篇将通过JNI_OnLoad中进行注册的方式,通过将所有cpp文件中所有的方法全部注册,就不要再通过swig转换,就可以提供给APP使用了.

步骤如下:

<1> : 新建一个Android工程,并且新建一个jni文件夹,新建一个org的包,在这个包下面新建一个Jnidemo.java的文件,JNidemo.java代码如下:

java文件不能从虚拟那边传过来,奇怪,看图吧:

 

<2> : build一次,然后通过在jni文件目录下:

javah -classpath ../bin/classes org.Jnidemo

在jni文件夹下就有了org_Jnidemo.h的文件,将其重新命名成jnidemo.h,这是这里面这个文件已经不重要,只能说用来作参考,因为实际build成so时并不需要这个文件.所需要的信息,反而是在/bin/classes 下:

javap -s org.Jnidemo

这里面打印出来的信息非常重要,主要是签名信息.

<3> : 然后新建onload.cpp,onload.h(等效网上其他博客为jniUtil.h),jnidemo.cpp:

onload.h

include<jni.h>
#ifndef _ON_LOAD_HEADER_H__
#define _ON_LOAD_HEADER_H__

JNIEnv* getJNIEnv();
int jniThrowException(JNIEnv *env,const char* className,const char* msg);
int jniRegisterNativeMethods(JNIEnv* env,const char* className,const JNINativeMethod* gMethod,int numMethods);

#endif

扫描二维码关注公众号,回复: 8894663 查看本文章


onload.cpp:include<stdlib.h>
#include<android/log.h>

#include "onload.h"

extern int register_android_jni_demo_android(JNIEnv *env);

static JavaVM *sEnv;

int jniThrowException(JNIEnv *env, const char* className, const char* msg) {

jclass exceptionClass = env->FindClass(className);

if (exceptionClass == NULL) {
return -1;
}

if (env->ThrowNew(exceptionClass, msg) != JNI_OK) {

}

return 0;

}

JNIEnv* getJNIEnv() {

JNIEnv* env = NULL;

if (sEnv->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return NULL;
}

return env;

}

int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
jclass clazz;

clazz = env->FindClass(className);

if (clazz == NULL) {
return -1;
}

if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return -1;
}

return 0;

}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {

JNIEnv* env = NULL;
jint result = JNI_ERR;
sEnv = vm;

/*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);
* GetEnv()函数返回的 Jni 环境对每个线程来说是不同的,
* 由于Dalvik虚拟机通常是Multi-threading的。每一个线程调用JNI_OnLoad()时,
* 所用的JNI Env是不同的,因此我们必须在每次进入函数时都要通过vm->GetEnv重新获取
*
*/

if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}

/*开始注册
* 传入参数是JNI env
* 由于下面有很多class,只以register_android_media_FFMpegPlayerAndroid(env)为例子
*/

if (register_android_jni_demo_android(env) != JNI_OK) {
goto end;
}

//following could do it as the previous the way of register

/*if (register_android_jni_demo_android_1(env) != JNI_OK) {
goto end;
}
...
if (register_android_jni_demo_android_n(env) != JNI_OK) {
goto end;
}*/


return JNI_VERSION_1_4;

end: return result;

}

jnidemo.cpp:#include<jni.h>
#include<stdlib.h>
#include"jnidemo.h"
#include"onload.h"

static const char *classpath = "org/Jnidemo";
namespace android {
int nativeopen() {
return 123;
}

float nativelibversion() {
return 1.0;
}

}

using namespace android;

static JNINativeMethod mMethods[] = {

{ "nativeopen", "()I", (void *) nativeopen }, {
"nativelibversion", "()F",
(void *) nativelibversion }

};

int register_android_jni_demo_android(JNIEnv* env) {

return jniRegisterNativeMethods(env, classpath, mMethods,
sizeof(mMethods) / sizeof(mMethods[0]));

}


jnidemo.cpp为实际有功能处理的程序文件,其他cpp可以仿照这个cpp文件,提供注册信息分为下面几个步骤:

<a> : 提供对应的类信息:

static const char *classpath = "org/Jnidemo";

<b> : 实现java方法在native中(即cpp中)所以对应的函数:

namespace android {
int nativeopen() {
return 123;
}

float nativelibversion() {
return 1.0;
}

}

如果见了命名空间,那么下面就要生效:

using namespace android;

<c> : 提供java(APP级)和native(CPP级)两者的对应关系,从而形成一个映射表:

static JNINativeMethod mMethods[] = {

{ "nativeopen", "()I", (void *) nativeopen }, {
"nativelibversion", "()F",
(void *) nativelibversion }

};

这是一个数组,其中数组元素:
第一个参数:为java中使用的方法名,如"nativeopen";

第二个参数:即通过javap -s可以显示出来的,或者直接注意它的规则,比如说:

前面:()代表要传入的参数位置,如果函数为add(int x,int y),那么他就会是(II),代表有两个参数,并且这两个参数的数据类型为整形int,I是int的缩写.

后面:即括号后面的代表返回值,如果函数为int add(...),那么他就是(...) I,I代表有一个返回值,并且返回类型是整形.int的缩写,我下面nativelibversion返回的是float,所以他就是()F,F是float的缩写 .

第三个参数:即在native的cpp文件中对应的函数名,无论是否放回,前面均是(void*) ;

请注意: 在每个cpp文件中只是提供注册信息,注册动作不在这里实现,统一放在onload.cpp文件中的JNI_OnLoad中进行.不过每个cpp中都调用onload.cpp文件中的函数:jniRegisterNativeMethods(...),

这个函数是onload.cpp提供给其他所有native cpp文件统一注册信息的调用的.

实际注册是APP调用so时,虚拟机会自动首先寻找JNI_OnLoad(JNIEnv*...)函数的,自动从这个函数进行执行,所以注册或者初始化一些信息可以在这里进行.

int JNI_OnLoad(JavaVM* vm, void* reserved) {

JNIEnv* env = NULL;
jint result = JNI_ERR;
sEnv = vm;

/*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);
* GetEnv()函数返回的 Jni 环境对每个线程来说是不同的,
* 由于Dalvik虚拟机通常是Multi-threading的。每一个线程调用JNI_OnLoad()时,
* 所用的JNI Env是不同的,因此我们必须在每次进入函数时都要通过vm->GetEnv重新获取
*
*/

if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}

/*开始注册
* 传入参数是JNI env
* 由于下面有很多class,只以register_android_media_FFMpegPlayerAndroid(env)为例子
*/

if (register_android_jni_demo_android(env) != JNI_OK) {
goto end;
}

//following could do it as the previous the way of register

/*if (register_android_jni_demo_android_1(env) != JNI_OK) {
goto end;
}
...
if (register_android_jni_demo_android_n(env) != JNI_OK) {
goto end;
}*/


return JNI_VERSION_1_4;

end: return result;

}

可以依次将所有native cpp中的在里面集中注册,注册又调用到其他cpp中的方法:
可以这样引入:

extern int register_android_jni_demo_android(JNIEnv *env);


另外:或者虚拟机:如果系统有多个虚拟机,并且版本不同,可以通过下面的方式,指定加载指定的虚拟机版本,下面是1.4的版本,他就不会加载其他版本了.JNIEnv* getJNIEnv() {

JNIEnv* env = NULL;

if (sEnv->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return NULL;
}

return env;

}

最后onload.cpp还有一个异常处理,这里是如果查找不到指定的类,就报异常:int jniThrowException(JNIEnv *env, const char* className, const char* msg) {

jclass exceptionClass = env->FindClass(className);

if (exceptionClass == NULL) {
return -1;
}

if (env->ThrowNew(exceptionClass, msg) != JNI_OK) {

}

return 0;

}

被其他native cpp调用输入注册信息的函数并且实现注册:int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
jclass clazz;

clazz = env->FindClass(className);

if (clazz == NULL) {
return -1;
}

if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return -1;
}

return 0;

}


所以注册调用方式:

onload.cpp实现注册功能的函数:jniRegisterNativeMethods--->被其他其他native cpp调用:register_android_jni_demo_android---->最后在onload.cpp的JNI_OnLoad中被调用.

文章整理来自:https://www.cnblogs.com/MMLoveMeMM/articles/3746843.html

发布了216 篇原创文章 · 获赞 67 · 访问量 95万+

猜你喜欢

转载自blog.csdn.net/cbk861110/article/details/89766447
今日推荐