对于未知的技术我们心里总是比较忐忑,但是当你攻下这个技术领域的时候又是一种满满的收获,坚持探索----------Bill 2019.04.08
本博文采用问答的方式讲解JNI的动态注册和静态注册的方式
1. 什么是JNI?
JNI的英文缩写是 java nativie interface ,按照字面解释就是java 本地接口。什么样的接口才叫nativie interface ,用c/c++写代码。所以JNI是用c++语言编写的接口供java调用。
2.为什么JNI用C++写得代码可以供java使用,两个是完全不同的语言,他们是如何转换的
我们用java中写native方法:
public native void native_init(); //关键字native
使用javah 可以生成类似这样的头文件
#include <jni.h>
/* Header for class aai_along_and_jni_test2_JNI_test1 */
#ifndef _Included_aai_along_and_jni_test2_JNI_test1
#define _Included_aai_along_and_jni_test2_JNI_test1
#ifdef __cplusplus
extern "C" {
#endif
里面有个#include <jni.h> ,#include 就是c/c++引用头文件的方式。所以答案也就在这个jni.h里面。对jni有点了解的应该知道jni的几个数据类型,c++的数据类型跟JNI的数据类型对应关系都定义在jni.h里面,如下。
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;
jni.h文件配合jvm,这样就完成了c++层的数据与java层的数据转换过程,因此native可以传递值到java ,java 也可以传值到native实现数据的相互通信关系。其实从中也是可以看出来JNI只是做了一个转换的工作:数据转换的过程。
3.静态注册
1)加载jni库并且加载nativie方法,本文的JNI库名为:jni-test
public class JNIStaticTest{
public String name;
public Context testContext;
private final String TAG="JNIStaticTest-java";
//加载的jni库,库名:libjni-test.so ,加载的时候省略lib和.so
static {
System.loadLibrary("jni-test");
}
public JNIStaticTest(){
native_init();
}
public JNIStaticTest(Context context){
Log.d(TAG,"初始化");
testContext=context;
native_init();
}
public void setName(String name1){
Log.i(TAG,"setName:"+name1);
name =name1;
}
public String getName(){
Log.i(TAG,"getName:"+name);
return name;
}
public String transimFromJNI(String from,ExternClass inner){
Log.i(TAG,"from="+from+",ExternClass name="+inner.className);
String returnString="Java have get information";
return returnString;
}
//以下内地方法实现在native层,这里只是作为函数的调用接口。
public native String stringFromJNI();
public native void native_init();
public native void transmitToJNI(String from, ExternClass inner);
}
可能你会问,系统是怎么识别到我的lib库呢?系统会在三个文件夹去搜索system/lib ,vend/lib ,your app package/lib。/data/app/aai.along.and.jni/lib/arm/libjni-test.so 这是在我的app安装目录下找到的。如果这三个目录都没有找到lib那么就会报UnsatisfiedLinkError异常。在libcore\ojluni\src\main\java\java\lang\Runtime.java 代码里面有这样的描述。
public void loadLibrary(String libname, ClassLoader classLoader) {
checkTargetSdkVersionForLoad("java.lang.Runtime#loadLibrary(String, ClassLoader)");
java.lang.System.logE("java.lang.Runtime#loadLibrary(String, ClassLoader)" +
" is private and will be removed in a future Android release");
loadLibrary0(classLoader, libname);
}
synchronized void loadLibrary0(ClassLoader loader, String libname) {
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
}
String libraryName = libname;
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
// It's not necessarily true that the ClassLoader used
// System.mapLibraryName, but the default setup does, and it's
// misleading to say we didn't find "libMyLibrary.so" when we
// actually searched for "liblibMyLibrary.so.so".
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}
String error = doLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
for (String directory : getLibPaths()) {
String candidate = directory + filename;
candidates.add(candidate);
if (IoUtils.canOpenReadOnly(candidate)) {
String error = doLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
private volatile String[] mLibPaths = null;
private String[] getLibPaths() {
if (mLibPaths == null) {
synchronized(this) {
if (mLibPaths == null) {
mLibPaths = initLibPaths();
}
}
}
return mLibPaths;
}
想要更详细的了解加载过程,可以参考这篇博文https://my.oschina.net/wolfcs/blog/129696
2) java层的我们已经写完,现在开始转到native层。native 层的方法命名是有一定的规则的,简单点就是:包名+类名+方法名 由于jni中的点有特殊用处,所以点用_替代 。我java层:包名:aai.along.and.jni 类名:JNIStaticTest 方法名:native_init() 使用javah 生产的nativie方法名是:JNIEXPORT void JNICALL Java_aai_along_and_jni_JNIStaticTest_native_1init (JNIEnv *, jobject); 不对啊,跟刚才说的规则不同,多了JNIEXPORT JNICALL 而且native_1init 比native_init 多了一个1.JNIEXPORT JNICALL 这两个都是jni的标志性,告诉系统这个是jni方法,没什么特殊含义。而native_1init 里面多了一个1确实需要特别注意的,只有当你的方法名、类名、包名中有_后面都需要加1.这是jni的语言规则。很多文章都没讲这个特殊性,是个大坑货。
3)android studio 如果生产native的头文件呢?
1.选择setings
2.选择External Tools
3.选择+ 创建新的工具。
.
4填写新的工具参数
5.代码中使用,在使用这个工具前,先要保证有生成对应的.class文件,执行make project
需要注意点是:参数的填写。
program :$JDKPath$\bin\javah.exe
argument:-d $ModuleFileDir$/src/main/jni -classpath $ModuleSdkPath$\platforms\android-28\android.jar;$ModuleSdkPath$\extras/android/m2repository/com/android/support/appcompat-v7/25.3.1/appcompat-v7-25.3.1-sources.jar; $FileClass$
working directory :$ModuleFileDir$\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\
参数说明: $JDKPath$\bin\javah.exe javah的路径 。-d $ModuleFileDir$/src/main/jni 生成的头文件目录, -classpath $ModuleSdkPath$\platforms\android-28\android.jar;$ModuleSdkPath$\extras/android/m2repository/com/android/support/appcompat-v7/25.3.1/appcompat-v7-25.3.1-sources.jar; 需要引用其他jar包路径。$FileClass$ 需要生成的java文件 $ModuleFileDir$\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\ 此处是表示你生成的class文件所在的目录。
这样我们生成的.h文件如下
#include <jni.h>
#include <android/log.h>
/* Header for class aai_along_and_jni_JNIStaticTest */
#ifndef _Included_aai_along_and_jni_JNIStaticTest
#define _Included_aai_along_and_jni_JNIStaticTest
#ifdef __cplusplus
extern "C" {
#endif
#define LOG_TAG "JNIStaticTest-jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
/*
* Class: aai_along_and_jni_JNIStaticTest
* Method: stringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_aai_along_and_jni_JNIStaticTest_stringFromJNI
(JNIEnv *, jobject);
/*
* Class: aai_along_and_jni_JNIStaticTest
* Method: native_init
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_aai_along_and_jni_JNIStaticTest_native_1init
(JNIEnv *, jobject);
/*
* Class: aai_along_and_jni_JNIStaticTest
* Method: transmitToJNI
* Signature: (Ljava/lang/String;Laai/along/and/jni/ExternClass;)V
*/
JNIEXPORT void JNICALL Java_aai_along_and_jni_JNIStaticTest_transmitToJNI
(JNIEnv *, jobject, jstring, jobject);
#ifdef __cplusplus
}
#endif
#endif
其中如下的代码增加是我为了调试打印log用的。因为native的代码属于c++,上层的logcat就无法抓取到对应的log,调试非常不方面,加了如下的log我们可以比较清楚的看到native 与java层的联动。
#include <android/log.h>
#define LOG_TAG "JNIStaticTest-jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
4)java层调用native层,实现刚才的.h文件的方法即可。参考如下代码。
#include <string>
#include <iostream>
#include <stdio.h>
//#include "jniUtils.h"
#include "aai_along_and_jni_JNIStaticTest.h"
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
//extern "C" JNIEXPORT jstring JNICALL
//extern "C" JNIEXPORT JNICALL
//extern "C" JNIEXPORT JNICALL
int file_size2(const char* filename);
using namespace std;
static const char* const kClassJniTest =
"aai/along/and/jni/JNIStaticTest";
static const char* const kClassJniExtern =
"aai/along/and/jni/ExternClass";
//保存java层的fieldID 和methodID 以方便后续使用
struct fields_t {
jfieldID context;
jfieldID name;
jclass clazz;
jobject obj;//obj的保存一定要使用NewGlobalRef的方法
jmethodID setNameMethodID;
jmethodID getNameMethodID;
jmethodID transimFromJNIMethodID;
}fields;
struct fields_t *Pfield;
jstring JNICALL Java_aai_along_and_jni_JNIStaticTest_stringFromJNI(
JNIEnv* env,
jobject obj ) {
LOGI("JAVA 调用 JNI stringFromJNI");
string hello = "Hello from JNI";
return env->NewStringUTF(hello.c_str());
}
void JNICALL Java_aai_along_and_jni_JNIStaticTest_native_1init
(JNIEnv *env, jobject object){
//printf("Java_aai_along_and_jni_JNIStaticTest_native_1init");
LOGI("JAVA 调用 JNI native_1init");
//初始化 方法 field id
Pfield=&fields;
jclass clazz = env->FindClass(kClassJniTest);//关联native 和java层 获取java层的class
Pfield->clazz=clazz;
if (Pfield->clazz == NULL) {
LOGE("can not find class");
return;
}
//获取java层的方法id 并且保存起来
Pfield->name = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
if (Pfield->name == NULL) {
LOGE("can not find name ID");
return;
}
Pfield->context = env->GetFieldID(clazz, "testContext", "Landroid/content/Context;");
if (Pfield->context == NULL) {
LOGE("can not find context ID");
return;
}
Pfield->setNameMethodID = env->GetMethodID(
clazz,
"setName",
"(Ljava/lang/String;)V");
if (Pfield->setNameMethodID == NULL) {
LOGE("can not find setNameMethodID");
return;
}
Pfield->getNameMethodID = env->GetMethodID(
clazz,
"getName",
"()Ljava/lang/String;");
if (Pfield->getNameMethodID == NULL) {
LOGE("can not find getNameMethodID");
return;
}
Pfield->transimFromJNIMethodID = env->GetMethodID(
clazz,
"transimFromJNI",
"(Ljava/lang/String;Laai/along/and/jni/ExternClass;)Ljava/lang/String;");
if (Pfield->transimFromJNIMethodID == NULL) {
LOGE("can not find transimFromJNIMethodID");
return;
}
}
void JNICALL Java_aai_along_and_jni_JNIStaticTest_transmitToJNI
(JNIEnv * env, jobject thizz,jstring information, jobject obj){
const char *transmitString = env->GetStringUTFChars(information, NULL);
LOGI("JAVA 调用 JNI transmitToJNI transmitString=%s",transmitString);
Pfield->obj=env->NewGlobalRef(thizz);//想持久保存thizz的对象,一定要使用NewGlobalRef 后续也要手动删除
//初始化 field id 获取obj 的name
jclass clazz=env->FindClass(kClassJniExtern);
jfieldID fieldNameID=env->GetFieldID(clazz, "className", "Ljava/lang/String;");
if (fieldNameID == NULL) {
LOGE("can not find className ");
return;
}
jobject objName;
objName=env->GetObjectField(obj,fieldNameID);
jstring jstringName=(jstring)(objName);
const char *charName = env->GetStringUTFChars(jstringName, NULL);
LOGI("JNI 调用 JAVA ExternClass className=%s",charName);
//调用java 层的方法。CallVoidMethod CallObjectMethod
string message = "JNI 调用 JAVA ";
jstring jMessage=env->NewStringUTF(message.c_str());
//通过CallVoidMethod方法,传入之前获取的MethodID 调用java层方法
env->CallVoidMethod(Pfield->obj, Pfield->setNameMethodID, jMessage);
jobject callName= env->CallObjectMethod(Pfield->obj, Pfield->getNameMethodID);
const char *callNameChar = env->GetStringUTFChars((jstring)callName, NULL);
LOGI("JNI 调用 JAVA getName=%s",callNameChar);
jobject calltransim= env->CallObjectMethod(Pfield->obj, Pfield->transimFromJNIMethodID, jMessage,obj);
const char *calltransimChar = env->GetStringUTFChars((jstring)calltransim, NULL);
LOGI("JNI 调用 JAVA transimFromJNI calltransimChar=%s",calltransimChar);
//垃圾回收
env->DeleteLocalRef(callName);
env->DeleteLocalRef(calltransim);
env->DeleteLocalRef(objName);
env->DeleteGlobalRef(Pfield->obj);
}
5)native 如何调用java层呢?
1.java层与native层的关联;
jclass clazz = env->FindClass(kClassJniTest);
2.获取java层的方法ID
Pfield->setNameMethodID = env->GetMethodID(
clazz,
"setName",
"(Ljava/lang/String;)V");
if (Pfield->setNameMethodID == NULL) {
LOGE("can not find setNameMethodID");
return;
}
3.向CallVoidMethod 方法,传入之前获取的setNameMethodID ,以及需要传递的参数值,jMessage
string message = "JNI 调用 JAVA ";
jstring jMessage=env->NewStringUTF(message.c_str());
env->CallVoidMethod(Pfield->obj, Pfield->setNameMethodID, jMessage);
具体的方法传递参数,可以参考jni.h文件。
在第二步中Pfield->setNameMethodID = env->GetMethodID( clazz, "setName", "(Ljava/lang/String;)V"); 各个参数的含义是什么呢?
jni.h 中的函数原型是这样的:
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
{ return functions->GetMethodID(this, clazz, name, sig); }
jclass clazz 很好理解,就是与native关联的java层的类。
const char* name 就是需要调用的方法名
const char* sig sig又是什么鬼?之前没听过,一脸懵逼。这个是jni特有的称呼。它的语法规则是:"(参数类型)返回类型"
参数类型和返回类型的规则需要按jni的方式。
由于我们java层的方法是setName(String name1) string类型,依据规则任何java类的全名:Ljava/lang/String; ;分号不要忘记
这样我们的java层与native层就能联动了,代码上传在最后面。静态注册的方法基本已经讲解完。静态注册的方式有很大的弊端就是编写方法名称太长,太不美观了。所以我觉得还是动态注册最好,最方便。
4.动态注册
java层的代码跟静态方式相同,就不啰嗦了,参考静态注册的代码。直接讲解native层 .cpp文件。
#include <jni.h>
#include <string>
#include <iostream>
#include <stdio.h>
#include <android/log.h>
//#include "JNIHelp.h"
#include <stdlib.h>
//#include "android_runtime/AndroidRuntime.h"
#define LOG_TAG "JNISharedTest-jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
using namespace std;
static const char* const kClassJniTest =
"aai/along/and/jni/JNISharedTest";
static const char* const kClassJniExtern =
"aai/along/and/jni/ExternClass";
//保存java层的fieldID 和methodID 以方便后续使用
struct fields_t {
jfieldID context;
jfieldID name;
jclass clazz;
jobject obj;//obj的保存一定要使用NewGlobalRef的方法
jmethodID setNameMethodID;
jmethodID getNameMethodID;
jmethodID transimFromJNIMethodID;
}fields;
struct fields_t *Pfield;
jstring JNISharedTest_stringFromJNI(JNIEnv* env, jobject obj){
LOGI("JAVA 调用 JNI stringFromJNI");
string hello = "Hello from JNI";
return env->NewStringUTF(hello.c_str());
}
void JNISharedTest_transmitToJNI(JNIEnv * env, jobject thizz,jstring information, jobject obj){
const char *transmitString = env->GetStringUTFChars(information, NULL);
LOGI("JAVA 调用 JNI transmitToJNI transmitString=%s",transmitString);
Pfield->obj=env->NewGlobalRef(thizz);//想持久保存thizz的对象,一定要使用NewGlobalRef 后续也要手动删除
//初始化 field id 获取obj 的name
jclass clazz=env->FindClass(kClassJniExtern);
jfieldID fieldNameID=env->GetFieldID(clazz, "className", "Ljava/lang/String;");
if (fieldNameID == NULL) {
LOGE("can not find className ");
return;
}
jobject objName;
objName=env->GetObjectField(obj,fieldNameID);
jstring jstringName=(jstring)(objName);
const char *charName = env->GetStringUTFChars(jstringName, NULL);
LOGI("JNI 调用 JAVA ExternClass className=%s",charName);
//调用java 层的方法。CallVoidMethod CallObjectMethod
string message = "JNI 调用 JAVA ";
jstring jMessage=env->NewStringUTF(message.c_str());
env->CallVoidMethod(Pfield->obj, Pfield->setNameMethodID, jMessage);
jobject callName= env->CallObjectMethod(Pfield->obj, Pfield->getNameMethodID);
const char *callNameChar = env->GetStringUTFChars((jstring)callName, NULL);
LOGI("JNI 调用 JAVA getName=%s",callNameChar);
jobject calltransim= env->CallObjectMethod(Pfield->obj, Pfield->transimFromJNIMethodID, jMessage,obj);
const char *calltransimChar = env->GetStringUTFChars((jstring)calltransim, NULL);
LOGI("JNI 调用 JAVA transimFromJNI calltransimChar=%s",calltransimChar);
//垃圾回收
env->DeleteLocalRef(callName);
env->DeleteLocalRef(calltransim);
env->DeleteLocalRef(objName);
env->DeleteGlobalRef(Pfield->obj);
}
void JNISharedTest_native_init(JNIEnv* env,jobject thizz){
LOGI("JAVA 调用 JNI native_1init");
//初始化 方法 field id
Pfield=&fields;
jclass clazz = env->FindClass(kClassJniTest);
Pfield->clazz=clazz;
if (Pfield->clazz == NULL) {
LOGE("can not find class");
return;
}
Pfield->name = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
if (Pfield->name == NULL) {
LOGE("can not find name ID");
return;
}
Pfield->context = env->GetFieldID(clazz, "testContext", "Landroid/content/Context;");
if (Pfield->context == NULL) {
LOGE("can not find context ID");
return;
}
Pfield->setNameMethodID = env->GetMethodID(
clazz,
"setName",
"(Ljava/lang/String;)V");
if (Pfield->setNameMethodID == NULL) {
LOGE("can not find setNameMethodID");
return;
}
Pfield->getNameMethodID = env->GetMethodID(
clazz,
"getName",
"()Ljava/lang/String;");
if (Pfield->getNameMethodID == NULL) {
LOGE("can not find getNameMethodID");
return;
}
Pfield->transimFromJNIMethodID = env->GetMethodID(
clazz,
"transimFromJNI",
"(Ljava/lang/String;Laai/along/and/jni/ExternClass;)Ljava/lang/String;");
if (Pfield->transimFromJNIMethodID == NULL) {
LOGE("can not find transimFromJNIMethodID");
return;
}
}
static const JNINativeMethod gMethods[] = {
{
"native_init",
"()V",
(void *)JNISharedTest_native_init
},
{
"transmitToJNI",
"(Ljava/lang/String;Laai/along/and/jni/ExternClass;)V",
(void *)JNISharedTest_transmitToJNI
},
{
"stringFromJNI",
"()Ljava/lang/String;",
(void *)JNISharedTest_stringFromJNI
},
};
static int registerNativeMethods(JNIEnv* env
, const char* className
, const JNINativeMethod* gMethod, int numMethods) {
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
LOGI(" JNI 注册 失败,没发现此类");
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethod, numMethods) < 0) {
LOGI(" JNI 注册 失败");
return JNI_FALSE;
}
LOGI(" JNI 注册 成功");
return JNI_TRUE;
}
static int register_along_jni(JNIEnv *env)
{
LOGI(" JNI 注册");
/// return AndroidRuntime::registerNativeMethods(env,
// kClassJniTest, gMethods, NELEM(gMethods));
return registerNativeMethods(env, kClassJniTest, gMethods,
NELEM(gMethods));
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
LOGI(" JNI 加载");
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGE("ERROR: JNI 版本错误");
return JNI_ERR;
}
if (register_along_jni(env) == -1) {
LOGE("ERROR: JNI_OnLoad 加载失败");
return JNI_ERR;
}
result = JNI_VERSION_1_6;
return result;
}
1)静态方法是根据方法名称来关联native与java的。那么动态注册方式又是通过什么方式关联native层和java层呢?
通过
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */)函数,在java中执行
static {
System.loadLibrary("jni-shared");
}系统会调用JNI_OnLoad方法,所以我们动态关联java层,只需要重写这个方法就可以。
这里需要特别说明一下,如果是在linux 环境编译的话,可以去掉JNIEXPORT JNICALL 这两个关键字。
2) 注册native方法
if (env->RegisterNatives(clazz, gMethod, numMethods) < 0) {
LOGI(" JNI 注册 失败");
return JNI_FALSE;
}
方法原型是:
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
jint nMethods)
{ return functions->RegisterNatives(this, clazz, methods, nMethods); }
jclass clazz java的类,跟之前静态注册方法一样。
jclass clazz = env->FindClass(kClassJniTest); kClassJniTest:类名,static const char* const kClassJniTest = "aai/along/and/jni/JNISharedTest";
const JNINativeMethod* methods :
static const JNINativeMethod gMethods[] = {
{
"native_init", //java 层的方法名
"()V", //签名 签名的规则跟静态注册方法相同。
(void *)JNISharedTest_native_init //对应的native 层的方法。方法名称你可以随便取。
},
{
"transmitToJNI",
"(Ljava/lang/String;Laai/along/and/jni/ExternClass;)V",
(void *)JNISharedTest_transmitToJNI
},
{
"stringFromJNI",
"()Ljava/lang/String;",
(void *)JNISharedTest_stringFromJNI
},
};
jint nMethods :gMethods数组有多少个JNINativeMethod 结构体,JNINativeMethod 结构体如下
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
为了比较方便的获取gMethods中的JNINativeMethod个数,使用如下的方法
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) // x用gMethods替换。
这里需要特别说明一下:如果是在linux环境
#include "android_runtime/AndroidRuntime.h"
#include "JNIHelp.h"
增加这两个头文件,可以使用如下方法注册
AndroidRuntime::registerNativeMethods(env,kClassMediaScanner, gMethods, NELEM(gMethods));参数跟RegisterNatives方法一样
至此动态注册方法讲解完毕,是不是觉得很简单。只需要两步就完成动态注册,所以以后还是建议使用动态注册。
静态注册代码连接:https://download.csdn.net/download/bill_xiao/11097666
百度网盘:链接:https://pan.baidu.com/s/19Fbvp7HvukwiKuXyv-cOjQ
提取码:ohce
动态注册代码连接:https://download.csdn.net/download/bill_xiao/11097670
百度网盘:链接:https://pan.baidu.com/s/1XakbRZZAslHLp9rm28bkmw
提取码:20ur