Android NDK JNI C++ <12>JNI_OnLoad具体实现(非常重要)
这一篇是非常重要,前面我们都介绍都是swig转换cpp文件以后在供上层APP使用,这一篇将通过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
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*...)函数的,自动从这个函数进行执行,所以注册或者初始化一些信息可以在这里进行.
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; }
可以依次将所有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中被调用.
Android.mk:
LOCAL_PATH :=$(call my-dir) include $(CLEAR_VARS) LOCAL_SHARED_LIBRARY := libnativehelper liblog LOCAL_MODULE :=jnidemolib LOCAL_SRC_FILES := jnidemo.cpp onload.cpp include $(BUILD_SHARED_LIBRARY)
结果: