目录
背景
博主之前做的日志组件的结构是java和c++相结合实现。将日志的加密,压缩,文件写入等对性能比较敏感的模块放在c++层实现,而将日志的格式化,日志脱敏等功能放在java层实现。这就会涉及到java和c++的交互,即jni。
jni注册主要分为静态注册和动态注册,其中静态注册通过方法名进行绑定,方便调试,编辑器可以直接跳转,但是方法名比较冗长,不够灵活,适合接口不多的场景;而动态注册可以直接在代码里面进行绑定,实现更加灵活,但是不方便调试,编辑器不能直接跳转。
静态注册
1.引入so文件和native方法
首先定义一个java类,然后随便命名一个so库,如JniTest,然后定义两个native方法。
public class JniTestInterface {
static {
System.loadLibrary("JniTest");
}
private static JniTestInterface instance = new JniTestInterface();
public static void set(String msg) {
instance.jniSet(msg);
}
public static String get() {
return instance.jniGet();
}
public native void jniSet(String msg);
public native String jniGet();
}
2.编译生成jni头文件
在java文件夹下执行命令javac com/example/jnitest/JniTestInterface.java得到class文件
执行命令 javah com.example.jnitest.JniTestInterface 得到头文件
3.实现jni方法
在main文件夹下创建cpp文件夹,将头文件重命名为jniTest.h,并移动到cpp文件夹下
接着新建jniTest.cpp文件,并实现头文件当中的方法
#include "jniTest.h"
#include <stdio.h>
extern "C" JNIEXPORT void JNICALL Java_com_example_jnitest_JniTestInterface_jniSet
(JNIEnv * env, jobject, jstring string){
printf("调用了setStr方法哟!");
char* str = (char *)env->GetStringUTFChars(string,NULL);
printf("%s\n",str);
env->ReleaseStringUTFChars(string,str);
}
extern "C" JNIEXPORT jstring JNICALL Java_com_example_jnitest_JniTestInterface_jniGet
(JNIEnv * env, jobject){
printf("调用了getStr方法哟!");
return env->NewStringUTF("hello from JNI !");
}
4.实现CMakeLists.txt
编辑CMakeLists.txt文件
# 项目信息
project(JniHelper)
cmake_minimum_required(VERSION 3.4.1)
# 源文件
set(
SRC_FILES
${CMAKE_SOURCE_DIR}/jniTest.h
${CMAKE_SOURCE_DIR}/jniTest.cpp
)
# 加入到动态库当中
add_library(
JniTest
SHARED
${SRC_FILES}
)
编辑build.gradle文件
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
}
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a' // <- only the supported ones
}
}
...
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.6.0"
}
}
}
动态注册
我们可以看到,静态注册通过方法名来关联java和c++的代码,c++的方法名会非常的长,当项目比较大,接口比较多时,就不方便进行管理,这时可以通过动态注册来解决。
接着来看下动态注册是怎么做的吧
1.定义对应交互的java方法
我们将新的动态库命名为JniDynamicTest
public class JniInterface {
static {
System.loadLibrary("JniDynamicTest");
}
private static JniInterface instance = new JniInterface();
public static void set(String msg) {
instance.jniDynamicSet(msg);
}
public static String get() {
return instance.jniDynamicGet();
}
public native void jniDynamicSet(String msg);
public native String jniDynamicGet();
}
2.引入c++代码和CMakeLists.txt文件
在main文件夹下新建cpp目录,并新建jniTest.cpp和CMakeLists.txt文件
3.编辑c++文件
c++的逻辑比较简单,主要分成以下几个步骤
1.定义方法映射表,完成java方法和c++方法的映射关系
2.注册所有的方法
3.jni_onload时,加载所有的注册方法
#include <jni.h>
#include <string>
#ifndef SIZEOF
#define SIZEOF(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif
extern "C" void jniSet(JNIEnv *env, jobject, jstring string) {
printf("调用了setStr方法哟!");
char *str = (char *) env->GetStringUTFChars(string, NULL);
printf("%s\n", str);
env->ReleaseStringUTFChars(string, str);
}
extern "C" jstring jniGet(JNIEnv *env, jobject) {
printf("调用了getStr方法哟!");
return env->NewStringUTF("hello Dynamic from JNI !");
}
/**
* 1.定义方法映射表
*/
static const JNINativeMethod jniMethods[] = {
{"jniDynamicSet", "(Ljava/lang/String;)V", (void *) jniSet},
{"jniDynamicGet", "()Ljava/lang/String;", (void *) jniGet}
};
/**
* 2.注册所有方法
*/
static int registerAllMethods(JNIEnv *env) {
const char *className = "com/example/jnidynamic/JniInterface";//对应的java类
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, jniMethods, SIZEOF(jniMethods)) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
/**
* 3.jni_onload时,加载所有的注册方法
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
if (!registerAllMethods(env)) {//注册所有方法
return -1;
}
return JNI_VERSION_1_4;
}
4.实现CMakeLists.txt
和静态绑定的CMakeLists.txt大同小异
# 项目信息
project(JniHelper)
cmake_minimum_required(VERSION 3.4.1)
# 源文件
set(
SRC_FILES
${CMAKE_SOURCE_DIR}/jniTest.cpp
)
# 加入到动态库当中
add_library(
JniDynamicTest
SHARED
${SRC_FILES}
)
build.gradle文件同静态绑定
参考:https://www.jianshu.com/p/54da9c2c3403