这一篇将通过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*...)函数的,自动从这个函数进行执行,所以注册或者初始化一些信息可以在这里进行.
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