JNI是什么?

JNI是Java Native Interface的缩写,中文名为JAVA本地调用。它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。Java是支持调用C/C++代码的,不过不能直接调用,因此需要一个中间层来进行转换、翻译,这就是JNI(Java Native Interface)的意思,JNI的作用就是粘合Java代码和C++代码。

JNIEnv类型

JNIEnv类型实际上代表了java的环境,通过JNIEnv *指针就可以对java端的代码进行操作。

jclass类型

为了能够在c/c++中使用java类,jni.h头文件中专门定义了jclass类型来表示java中的class类:

  • jclass FindClass(const char* name)
    • 通过类的名称获取jcalss,例如jclass str = env->FindClass (“java/lang/String”);获取java中String对象的class对象
  • jclass GetObjectClass(jobject obj)
    • 通过对象的实例获取jclass
  • jclass GetSuperClass(jclass obj) 通过jclass获取父类的jclass对象
  • jclass GetMethodID(jclass clazz, const char* name, const char* sig)
    •  参数一:jclass 查找到的java类
    •  参数二:const char* name 方法名
    •  参数三:const char* sig 方法的返回值

JNI注册方法

​ 静态注册和动态注册。区别是效率。静态注册,每次使用native方法时,都要去寻找;而动态注册,由于有张表的存在,因此查找效率高。静态注册多用于NDK开发,而动态注册多用于Framework开发。不管是静态注册方法,还是动态注册方法,都需要将c文件编译成平台所需要的库。

静态注册

  1. 定义
    通过 JNIEXPORT 和 JNICALL 两个宏定义声明,在虚拟机加载 so 时发现上面两个宏定义的函数时就会链接到对应的 native 方法。

  2. 对应规则
    Java + 包名 + 类名 + 方法名

    其中使用下划线将每部分隔开,包名也使用下划线隔开,如果名称中本来就包含下划线,将使用下划线加数字替换。

    示例(包名:com.jane.jnidemo,类名MainActivity):

    通过javah命令生成JNI的头文件

    1
    javah -jni 包名+类名

    会生成一个.h的头文件,然后新建一个文件夹家jni,把它放入里面

    1
    2
    3
    4
    5
    6
    7
    8
    // Java native method,该方法具体实现由c/c++代码完成
    public native String stringFromJNI();


    // JNI method
    JNIEXPORT jstring JNICALL
    Java_com_jane_jnidemo_MainActivity_stringFromJNI( JNIEnv *env, jobject instance);

动态注册

  1. 动态注册是在JNi层实现的,JAVA层不需要关心,因为在system.load时就会去调用JNI_OnLoad,有就注册,没有就不注册。动态注册的原理:JNI 允许我们提供一个函数映射表,注册给 JVM,这样 JVM 就可以用函数映射表来调用相应的函数, 而不必通过函数名来查找相关函数(这个查找效率很低,函数名超级长)流程更加清晰可控,效率更高.。

  2. 实现流程:
    1、利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系.
    2、实现 JNI_OnLoad 方法,在加载动态库后,执行动态注册.
    3、调用 FindClass 方法,获取 java 对象.
    4、调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册.

  3. 动态注册的关键字是两个:
    1、JNI_OnLoad()方法,这个是载入Jni库后调用的第一个方法,在这里可以将方法对应表注册给JNI环境
    2、JNINativeMethod结构,这个结构是将jni层的方法映射到Java端方法的关键,name:JNI层的方法名

signature:Java层的方法签名 fnPtr:JNI层的函数指针,其定义如下:

1
2
3
4
5
typedefstruct{
constchar* name;
constchar* signature;
void* fnPtr;
}JNINativeMethod;

java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends AppCompatActivity {

// 加载so
static {
System.loadLibrary("native-lib");
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
tv.setText(getString());
}

/**
* 定义native方法
*/
public native String getString();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <jni.h>
#include <string>
/**
* extern "C" :主要作用就是为了能够正确实现C++代码调用其他C语言代码
* JNIEXPORT,JNICALL :告诉虚拟机,这是jni函数
*/
extern "C"
JNIEXPORT jstring JNICALL
native_getString(JNIEnv *env, jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
/**
* 对应java类的全路径名,.用/代替
*/
const char *classPathName = "com/chenpeng/registernativemethoddemo/MainActivity";

/**
* JNINativeMethod 结构体的数组
* 结构体参数1:对应java类总的native方法
* 结构体参数2:对应java类总的native方法的描述信息,用javap -s xxxx.class 查看
* 结构体参数3:c/c++ 种对应的方法名
*/
JNINativeMethod method[] = {{"getString", "()Ljava/lang/String;", (void *) native_getString}};

/**
* 该函数定义在jni.h头文件中,System.loadLibrary()时会调用JNI_OnLoad()函数
*/
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
//定义 JNIEnv 指针
JNIEnv *env = NULL;
//获取 JNIEnv
vm->GetEnv((void **) &env, JNI_VERSION_1_6);
//获取对应的java类
jclass clazz = env->FindClass(classPathName);
//注册native方法
env->RegisterNatives(clazz, method, 1);
//返回Jni 的版本
return JNI_VERSION_1_6;
}



参考实例:

java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package cn.wjdiankong.encryptdemo;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class MainActivity extends Activity {

static {
System.loadLibrary("encrypt");
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean res = isEquals("123456");
Log.i("jw", "res:" + res);
}
});

}

