JNI(Java Native Interface)是一套编程接口, 用来实现 Java 代码与本地的 C/C++ 代码进行交互,其有两种注册方式:静态注册和动态注册。
静态注册
- 理解和使用方式简单, 使用相关工具按流程操作就行, 编码出错率低
- JNI 层函数名特别长,且当需要更改类名,包名或者方法时, 需要按照之前方法重新生成头文件, 灵活性不高
- 初次调用 native 函数时要根据函数名搜索对应的 JNI 层函数来建立联系,效率较低
动态注册
- 灵活性高, 更改类名、包名或方法时, 只需对更改模块进行少量修改, 效率高
- 使用用函数映射表来调用相应的函数,效率较高(Android 源码使用的动态注册)
- 理解和使用方式稍复杂, 容易搞错签名、方法, 导致注册失败
本文主要讲动态注册,静态注册见我另外一篇博客《JNI 传递和返回基本参数》
动态注册
动态注册基本思想是:当在 Java 层调用 System.loadLibrary(libName) 的时候,底层会调用 JNI_OnLoad () 函数,在这个函数中通过 NDK 提供的 RegisterNatives() 方法来将 C/C++ 方法和 Java 方法注册映射起来,后续从 Java 层调用声明为 native 的方法时就会根据映射表找到对应的 C/C++ 方法。注册整体流程如下:
1、编写 Java 层的相关 Native 方法,如
package com.alan.jniexamples.dynamic;
/**
* Author: AlanWang4523.
* Date: 19/5/18 11:14.
* Mail: alanwang4523@gmail.com
*/
public class NativeDynamic {
static {
System.loadLibrary("jni_example");
}
public native int nativeSetBasicArgs(int iArg, float fArg, long lArg, boolean bArg);
public native void nativeSetStringArgs(String str);
}
2、编写 C/C++ 代码,将 Java 方法和 C/C++ 方法通过签名信息对应起来,如:
static int JNISetBasicArgs(JNIEnv *env, jobject obj,
jint iArg, jfloat fArg, jlong lArg, jboolean bArg) {
LOGD("NativeDynamicJNI", "JNISetBasicArgs()-->>iArg = %d, fArg = %f, lArg = %ld, bArg = %d\n",
iArg, fArg, lArg, bArg);
return 0;
}
static void JNISetStringArgs(JNIEnv *env, jobject obj,jstring strArg) {
jboolean iscopy;
// 这里不能直接使用 strArg,需要将其通过 GetStringUTFChars 接口将其转成 UTF-8 的 字符串的指针
char *cStr = (char *) env->GetStringUTFChars(strArg, &iscopy);
LOGD("NativeDynamicJNI", "JNISetStringArgs()--->cStr = %s", cStr);
// 最后需要释放,否则可能导致内存泄漏
env->ReleaseStringUTFChars(strArg, cStr);
}
// Java 层声明 native 方法的类的全路径
static const char *className = "com/alan/jniexamples/dynamic/NativeDynamic";
static JNINativeMethod gJni_Methods[] = {
// 如: nativeSetBasicArgs 是 Java 声明的方法
// "(IFJZ)I" 是函数签名
// JNISetBasicArgs 是 C/C++ 声明的方法
{"nativeSetBasicArgs", "(IFJZ)I", (void*)JNISetBasicArgs},
{"nativeSetStringArgs", "(Ljava/lang/String;)V", (void*)JNISetStringArgs},
};
3、在 JNI_OnLoad () 函数中通过 NDK 提供的 RegisterNatives() 方法来将 C/C++ 方法和 Java 方法注册映射起来:
jint JNI_OnLoad(JavaVM* jvm, void* reserved){
JNIEnv* env = NULL;
jint result = -1;
if (jvm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
}
jniRegisterNativeMethods(env, className, gJni_Methods, NELEM(gJni_Methods));
return JNI_VERSION_1_4;
}
jniRegisterNativeMethods 方法如下:
int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) {
LOGD("JNIHelper", "Start registering %s native methods.\n", className);
jclass clazz = (env)->FindClass(className);
if (clazz == NULL) {
LOGE("JNIHelper", "Native registration unable to find class '%s'.\n", className);
return -1;
}
int result = 0;
if ((env)->RegisterNatives(clazz, gMethods, numMethods) < 0) {
LOGE("JNIHelper", "RegisterNatives failed for '%s'.\n", className);
result = -1;
}
(env)->DeleteLocalRef(clazz);
LOGD("JNIHelper", "Registering %s native methods success.\n", className);
return result;
}
4、测试调用:
private void testDynamicJNIs() {
NativeDynamic nativeDynamic = new NativeDynamic();
nativeDynamic.nativeSetBasicArgs(2, 3.2f, 1000L, true);
nativeDynamic.nativeSetStringArgs("Hello Alan From Java!");
}
输出:
05-18 21:13:28.891 8156-8156/com.alan.jniexamples D/NativeDynamicJNI: JNISetBasicArgs()-->>iArg = 2, fArg = 3.200000, lArg = 1000, bArg = 1
05-18 21:13:28.891 8156-8156/com.alan.jniexamples D/NativeDynamicJNI: JNISetStringArgs()--->cStr = Hello Alan From Java!
完整代码见 GitHub https://github.com/alanwang4523/JNIExamples
看一个比较复杂的完成 demo
/*
* Copyright (c) 2019-present AlanWang4523 <alanwang4523@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Author: AlanWang4523.
* Date: 19/5/19 22:22.
* Mail: alanwang4523@gmail.com
*/
#include <jni.h>
#include <cstring>
#include "common/JNIHelper.h"
#include "common/Log.h"
#include "business/MediaServer.h"
struct fields_t {
jfieldID context;
};
static fields_t gFields;
// Java 层声明 native 方法的类的全路径
static const char *className = "com/alan/jniexamples/dynamic/MediaServerDynamic";
static int MediaServer_native_init(JNIEnv *env) {
LOGD("MediaServerDynamicJNI", "MediaServer_native_init()-->>\n");
jclass clazz = (env)->FindClass(className);
if (clazz == NULL) {
LOGE("MediaServerDynamicJNI", "Unable to find class\n");
return -1;
}
gFields.context = env->GetFieldID(clazz, "mNativeContext", "J");
return 0;
}
static void MediaServer_native_create(JNIEnv *env, jobject obj,jstring strArg) {
jboolean isCopy;
// 这里不能直接使用 strArg,需要将其通过 GetStringUTFChars 接口将其转成 UTF-8 的 字符串的指针
char *cStr = (char *) env->GetStringUTFChars(strArg, &isCopy);
// 创建底层实例,并与上层实例关联,即将底层实例的地址赋值给上层实例的成员变量 mNativeContext
MediaServer * mediaServer = new MediaServer(cStr);
env->SetLongField(obj, gFields.context, (jlong)mediaServer);
// 最后需要释放,否则可能导致内存泄漏
env->ReleaseStringUTFChars(strArg, cStr);
}
static int MediaServer_native_config(JNIEnv *env, jobject obj, jint type) {
// 通过上层实例的成员变量获取对应的底层实例
MediaServer * mediaServer = (MediaServer *)env->GetLongField(obj, gFields.context);
if (mediaServer) {
return mediaServer->config(type);
} else {
return ERROR_PARAM;
}
}
static int MediaServer_native_setMediaParam(JNIEnv *env, jobject obj, jobject jniParamObj) {
// 通过上层实例的成员变量获取对应的底层实例
MediaServer * mediaServer = (MediaServer *)env->GetLongField(obj, gFields.context);
if (!mediaServer) {
return ERROR_PARAM;
}
int errCode = SUCCESS;
jclass jclsParamClass = NULL;
jmethodID jmid = NULL;
jstring jsFilePath = NULL;
MediaParam mediaParam;
mediaParam.path = NULL;
if ((NULL == env) || (jniParamObj == NULL)) {
errCode = ERROR_PARAM;
goto exit;
}
jclsParamClass = env->GetObjectClass(jniParamObj);
if (NULL == jclsParamClass) {
errCode = ERROR_PARAM;
goto exit;
}
//获取文件路径
jmid = env->GetMethodID(jclsParamClass, "getPath", "()Ljava/lang/String;");
if (NULL == jmid) {
errCode = ERROR_PARAM;
goto exit;
}
jsFilePath = (jstring) env->CallObjectMethod(jniParamObj, jmid);
if (NULL != jsFilePath) {
mediaParam.path = env->GetStringUTFChars(jsFilePath, NULL);
}
jmid = env->GetMethodID(jclsParamClass, "getStartTime", "()J");
if (NULL == jmid) {
errCode = ERROR_PARAM;
goto exit;
}
mediaParam.start_time = env->CallLongMethod(jniParamObj, jmid);
jmid = env->GetMethodID(jclsParamClass, "getEndTime", "()J");
if (NULL == jmid) {
errCode = ERROR_PARAM;
goto exit;
}
mediaParam.end_time = env->CallLongMethod(jniParamObj, jmid);
jmid = env->GetMethodID(jclsParamClass, "isEnableLoop", "()Z");
if (NULL == jmid) {
errCode = ERROR_PARAM;
goto exit;
}
mediaParam.enable_loop = env->CallBooleanMethod(jniParamObj, jmid);
errCode = mediaServer->setMediaParam(&mediaParam);
exit:
if (jclsParamClass) {
env->DeleteLocalRef(jclsParamClass);
}
//释放文件路径相关的内存
if (mediaParam.path) {
env->ReleaseStringUTFChars(jsFilePath, mediaParam.path);
}
if (jsFilePath) {
env->DeleteLocalRef(jsFilePath);
}
return errCode;
}
static void MediaServer_native_setCallback(JNIEnv *env, jobject obj, jobject jCallback) {
// 通过上层实例的成员变量获取对应的底层实例
MediaServer *mediaServer = (MediaServer *) env->GetLongField(obj, gFields.context);
if (!mediaServer) {
return;
}
memset(mediaServer->getCallbackContext(), 0, sizeof(CallbackContext));
env->GetJavaVM(&mediaServer->getCallbackContext()->jvm);
mediaServer->getCallbackContext()->jniCallbackObj = env->NewGlobalRef(jCallback);
jclass cbkClass = env->GetObjectClass(jCallback);
mediaServer->getCallbackContext()->jmidGetImageTexture = env->GetMethodID(cbkClass, "getImageTexture", "(Ljava/lang/String;)I");
mediaServer->getCallbackContext()->jmidOnError = env->GetMethodID(cbkClass, "onError", "(I)V");
}
static jstring MediaServer_native_getName(JNIEnv *env, jobject obj) {
MediaServer * mediaServer = (MediaServer *)env->GetLongField(obj, gFields.context);
if (mediaServer) {
const char *c_name = mediaServer->getName().c_str();
return env->NewStringUTF(c_name);
} else {
return NULL;
}
}
static jobject MediaServer_native_getMediaInfo(JNIEnv *env, jobject obj) {
MediaServer * mediaServer = (MediaServer *)env->GetLongField(obj, gFields.context);
if (!mediaServer) {
return NULL;
}
// 找到要创建的 Java 类
jclass jclMediaInfo = env->FindClass("com/alan/jniexamples/common/MediaInfo");
// 获取 Java 类的构造函数及相关方法
jmethodID jmidConstructor = env->GetMethodID(jclMediaInfo, "<init>", "()V");
jmethodID jmidSetSampleRate = env->GetMethodID(jclMediaInfo, "setSampleRate", "(I)V");
jmethodID jmidSetChanelCount = env->GetMethodID(jclMediaInfo, "setChanelCount", "(I)V");
jmethodID jmidSetDuration = env->GetMethodID(jclMediaInfo, "setDuration", "(J)V");
// 通过构造函数创建 Java 实例
jobject jobjMediaInfo = env->NewObject(jclMediaInfo, jmidConstructor);
// 从 C/C++ 的业务实例获取 Java 层要获取的信息
pMediaInfo mediaInfo = mediaServer->getMediaInfo();
// 通过相应的方法给创建的 Java 实例赋值
env->CallVoidMethod(jobjMediaInfo, jmidSetSampleRate, mediaInfo->sample_rate);
env->CallVoidMethod(jobjMediaInfo, jmidSetChanelCount, mediaInfo->chanel_count);
env->CallVoidMethod(jobjMediaInfo, jmidSetDuration, mediaInfo->duration);
return jobjMediaInfo;
}
static void MediaServer_native_release(JNIEnv *env, jobject obj, jint type) {
// 通过上层实例的成员变量获取对应的底层实例
MediaServer * mediaServer = (MediaServer *)env->GetLongField(obj, gFields.context);
if (mediaServer) {
delete mediaServer;
}
env->SetLongField(obj, gFields.context, (jlong)0);
}
static JNINativeMethod gJni_Methods[] = {
{"native_init", "()V", (void*)MediaServer_native_init},
{"native_create", "(Ljava/lang/String;)V", (void*)MediaServer_native_create},
{"native_config", "(I)V", (void*)MediaServer_native_config},
{"native_setMediaParam", "(Lcom/alan/jniexamples/common/MediaParam;)I", (void*)MediaServer_native_setMediaParam},
{"native_setCallback", "(Lcom/alan/jniexamples/common/MediaServerCallback;)V", (void*)MediaServer_native_setCallback},
{"native_getName", "()Ljava/lang/String;", (void*)MediaServer_native_getName},
{"native_getMediaInfo", "()Lcom/alan/jniexamples/common/MediaInfo;", (void*)MediaServer_native_getMediaInfo},
{"native_release", "()V", (void*)MediaServer_native_release},
};
int register_MediaServerDynamic(JNIEnv* env) {
return jniRegisterNativeMethods(env, className, gJni_Methods, NELEM(gJni_Methods));
}