private native boolean isEquals(String str);

}

头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class cn_wjdiankong_encryptdemo_MainActivity */

#ifndef _Included_cn_wjdiankong_encryptdemo_MainActivity
#define _Included_cn_wjdiankong_encryptdemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: cn_wjdiankong_encryptdemo_MainActivity
* Method: isEquals
* Signature: (Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_cn_wjdiankong_encryptdemo_MainActivity_isEquals
(JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

c文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
// Created by jiangwei1-g on 2016/5/23.
#include "encrypt.h"
#include "cn_wjdiankong_encryptdemo_MainActivity.h"
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

char key_src[] = {'z','y','t','y','r','T','R','A','*','B','n','i','q','C','P','p','V','s'};

int is_number(const char* src){
if(src == NULL){
return 0;
}
while(*src != '\0'){
if(*src < 48 || *src > 57){
return 0;
}
src++;
}
return 1;
}

char * get_encrypt_str(const char* src){
if(src == NULL){
return NULL;
}
int len = strlen(src);
len++;
char *dest = (char*) malloc(len * sizeof(char));
char *head = dest;
char *tmp = src;
int i=0;
int key_len = 18;
for(;i<len;i++){
int index = (*tmp) - 48;
if(index == 0){
index = 1;
}
*dest = key_src[key_len-index];
tmp++;
dest++;
}
dest++;
*dest = '\0';
return head;
}

JNIEXPORT jboolean JNICALL jiangwei
(JNIEnv * env, jobject obj, jstring str)
{

LOGD("JNIEnv1:%p", env);

const char *strAry = (*env)->GetStringUTFChars(env, str, 0);
int len = strlen(strAry);
char* dest = (char*)malloc(len);
strcpy(dest, strAry);

int number = is_number(strAry);
if(number == 0){
return 0;
}

char* encry_str = get_encrypt_str(strAry);
const char* pas = "ssBCqpBssP";
int result = strcmp(pas, encry_str);

(*env)->ReleaseStringUTFChars(env, str, strAry);

if(result == 0){
return 1;
}else{
return 0;
}

}

pthread_t t_id;

int getnumberfor_str(char* str){
if(str == NULL){
return -1;
}
char result[20];
int count = 0;
while(*str != '\0'){
if(*str >= 48 && *str <= 57){
result[count] = *str;
count++;
}
str++;
}
int val = atoi(result);
return val;
}

void thread_fuction() {
int pid = getpid();
char file_name[20] = {'\0'};
sprintf(file_name, "/proc/%d/status",pid);
char linestr[256];
int i=0, traceid;
FILE *fp;
while(1){
i = 0;
fp = fopen(file_name,"r");
if(fp == NULL){
break;
}
while(!feof(fp)){
fgets(linestr, 256, fp);
if(i == 5){
traceid = getnumberfor_str(linestr);
if(traceid > 0){
LOGD("I was be traced...trace pid:%d",traceid);
exit(0);
}
break;
}
i++;
}
fclose(fp);
sleep(5);
}

}

void create_thread_check_traceid(){
int err = pthread_create(&t_id,NULL, thread_fuction, NULL);
if(err != 0 ) {
LOGD("create thread fail: %s\n",strerror(err));
}
}

const char *app_signature= "3082030d308201f5a00302010202044054662e300d06092a864886f70d01010b05003037310b30090603550406130255533110300e060355040a1307416e64726f6964311630140603550403130d416e64726f6964204465627567301e170d3136303432303038323733395a170d3436303431333038323733395a3037310b30090603550406130255533110300e060355040a1307416e64726f6964311630140603550403130d416e64726f696420446562756730820122300d06092a864886f70d01010105000382010f003082010a028201010097682c11f190cf5a36feb8adf72a6d3a44e4cf5eb82527ebf396ffad13055ca59b6ba835d4b1a3e3ecc23d39bd1b5b19471e0d3024495b6d97c7a6aa57fe4156593f47af5444e973d19e6213489982e5943534db51315dddb7485f8ffc53e6e8b418394bace31cdcee2da397626cdbe30fe682db1a6b4a56718011f8aa391d0ff7917fc15007bb83ab40b98123cd89a28aeb4c6d2616e3cb91ec1e405cf05880172a3b7db7a3c7030238d2df21d9cfdefc24b6bc526c40b6f5755ce79fb5af778a0fec08e2399d98bcd7ae75c297ecf5e8759aa1839396a8031ac2a93631e4d02cbaabda78594a2d34384404690a1855189ea7dea10233805c2b829d71e5e30f0203010001a321301f301d0603551d0e041604147e68d92245a6b11d2aa611cb6e2f3331154523e8300d06092a864886f70d01010b05000382010100366e1b975c4235f195f7ccffe6c2618e2b9926792bc30fddd8b9c20bb4546684c48c88be8b2af3c8bb24815c6e83e94afdee35d173e7cfab204d0ea14a22df36e91a3fb5ffb7ababe978832039b0fadfcd0d7960b8fccc724ba7309b2c4c967bfb40fbd3f3265be23813d632cdca365782fcc61917229ce12c9e9c05ab61589aceff0de412e2cd985239859a1f1904841b730b5fe7a46905ba6c3dc0d927507baafde4398aecf0827ce0beb6f85a6e4c3f64fdc788220ff543d619ebeb2e15e1f9a6944ea04aee933271a02510679b1d2578edf3894832ed70b1039b0ddbaf0b1c7077c5fffd39daf0fb38a46fec384ee0b237855fe66a35cf46ebbf5cf919a0";
int equal_sign(JNIEnv* env){

//调用Java层的Utils中的获取签名的方法
char* className = "cn/wjdiankong/encryptdemo/Utils";
jclass clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
LOGD("not find class '%s'", className);
return 1;
}

LOGD("class name:%p", clazz);

jmethodID method = (*env)->GetStaticMethodID(env, clazz, "getSignature", "()Ljava/lang/String;");
if(method == NULL){
LOGD("not find method '%s'", method);
return 1;
}

jstring obj = (jstring)(*env)->CallStaticObjectMethod(env, clazz,method);
if(obj == NULL){
LOGD("method invoke error:%p", obj);
return 1;
}

const char *strAry = (*env)->GetStringUTFChars(env, obj, 0);
int cmpval = strcmp(strAry, app_signature);
if(cmpval == 0){
return 1;
}

(*env)->ReleaseStringUTFChars(env, obj, strAry);

return 0;

}

//定义目标类名称
static const char *className = "cn/wjdiankong/encryptdemo/MainActivity";

//定义方法隐射关系
static JNINativeMethod methods[] = {
{"isEquals", "(Ljava/lang/String;)Z", (void*)*jiangwei},
};

jint JNI_OnLoad(JavaVM* vm, void* reserved){
LOGD("JNI on load...");

//检测自己有没有被trace
create_thread_check_traceid();

//声明变量
jint result = JNI_ERR;
JNIEnv* env = NULL;
jclass clazz;
int methodsLenght;

//获取JNI环境对象
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGD("ERROR: GetEnv failed\n");
return JNI_ERR;
}

LOGD("JNIEnv:%p", env);
LOGD("start equal signature...");
int check_sign = equal_sign(env);
LOGD("check_sign:%d", check_sign);
if(check_sign == 0){
exit(0);
}

//注册本地方法.Load 目标类
clazz = (*env)->FindClass(env,className);
if (clazz == NULL) {
LOGD("Native registration unable to find class '%s'", className);
return JNI_ERR;
}

//建立方法隐射关系
//取得方法长度
methodsLenght = sizeof(methods) / sizeof(methods[0]);
if ((*env)->RegisterNatives(env,clazz, methods, methodsLenght) < 0) {
LOGD("RegisterNatives failed for '%s'", className);
return JNI_ERR;
}

result = JNI_VERSION_1_4;
return result;
}

//onUnLoad方法,在JNI组件被释放时调用
void JNI_OnUnload(JavaVM* vm, void* reserved){
LOGD("JNI unload...");
}

附链接:

https://sec.mrfan.xyz/2019/08/19/JNI%E8%B0%83%E7%94%A8%E5%92%8C%E5%8A%A8%E6%80%81%E6%B3%A8%E5%86%8C%E6%8E%A2%E7%B4%A2/#0x03-%E6%90%9C%E6%9F%A5%E9%9D%99%E6%80%81%E6%B3%A8%E5%86%8C%E5%87%BD%E6%95%B0