NDK开发详解之NDK开发简介

篇幅有限

完整内容及源码关注公众号:ReverseCode,发送

JNI简述

JNI:Java Native Interface的缩写,通常翻译为JAVA本地 接口。从Java1.1开始,Java Native Interface(JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为本地已编译语言,尤其是 C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。

JNI不是Android特有,windows、linux等凡是有JVM的地方都支持JNI。Android的Dalvik/ART虚拟机同样支持JNI标准。通过JNI,便可以打通Android里的两个世界:JAVA世界和Native世界。可以说,JNI是Java和Native世界的桥梁。而背后的一切都由Dalvik/ART虚拟机来驱动。

image-20210725212710380

JAVA特点

特性:简单、面向对象、分布式、编译和解释性、健壮性、跨平台性(Write Once,Run Anywhere)、多线程、动态性等等

但是:性能较低、易于逆向分析等问题。

因此,如何能够提高java程序的性能以及如何能够实现对海量已有C/C++代码库的复用问题?

image-20210725212824225

NDK简述

NDK 即Native Development Kit,因此称为“NDK”, 完成使用JNI提供的接口与java层的交互。

  • NDK是一系列工具的集合。

    • NDK提供了工具链,帮助开发者快速开发以及调试C(或C++)的动态库。
  • NDK提供了一份稳定、功能有限的API库。

    • 不同于linux的glibc,Android采用的是Google Bionic Libc,大部分api是一致的。
  • 一些重要逻辑、算法可以采用C/C++、甚至是汇编的形式通过NDK的工具链最终编译生成动态库,最后通过JNI完成和Dalvik/ART虚拟机环境中的Java代码的交互

使用NDK开发的so不再具有跨平台特性,需要编译提供不同平台支持。ABI:ApplicationBinary Interface

image-20210725213404656

优点

  • 运行效率高:Android开发的原生代码不需要在Dalvik/ART虚拟机下运行,直接运行的汇编代码
  • 代码安全性高:Java层代码很容易被反编译,而C/C++反编译难度较大,代码混淆比如使用ollvm,字符串加密等保护汇编代码。同时,相对于针对Java代码的混淆保护来说,可以运用当前一切C/C++代码保护技术,如控制流混淆,加壳等;
  • 易于移植,可复用海量代码库:当时用第三方 C/C++开源库时便于移植,可以充分复用C/C++海量代码库

缺点

  • 开发效率低:和Android的Java世界(Dalvik/ART)交互繁琐,无法直接使用Android系统框架提供的丰富API
  • 开发难度大:对于当前市面的APP开发人员来说掌握JNI开发存在一定困难
  • 稳定性难以保证:对开发人员要求较高,如存在内存泄漏出现的频率搞,修正bug困难。

对于Android中的java函数,在Dalvik时代分为解释模式和JIT模式,JIT根据代码运行过程中的频度进行编译提升运行效率,4.4后的art中新增dex2oat流程,直接对dex中所有的函数进行编译,而不是像4.4在运行过程中JIT对热点代码编译。在app在运行前将字节码编译成汇编代码,能够提升app运行效率,必然在安装时需要对dex中函数编译很耗时,耗电,因此生成代码量很大,占用很大的内存,安装耗时,如在dexclassloader动态加载时dex2oat编译过程很耗时,很耗电,很占存储。在7.0后结合AOT和AIT混合编译。

ART 的运作方式

  1. 最初安装应用时不进行任何 AOT 编译。应用前几次运行时,系统会对其进行解译,并对经常执行的方法进行 JIT 编译。
  2. 当设备闲置和充电时,编译守护程序会运行,以便根据在应用前几次运行期间生成的配置文件对常用代码进行 AOT 编译。
  3. 下一次重新启动应用时将会使用配置文件引导型代码,并避免在运行时对已经过编译的方法进行 JIT 编译。在应用后续运行期间经过 JIT 编译的方法将会添加到配置文件中,然后编译守护程序将会对这些方法进行 AOT 编译。

Java函数运行模式

  1. 纯解释模式下执行
  2. JIT模式
  3. 经过dex2oat编译后在quick模式下运行

注意:Android 7.0(代号Nougat,简称N)开始结合用AOT,即时(JIT编译和配置文件引导型编译)。因此一个java函数可能运行在解释模式,JIT或者quick模式

JNI与NDK的关系

image-20210725214057952

NDK开发1

as新建项目类型选择Native C++,项目名为ndk01

  • extern C : 由于C++支持重载,C++类中的函数会有name mangling
  • jni函数的参数问题:JNIEnv* 和jobject以及JNIEnv*和jclass
  • JNICALL: 空宏
  • JNIEXPORT: __attribute__((visibility(“default”))),代表当前函数符号需要导出,与之对应的为hidden隐藏符号信息
  • AndroidStudio默认生成函数名很长,如:Java_com_kanxue_ndk01_MainActivity_stringFromJN
1
2
cp /system/lib/libc.so /sdcard
adb pull /sdcard/libc.so 使用ida打开后查看fopen

image-20210731173859419

MainActivity.java

1
2
3
int result = this.myfirstjni(10);  在onCreate中调用
public native String stringFromJNI();
public native int myfirstjni(int a);

一般函数

修改native-lib.cpp.cpp编译后打开libnative-lib.so

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 对函数名称按照c++的name mangling编译后修改函数名
int add(int a, int b) {
return a + b;
}
// 按照c风格保留函数符号名
extern "C" int add1(int a, int b) {
return a + b;
}
// 以C函数形式编译,JNIEXPORT告知编译器编译后导出函数名保留,IDA打开so后显示相同的静态注册的函数名,在IDA中左侧搜索Java检索到该方法
// 该函数静态注册,art中对jni函数查询过程决定以下划线拼接这么长的函数名
extern "C" JNIEXPORT jstring JNICALL
Java_com_kanxue_ndk01_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}

image-20210731175441986

image-20210731175522935

JNIEnv: 是一个JNI接口指针,指向了本地方法的函数表,该函数表中每一个成员指向了一个JNI函数。

静态函数

testc.h

1
2
3
4
5
6
7
#ifndef NDK01_TESTC_H
#define NDK01_TESTC_H

#endif //NDK01_TESTC_H

#include <stdio.h>
int add_c(int a,int b);

testc.c

1
2
3
4
5
6
7
8
9
10
11
#include <stdbool.h>
#include "testc.h"
#include "jni.h"
int add_c(int a,int b){
return a+b;
}

jboolean testc(JNIEnv* env,jobject obj,jstring c){
(*env)->GetStringUTFChars(env,c,NULL);
return true;
}

native-lib.cpp

1
2
3
4
5
6
7
8
9
10
11
extern "C" {
#include "testc.h"
}

extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_ndk01_MainActivity_staticfunc(JNIEnv *env, jclass /* this */) {
// __android_log_print(int prio, const char* tag, const char* fmt, ...)

int result = add_c(4, 6);
__android_log_print(ANDROID_LOG_INFO, "JNI", "JNI->%d", result);
}

MainActivity.java

1
2
3
4
5
6
7
8
9
String gbkStr = "中国人民"; //源码文件是GBK格式,或者这个字符串是从GBK文件中读取出来的, 转换为string 变成unicode格式
//利用getBytes将unicode字符串转成UTF-8格式的字节数组
byte[] utf8Bytes = gbkStr.getBytes("UTF-8");
//然后用utf-8 对这个字节数组解码成新的字符串
String utf8Str = new String(utf8Bytes, "UTF-8");
MainActivity.testString(utf8Str);


public static native void staticfunc();

JNI类型映射

基础数据类型

Native Type这8种数据类型可以C/C++中直接使用,包括域描述符,类描述符斜杠区分,函数描述符(参数域描述符叠加)返回类型描述符

Java类型本地类型(Native Type)描述域描述符
booleanjbooleanC/C++无符号8位整型(unsigned char)Z
bytejbyteC/C++带符号8位整型(char)B
charjcharC/C++无符号8位整型(unsigned short)C
shortjshortC/C++带符号8位整型(short)S
intjintC/C++带符号8位整型(int)I
longjlongC/C++带符号8位整型(long)J
floatjfloatC/C++32位浮点型(float)F
doublejdoubleC/C++64位浮点型(double)D

引用数据类型为L+该类型的类描述符+;String类型域描述符为Ljava/lang/String, int[]描述符为[I, float[]描述符为[F,String[]描述符为[Ljava/lang/String;,Object[]域描述符为[Ljava/lang/Object;,int[][] [][]描述符为[[I,float[][]描述符为[[F

引用数据类型

JNI数据类型java数据类型
jobjectjava.lang.Object
jclassjava.lang.Class
jstringjava.lang.String
jthrowablejava.lang.Throwable

image-20210801185420830

MainActivity.java

1
2
3
public native int myfirstjni(int a);

public native boolean booleantest(boolean a);

native-lib.cpp

1
2
3
4
5
6
7
8
9
10
11
12
extern "C" JNIEXPORT jint JNICALL
Java_com_kanxue_ndk01_MainActivity_myfirstjni(JNIEnv *env, jobject /* this */, jint a) {
int result = 0;
for (int i = 1; i < a; i++) {
result = result + i;
}
return result;
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_kanxue_ndk01_MainActivity_booleantest(JNIEnv *env, jobject /* this */, jboolean a) {
return a;
}

JNI其他类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
成员域ID,成员方法ID
struct _jfieldId;
typedef struct _jfieldID* jfieldId;
struct _jmethodID;
typedef struct_jmethodID* jmethodId;
typedef union jvalue{
jboolean z;
jbyte b;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
}jvalue;

jni字符串操作

jstring NewStringUTF( const char* bytes)):函数使用给定的C字符串创建一个新的JNI字符串( jstring),不能为构造的 java. lang String分配足够的内存, NewStringUTF会抛出一个 OutOfMemoryError异常,并返回一个NULL

const char* GetStringUTFChars(jstring string, boolean* is Copy):函数可用于从给定的Java的 jstring创建新的C字符串(char*)。如果无法分配内存,则该函数返回NULL。检査NULL是一个好习惯。不要忘记检查。因为该函数需要为新诞生的UTF8字符串分配内存,这个操作有可能因为内存太少而失败。失败时, GetStringUTFChars会返回NULL,并抛出一个 OutofMemoryError异常,在不使用 GetStringUTFChars()返回的字符串时,需要来释放内存和引用以便可以对其进行垃圾回收,因此之后应始终调用 ReleaseStringUTFChars()

jsize GetStringUTFLength(jstring string):用于获取 jstring的长度

MainActivity.java

1
2
3
4
String resultString=testjstringapis("hello from java");  在onCreate中调用
Log.i("kanxue", "result:" + resultString);

public native String testjstringapis(String content);

native-lib.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
extern "C" JNIEXPORT jstring JNICALL
Java_com_kanxue_performancetest01_MainActivity_testjstringapis(
JNIEnv *env,
jobject /* this */,jstring content) {

const char* a=env->GetStringUTFChars(content,nullptr); // 将java中字符串转成c/c++使用的char *
int jstring_size=env->GetStringUTFLength(content); // 获取jstring长度
if(a!= nullptr){
__android_log_print(ANDROID_LOG_INFO,"kanxue", "char content:%s,size:%d", a,jstring_size);
}
env->ReleaseStringUTFChars(content,a); // 不使用该字符串手动释放内存
jstring result=env->NewStringUTF("Hello from jni"); // 创建新字符串
return result; // 返回给java层

}

api如下

1
2
3
4
5
6
7
8
jstring(* NewString)(JNIEnv*, const char*,jsize);//新建一个 unicode编码字符串
jsize(* GetStringLength)(JNIEnv*, jstring);/)回 unicode编码的字符串的长度
const jchar*(* GetStringChars)(JNIEnv*, jstring, jboolean*);//返回一个 unicode编码的 jstring的 char指针
void(* Release String Chars)(JNIEnv*, jstring, const jchar*):/释放使用 Newstring时分配的内存
string(* NewStringUTF)(JNIEnv*, const char*);//char为1个字节,ut8编码字符串
jsize(* GetStringUTFLength)(JNIEnv*, jstring);/返回utf8编码的字符串的字节长度,字符个数
const char*(*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);
void (*ReleaseStringUTFChars( JNIEnv*, jstring, const char*);/释放使用 NewStringUTFChars时分配的内存

NDK开发2

ANDROID: 使用OATDUMP反编译OAT文件:用来对dex2oat编译生成oat文件进行反汇编,看到dex2oat编译的结果

as新建项目类型选择Native C++,项目名为PerformanceTest01

MainActivity.java

1
2
3
4
5
6
7
8
9
int result = java_add(1000000);  // 在onCreate中调用
public int java_add(int num) {

int result = 0;
for (int i = 0; i <= num; i++) {
result = result + i;
}
return result;
}

编译好后安装到手机上,ls /data/app/com.kanxue.performancetest01-*/oat/arm64可以看到base.dex和base.vdex

ART 包括一个编译器(dex2oat 工具)和一个为启动 Zygote 而加载的运行时 (libart.so)。dex2oat 工具接受一个 APK 文件,并生成一个或多个编译工件文件,然后运行时将会加载这些文件。文件的个数、扩展名和名称因版本而异,但在 Android O 版本中,将会生成以下文件:

  • .vdex:其中包含 APK 的未压缩 DEX 代码,以及一些旨在加快验证速度的元数据。
  • .odex:其中包含 APK 中已经过 AOT 编译的方法代码。
  • .art (optional):其中包含 APK 中列出的某些字符串和类的 ART 内部表示,用于加快应用启动速度。
1
2
adb pull /sdcard/base.dex
010Editor base.vdex 删除dex.035.前面的内容,保存为dex文件解析并可以查看函数列表,改名vdex为dex使用jadx打开

image-20210801132658638

1
2
3
oatdump --oat-file=base.dex >> /sdcard/oatdump1.txt
adb pull /sdcard/oatdump1.txt
010Editor oatdump1.txt 输出函数的smali指令,编译函数后的偏移,dex2oat生成的汇编指令

image-20210801141651486

说明还没有被dex2oat编译。nexus 6p默认Android7.0在最初安装时不进行AOT编译,通过查看7.0以前nexus 5的ART来对比编译后的汇编代码,安装完直接编译。编译好后安装到手机上,ls /data/app/com.kanxue.performancetest01-*/oat/arm可以看到base.odex

1
2
3
oatdump --oat-file=base.dex >> /sdcard/6.0oatdump.txt
adb pull /sdcard/0oatdump.txt
010Editor 0oatdump.txt 输出函数的smali指令,编译函数后的偏移,dex2oat生成的汇编

image-20210801181213830

说明6.0时smali正常显示,CODE部分已经编译生成汇编代码,当6.0安装完该函数必然运行在quick下,并不会解释执行原有的java代码。

解释模式

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
在onCreate中调用两个函数对比时间效率
long start = System.nanoTime();
int result = java_add(1000000);
long end = System.nanoTime();
Log.i("kanxue", "result:" + result + "--" + (end - start));
start = System.nanoTime();
result = jni_add(1000000);
end = System.nanoTime();
Log.i("kanxue", "result:" + result + "--" + (end - start));


public native int jni_add(int num);

native-lib.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
extern "C" JNIEXPORT jint JNICALL
Java_com_kanxue_performancetest01_MainActivity_jni_1add(
JNIEnv *env,
jobject obj/* this */, jint num) {

jclass thisclazz=env->GetObjectClass(obj);
jmethodID add_mid=env->GetMethodID(thisclazz,"add","()V");

jint result = 0;
for (int i = 0; i <= num; i++) {

result = result + i;
}
return result;
}

首先比较在纯解释模式下运行java函数和jni函数花销,自然不能让art下的dex2oat的编译流程完成对java函数的编译。在8.0后app安装完后的函数还是没有被编译,运行在解释模式下的。运行了一定时间后,某些函数就已经被编译了。自然导致在7.0以后函数运行在不同模式下都有可能。首先要让函数运行在解释模式下,可以通过禁用art下的dex2oat的编译流程, 或者使用4.4之前的dalvik的执行环境,必然是在一个解释模式下的。那么使用4.4的hammerhead安装该app,自然默认运行在dalvik下。

1
2
result:1784293664--23747499   smali指令在纯解释模式,jni大致是java函数的5倍
result:1784293664--4924895

quick模式

接下来对比art下经过dex2oat编译后的quick模式下java函数执行的时间花销和jni函数的时间花销,首先保证java函数被dex2oat编译,且函数运行在quick模式下,也就是编译后的汇编代码。7.0以后一开始是没有编译,是在解释模式下,只有在运行一段时间后,调用频率比较高时在充电或者闲置时会被编译。自然确保确实被编译运行在dex2oat的quick模式下,可以使用7.0以前的art测试,可以使用6.0的hammerhead,安装好app后这个时候运行的java函数就会被编译成了汇编了。

1
2
result:1784293664--5778594   java函数经过dex2oat编译以后运行在quick模式下时花销是jni花销的一倍多一点
result:1784293664--3798072

性能提升总结

以上分别比较相同代码逻辑的java函数和jni函数的时间代价。

java函数运行模式

  • 在4.4以前的dalvik下运行或者art下利用hook禁用掉dex2oat过程强制让其运行在解释模式
  • 使用Android6.0测试java函数在quick模式(运行dex2oat编译以后的汇编代码)

jni函数实现

  • 和java函数相同的逻辑,纯C/C++实现
  • 和java函数相同的逻辑,经过JNI提供的接口频繁调用java函数

Java反射

java反射:java反射机制是在运行状态中,对于任意类,都能够知道这个类的所有属性和方法;对于任意类的静态属性和方法,都能够完成对静态属性的获取和设置以及静态方法的调用;对于任意个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为ava语言的反射机制。

在日常的第三方应用开发过程中,经常会遇到某类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。而对于public类型的成员变量和方法和属性都可以使用反射来进行访问。

类名用途
Class类代表类的实体,在运行的Java应用程序中表示类和接口
Field类代表类的成员变量(成员变量也成为了类的属性)
Method类代表类的方法
Constructor类代表类的构造方法

Class

代表类的实体,在运行的Java应用程序中表示类和接口。在类中提供了很多有用的方法。

image-20210801221112872

获取类中属性相关方法

image-20210801221225452

获取类中注解相关方法

image-20210801221409618

获取类中构造器相关方法

image-20210801221429821

获取类中方法的相关方法

image-20210801221528188

获取类中其他重要的方法

image-20210801222015525

Field

Field代表类的成员变量(成员变量也称为类的属性)

image-20210801222219772

Method

image-20210801222414308

Constructor

image-20210801222548458

破坏本身的封装性和安全性,访问 private域和方法类 AccessibleObject中有函数 public void setAccessible( boolean flag),该函数在传入tue作为参数后,让访问 private修饰的域和函数成为可能。而Method、Feld和 Constructot类共同继承了 AccessibleObject类,该基类有两个 setAccessible方法能在运行时压制Java语言访问控制检查(Javalanguage access control checks),从而能任意调用被私有化保护的方法域和构造方法

新建C++项目ReflectionTest3

Test.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
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
public class Test {

public String flag = null;
public int[] intarray = null;

public Test() {

flag = "Test()";
intarray = new int[10];
for (int i = 0; i < 10; i++) {
intarray[i] = i;

}
}

public Test(String arg) {

flag = "Test(String arg)";
intarray = new int[10];
for (int i = 0; i < 10; i++) {
intarray[i] = i;

}
}

public Test(String arg, int arg2) {

flag = "Test(String arg,int arg2)";
intarray = new int[10];
for (int i = 0; i < 10; i++) {
intarray[i] = i;

}
}

public static String publicStaticField = "i am a publicStaticField";
public static int publicStaticField_int = 100;
public String publicField = "i am a publicField";
private static String privateStaticField = "i am a privateStaticField";
private String privateField = "i am a privateField";
private int privateField_int = 200;

public static void publicStaticFunc() {
Log.i("kanxue", "i am from publicStaticFunc");
}

public static int publicStaticFunc_int(int a) {
Log.i("kanxue", "i am from publicStaticFunc_int");
return 100 + a;
}

public static String publicStaticFunc_string(String arg) {
Log.i("kanxue", "i am from publicStaticFunc_int");
return "publicStaticFunc_string->" + arg;
}

public void publicFunc() {
Log.i("kanxue", "i am from publicFunc");
}


//add
private String privatetest(int a, String b) {

Log.i("kanxue", "privatetest func is called:" + a + "---" + b);
return b + a;
}

private static void privateStaticFunc() {
Log.i("kanxue", "i am from privateStaticFunc");
}

private int[] privateFunc_array(int num) {
Log.i("kanxue", "i am from privateStaticFunc");

int array[] = new int[num];
for (int i = 0; i < array.length; i++) {
array[i] = i;
}
return array;
}

private static void privateFunc() {
Log.i("kanxue", "i am from privateStaticFunc");
}

}

MainActivity.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
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
testField();  在onCreate中调用
testMethod();

public void testField() {
Test a_obj = new Test();
Test b_obj = new Test("test");
Test c_obj = new Test("test", 2);
Class testClazz = null;
try {
testClazz = MainActivity.class.getClassLoader().loadClass("com.kanxue.reflectiontest.Test");
Log.i("kanxue", "ClassLoader.loadClass->" + testClazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class testClazz2;
try {
testClazz2 = Class.forName("com.kanxue.reflectiontest.Test");
Log.i("kanxue", "Class.forName->" + testClazz2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class testClazz3 = Test.class;
Log.i("kanxue", "Test.class->" + testClazz);
try {
Field publicStaticField_field = testClazz3.getDeclaredField("publicStaticField");
Log.i("kanxue", "getDeclaredField->" + publicStaticField_field);
String content = (String) publicStaticField_field.get(null);
Log.i("kanxue", "publicStaticField->" + content);
content = (String) publicStaticField_field.get(a_obj);
Log.i("kanxue", "publicStaticField->a_obj" + content);

} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
//privateField
try {
Field privateStaticField_field = testClazz3.getDeclaredField("privateStaticField");
Log.i("kanxue", "getDeclaredField->" + privateStaticField_field);
privateStaticField_field.setAccessible(true);
privateStaticField_field.set(null, "modified");
String content = (String) privateStaticField_field.get(null);

Log.i("kanxue", "privateStaticField_field->" + content);

} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
try {
Field privateField_field = testClazz3.getField("privateField");
Log.i("kanxue", "getField->" + privateField_field);
} catch (Error | NoSuchFieldException e) {
e.printStackTrace();
}
Field[] fields = testClazz3.getDeclaredFields();
for (Field i : fields) {
Log.i("kanxue", "getDeclaredFields->" + i);
}

Field[] fields_1 = testClazz3.getFields();
for (Field i : fields_1) {
Log.i("kanxue", "getFields->" + i);
}


}

public void testMethod() {
Class testClazz = Test.class;
Method publicStaticFunc_method = null;
try {
publicStaticFunc_method = testClazz.getDeclaredMethod("publicStaticFunc");
publicStaticFunc_method.invoke(null);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
Log.i("kanxue", "getDeclaredMethod->" + publicStaticFunc_method);

Method privateStaticFunc_method = null;
try {
privateStaticFunc_method = testClazz.getDeclaredMethod("privateStaticFunc");
privateStaticFunc_method.setAccessible(true);
privateStaticFunc_method.invoke(null);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
Log.i("kanxue", "getDeclaredMethod->" + privateStaticFunc_method);

Method[] methods = testClazz.getDeclaredMethods();
for (Method i : methods) {
Log.i("kanxue", "getDeclaredMethods->" + i);
}
Method[] methods_1 = testClazz.getMethods();
for (Method i : methods_1) {
Log.i("kanxue", "getMethods->" + i);
}
Constructor[] constructors = testClazz.getDeclaredConstructors();
for (Constructor i : constructors) {
Log.i("kanxue", "getDeclaredConstructors->" + i);
}
try {
Constructor constructor = testClazz.getDeclaredConstructor(String.class, int.class);
Object testobj = constructor.newInstance("test", 666);
Field privateField_field = testClazz.getDeclaredField("privateField");
privateField_field.setAccessible(true);
String privateField_String = (String) privateField_field.get(testobj);
Log.i("kanxue", "privateField->" + privateField_String);


Log.i("kanxue", "getDeclaredConstructor->" + constructor);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}

}


public native String stringFromJNI();

native-lib.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extern "C" JNIEXPORT jstring JNICALL
Java_com_kanxue_reflectiontest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {

__android_log_print(4, "kanxue->jni",
"Java_com_kanxue_reflectiontest_MainActivity_stringFromJNI->%p", env);

std::string hello = "Hello from C++";
jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
jfieldID publicStaticField_jfieldID = env->GetStaticFieldID(TestJclass, "publicStaticField","Ljava/lang/String;");
jstring publicStaticField_content = (jstring) env->GetStaticObjectField(TestJclass,publicStaticField_jfieldID);
const char *content_ptr = env->GetStringUTFChars(publicStaticField_content, nullptr);
__android_log_print(4, "kanxue->jni", "jni->%s", content_ptr);

return env->NewStringUTF(hello.c_str());
}

JavaVM与JNIEnv

JNIEnv:指Java Native Interface Environment,是一个JNI接口指针,指向了本地方法 的一个函数表,该函数表中的每一个成员指向了一个JNI函数,本地方法通过JNI函数 来访问JVM中的数据结构。

JNIEnv 表示 Java 调用 native 语言的环境,是一个封装了几乎全部 JNI 方法的指针。

JNIEnv 只在创建它的线程生效,不能跨线程传递,不同线程的 JNIEnv 彼此独立。

native 环境中创建的线程,如果需要访问 JNI,必须要调用 AttachCurrentThread 关联,并使用 DetachCurrentThread 解除链接

JavaVM 和 JNIEnv 在 C 语言环境下和 C++ 环境下调用是有区别的,在C的定义中,env是一个两级指针,而在C++的定义中,env是个一级指针,主要表现在:

1
2
C风格:(*env)->NewStringUTF(env, “Hellow World!”); 
C++风格:env->NewStringUTF(“Hellow World!”);

JavaVM获取方式有两种,1-在JNI_Onload中作为参数获得,2-通过JNIEnv的GetJavaVM函数获得

1
2
JavaVM *globalVM = nullptr;
env->GetJavaVM(&globalVM);

JNIEnv获取方式有三种,

1-如果当前线程绑定了一个JNIEnv,可以通过JavaVM获取比如主线程中的JNI_Onload内容

1
2
JNIEnv* env = NULL;
if(vm->GetEnv(reinterpret_case<void**>(&env),JNI_VERSION_1_6)!=JNI_OK){ return -1;}

2-通过JNI函数的参数传入获取,对于任何一个JNI函数来说,第一个参数都是JNIEnv指针

3-在非主线程中时需要通过当前进程的JavaVM调用AttachCurrentThread(&env,NULL )来获取

注意:

JNIEnv是与一个ClassLoader绑定的,当时用env->FindClass()进行类的查询和加载时便是时用的这个ClassLoader

JNIEnv是当前Java线程的执行环境,一个JVM对应一个JavaVM结构,而一个JVM中可能创建多个Java线程,时用pthread_create新建的线程当时用AttachCurrentThread(&env, NULL)获取到JNIEnv后,该JNIEnv的ClassLoader并不是主线程的ClassLoader,因此也无法加载app自己的class。

JNIEnv 和 JavaVM 其实只是对 JNINativeInterface 和 JNIInvokeInterface 的一层封装,实际调用和操作的还是 JNINativeInterface 和 JNIInvokeInterface 里的方法。

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
//extern "C"
JNIEXPORT jstring JNICALL
Java_com_kanxue_reflectiontest_MainActivity_stringFromJNIC(
JNIEnv *env,
jobject thisobj/* this */) {
/* jint GetVersion()
{ return functions->GetVersion(this); }

jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
jsize bufLen)
{ return functions->DefineClass(this, name, loader, buf, bufLen); }

jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }*/

const char* hello = "Hello from C";
//publicStaticField
jclass TestJclass = (*env)->FindClass(env,"com/kanxue/reflectiontest/Test");
//jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
jfieldID publicStaticField_jfieldID = (*env)->GetStaticFieldID(env,TestJclass, "publicStaticField",
"Ljava/lang/String;");
//jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
jstring publicStaticField_content = (jstring) (*env)->GetStaticObjectField(env,TestJclass,
publicStaticField_jfieldID);
const char *content_ptr = (*env)->GetStringUTFChars(env,publicStaticField_content, NULL);
__android_log_print(4, "kanxue->jni", "jni->%s", content_ptr);

return (*env)->NewStringUTF(env,hello);
}

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
JavaVM *globalVM = nullptr;
extern "C" JNIEXPORT jstring JNICALL
Java_com_kanxue_reflectiontest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
JavaVM *thisvm = nullptr;
env->GetJavaVM(&thisvm);

__android_log_print(4, "kanxue->jni", "env->GetJavaVM(&thisvm)->%p", thisvm);
__android_log_print(4, "kanxue->jni",
"Java_com_kanxue_reflectiontest_MainActivity_stringFromJNI->%p", env);

/* jint GetVersion()
{ return functions->GetVersion(this); }

jclass DefineClass(const char *name, jobject loader, const jbyte* buf,jsize bufLen)
{ return functions->DefineClass(this, name, loader, buf, bufLen); }

jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }*/

std::string hello = "Hello from C++";
//publicStaticField
jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
//jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
jfieldID publicStaticField_jfieldID = env->GetStaticFieldID(TestJclass, "publicStaticField","Ljava/lang/String;");
//jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
jstring publicStaticField_content = (jstring) env->GetStaticObjectField(TestJclass,publicStaticField_jfieldID);
const char *content_ptr = env->GetStringUTFChars(publicStaticField_content, nullptr);
__android_log_print(4, "kanxue->jni", "jni->%s", content_ptr);

return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_kanxue_reflectiontest_MainActivity_getFields(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
}
// 新建线程 AttachCurrentThread 才能获取JNIEnv
void *threadtest(void *args) {

for (int i = 0; i < 10; i++) {
__android_log_print(4, "kanxue->jni", "jni->%s,%d", "I am from threadtest", i);
}
JNIEnv *threadenv = nullptr;
if (globalVM->GetEnv((void **) &threadenv, JNI_VERSION_1_6) == JNI_OK) {
__android_log_print(4, "kanxue->jni", "jni->%s", "globalVM->GetEnv((void**)&threadenv,JNI_VERSION_1_6)==JNI_OK success");

} else {
__android_log_print(4, "kanxue->jni", "jni->%s","globalVM->GetEnv((void**)&threadenv,JNI_VERSION_1_6)==JNI_OK failed");
}
int result = globalVM->AttachCurrentThread(&threadenv, nullptr);
if (result == JNI_OK) {
__android_log_print(4, "kanxue->jni", "jni->%s"," globalVM->AttachCurrentThread(&threadenv, nullptr)==JNI_OK success");
jstring jstring1 = threadenv->NewStringUTF("threadtest jstring");
const char *content = threadenv->GetStringUTFChars(jstring1, nullptr);
__android_log_print(4, "kanxue->jni", "jni->%s", content);
threadenv->ReleaseStringUTFChars(jstring1, content);

} else {
__android_log_print(4, "kanxue->jni", "jni->%s"," globalVM->AttachCurrentThread(&threadenv, nullptr)==JNI_OK failed");
}
//publicStaticField
jclass TestJclass = threadenv->FindClass("com/kanxue/reflectiontest/Test");
threadenv->ExceptionDescribe();
threadenv->ExceptionClear();
if (TestJclass == nullptr) {
__android_log_print(4, "kanxue->jni", "jni->%s",
"TestJclass is nullptr");
} else {
__android_log_print(4, "kanxue->jni", "jni->%s",
"TestJclass is not nullptr");
//jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
jfieldID publicStaticField_jfieldID = threadenv->GetStaticFieldID(TestJclass,
"publicStaticField",
"Ljava/lang/String;");
//jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
jstring publicStaticField_content = (jstring) threadenv->GetStaticObjectField(TestJclass,
publicStaticField_jfieldID);
const char *content_ptr = threadenv->GetStringUTFChars(publicStaticField_content, nullptr);
__android_log_print(4, "kanxue->jni", "jni->%s", content_ptr);

};

//globalVM->AttachCurrentThread()
globalVM->DetachCurrentThread();
pthread_exit(0);

}
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
globalVM = vm;
__android_log_print(4, "kanxue->jni", "JNI_OnLoad(JavaVM *vm, void *reserved)->%p", vm);
__android_log_print(4, "kanxue->jni", "jni->%s", "JNI_OnLoad is called");
jint result = 0;
JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) == JNI_OK) {
__android_log_print(4, "kanxue->jni", "jni->%s",
"vm->GetEnv((void**)&env,JNI_VERSION_1_6) success");
}

//publicStaticField
jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
//jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
jfieldID publicStaticField_jfieldID = env->GetStaticFieldID(TestJclass, "publicStaticField",
"Ljava/lang/String;");
//jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
jstring publicStaticField_content = (jstring) env->GetStaticObjectField(TestJclass,
publicStaticField_jfieldID);
const char *content_ptr = env->GetStringUTFChars(publicStaticField_content, nullptr);
__android_log_print(4, "kanxue->jni", "jni->%s", content_ptr);

__android_log_print(4, "kanxue->jni", "GetEnv((void**)&env,JNI_VERSION_1_6)->%p", env);
//int pthread_create(pthread_t* __pthread_ptr, pthread_attr_t const* __attr, void* (*__start_routine)(void*), void*);
pthread_t thread;
//pthread_create(&thread, nullptr,threadtest, nullptr);
pthread_create(&thread, nullptr, threadtest, nullptr);
pthread_join(thread, nullptr);

/* jint DestroyJavaVM()
{ return functions->DestroyJavaVM(this); }
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{ return functions->DetachCurrentThread(this); }
jint GetEnv(void** env, jint version)
{ return functions->GetEnv(this, env, version); }
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }*/
result = JNI_VERSION_1_6;
return result;
}

JavaVM 是虚拟机在 JNI 层的代表,一个进程只有一个 JavaVM,所有 的线程共用一个 JavaVM。

  • JNIInvokeInterface_结构封装和JVM相关的功能函数,如销毁JVM,获得当前线程的Java执行环境。
  • 在C和C++中JavaVM的定义有所不同,在C中JavaVM是JNIInvokeInterface_类型指针,而在C++中又对 JNIInvokeInterface_进行了一次封装。
  • 推荐用C++来编写JNI代码,比C中少了一个参数
  1. 虚拟机是唯一的,但是可以有很多线程,所以使用JNIEnv和一个线程绑定。

    1.1. 如果当前线程已绑定了一个JNIEnv,可以通过jint (JNICALL GetEnv)(JavaVM vm, voidpenv, jint version); 获取。

    1.2. 如果未绑定,调用jint (JNICALL AttachCurrentThread)(JavaVM *vm,void *penv, void *args); 主动绑定,线程结束或者不需要的话解除绑定。

  2. JNIEnv是当前Java线程的执行环境,一个JVM对应一个JavaVM结构,而一个JVM中可能创建多个Java线程,每个线程对应一个JNIEnv结构,它们保存在线程本地存储中。因此,不同的线程的JNIEnv是不同,也不能相互共享使用。

  3. JNIEnv结构也是一个函数表(可以这么理解),在Native代码中通过JNIEnv的函数表来操作Java数据或调用Java方法。

  4. 子线程和主线程的JNIEnv并不是都在调用方的ClassLoader当中,因此,需要特别注意env->FindClass的用法

JNI新建对象

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
extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_newObject(
JNIEnv *env,
jobject /* this */) {
//NewObject创建对象构造方法的方法返回值类型前面始终为void
jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
//public Test(String arg)
// jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
jmethodID con_mid = env->GetMethodID(TestJclass, "<init>", "(Ljava/lang/String;)V");
jstring arg = env->NewStringUTF("I am From Jni");
//jobject NewObject(jclass clazz, jmethodID methodID, ...)
jobject testobj = env->NewObject(TestJclass, con_mid, arg);
if (testobj != nullptr) {
__android_log_print(4, "kanxue->jni", "jni->%s", "NewObject success!");
}

//AllocObject 根据传入的jclass创建一个费初始化的JAVA对象,需要用CallNonvirtualVoidMethod初始化,可以延迟构造函数的调用
//jclass clazz_str = env->FindClass("java/lang/String");
//jmethodID methodID_str = env->GetMethodID(clazz_str ,"<init>", "()V");
jobject testobj2 = env->AllocObject(TestJclass);
jstring arg1 = env->NewStringUTF("I am From Jni->AllocObject");
env->CallNonvirtualVoidMethod(testobj2, TestJclass, con_mid, arg1);
if (testobj2 != nullptr) {

__android_log_print(4, "kanxue->jni", "jni->%s", "AllocObject success!");
}

}

获取静态域
extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_getStaticField(
JNIEnv *env,
jobject /* this */) {
//private static
jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
// public static String publicStaticField = "i am a publicStaticField";
// jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
jfieldID publicStaticField_fid = env->GetStaticFieldID(TestJclass, "publicStaticField","Ljava/lang/String;");
/*jstring publicStaticField_obj = static_cast<jstring>(env->GetStaticObjectField(TestJclass,publicStaticField_fid));
const char *publicStaticField_content = env->GetStringUTFChars(publicStaticField_obj, nullptr);
__android_log_print(4, "kanxue->jni", "publicStaticField_obj->%s", publicStaticField_content);*/

jstring setjstring = env->NewStringUTF("modified by jni");
// void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)
env->SetStaticObjectField(TestJclass, publicStaticField_fid, setjstring);
jstring publicStaticField_obj = static_cast<jstring>(env->GetStaticObjectField(TestJclass,publicStaticField_fid));
const char *publicStaticField_content = env->GetStringUTFChars(publicStaticField_obj, nullptr);
__android_log_print(4, "kanxue->jni", "publicStaticField_obj->%s", publicStaticField_content);

//private static String privateStaticField = "i am a privateStaticField";
jfieldID privateStaticField_fid = env->GetStaticFieldID(TestJclass, "privateStaticField","Ljava/lang/String;");
jstring privateStaticField_obj = static_cast<jstring>(env->GetStaticObjectField(TestJclass,privateStaticField_fid));
const char *privateStaticField_content = env->GetStringUTFChars(privateStaticField_obj,nullptr);
__android_log_print(4, "kanxue->jni", "privateStaticField_obj->%s", privateStaticField_content);

// public static int publicStaticField_int = 100;

jfieldID publicStaticField_int_fid = env->GetStaticFieldID(TestJclass, "publicStaticField_int","I");


env->SetStaticIntField(TestJclass, publicStaticField_int_fid, 200);

jint publicStaticField_int_value = env->GetStaticIntField(TestJclass, publicStaticField_int_fid);

__android_log_print(4, "kanxue->jni", "publicStaticField_int_value->%d",
publicStaticField_int_value);

/* jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
{ return functions->GetStaticObjectField(this, clazz, fieldID); }


jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID)
{ return functions->GetStaticBooleanField(this, clazz, fieldID); }
jbyte GetStaticByteField(jclass clazz, jfieldID fieldID)
{ return functions->GetStaticByteField(this, clazz, fieldID); }
jchar GetStaticCharField(jclass clazz, jfieldID fieldID)
{ return functions->GetStaticCharField(this, clazz, fieldID); }
jshort GetStaticShortField(jclass clazz, jfieldID fieldID)
{ return functions->GetStaticShortField(this, clazz, fieldID); }
jint GetStaticIntField(jclass clazz, jfieldID fieldID)
{ return functions->GetStaticIntField(this, clazz, fieldID); }
jlong GetStaticLongField(jclass clazz, jfieldID fieldID)
{ return functions->GetStaticLongField(this, clazz, fieldID); }
jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID)
{ return functions->GetStaticFloatField(this, clazz, fieldID); }
jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID)
{ return functions->GetStaticDoubleField(this, clazz, fieldID); }*/



/* void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)
{ functions->SetStaticObjectField(this, clazz, fieldID, value); }
void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value)
{ functions->SetStaticBooleanField(this, clazz, fieldID, value); }
void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value)
{ functions->SetStaticByteField(this, clazz, fieldID, value); }
void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value)
{ functions->SetStaticCharField(this, clazz, fieldID, value); }
void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value)
{ functions->SetStaticShortField(this, clazz, fieldID, value); }
void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
{ functions->SetStaticIntField(this, clazz, fieldID, value); }
void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value)
{ functions->SetStaticLongField(this, clazz, fieldID, value); }
void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value)
{ functions->SetStaticFloatField(this, clazz, fieldID, value); }
void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value)
{ functions->SetStaticDoubleField(this, clazz, fieldID, value); }*/


}

获取非静态域
extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_getNonStaticField(
JNIEnv *env,
jobject obj, jobject testobj) {

// private String privateField = "i am a privateField";

jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
jfieldID privateField_fid = env->GetFieldID(TestJclass, "privateField", "Ljava/lang/String;");
// jobject GetObjectField(jobject obj, jfieldID fieldID)
jstring privateField_obj = static_cast<jstring>(env->GetObjectField(testobj, privateField_fid));
/* const char *privateField_obj_content = env->GetStringUTFChars(privateField_obj, nullptr);
__android_log_print(4, "kanxue->jni", "privateField_obj->%s", privateField_obj_content);*/

//
jstring newString = env->NewStringUTF("Modified by jni");
env->SetObjectField(testobj, privateField_fid, newString);


privateField_obj = static_cast<jstring>(env->GetObjectField(testobj, privateField_fid));
const char *privateField_obj_content = env->GetStringUTFChars(privateField_obj, nullptr);
__android_log_print(4, "kanxue->jni", "privateField_obj->%s", privateField_obj_content);

/* void SetObjectField(jobject obj, jfieldID fieldID, jobject value)
{ functions->SetObjectField(this, obj, fieldID, value); }
void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value)
{ functions->SetBooleanField(this, obj, fieldID, value); }
void SetByteField(jobject obj, jfieldID fieldID, jbyte value)
{ functions->SetByteField(this, obj, fieldID, value); }
void SetCharField(jobject obj, jfieldID fieldID, jchar value)
{ functions->SetCharField(this, obj, fieldID, value); }
void SetShortField(jobject obj, jfieldID fieldID, jshort value)
{ functions->SetShortField(this, obj, fieldID, value); }
void SetIntField(jobject obj, jfieldID fieldID, jint value)
{ functions->SetIntField(this, obj, fieldID, value); }
void SetLongField(jobject obj, jfieldID fieldID, jlong value)
{ functions->SetLongField(this, obj, fieldID, value); }
void SetFloatField(jobject obj, jfieldID fieldID, jfloat value)
{ functions->SetFloatField(this, obj, fieldID, value); }
void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value)
{ functions->SetDoubleField(this, obj, fieldID, value); }*/

// private int privateField_int = 200;

jfieldID privateField_int_fid = env->GetFieldID(TestJclass, "privateField_int", "I");
/* jint privateField_int_value = env->GetIntField(testobj, privateField_int_fid);
__android_log_print(4, "kanxue->jni", "privateField_int_value->%d", privateField_int_value);*/

env->SetIntField(testobj, privateField_int_fid, 300);
jint privateField_int_value = env->GetIntField(testobj, privateField_int_fid);
__android_log_print(4, "kanxue->jni", "privateField_int_value->%d", privateField_int_value);

// public int[] intarray = null;
jfieldID intarray_fid=env->GetFieldID(TestJclass,"intarray","[I");
jintArray intarray_obj= static_cast<jintArray>(env->GetObjectField(testobj, intarray_fid));
// 获取数组长度
int arraylength=env->GetArrayLength(intarray_obj);

__android_log_print(4, "kanxue->jni", "arraylength->%d", arraylength);

/* int* array=env->GetIntArrayElements(intarray_obj, nullptr);
for(int i=0;i<arraylength;i++){
__android_log_print(4, "kanxue->jni", "array[%d]->%d", i,array[i]);
}*/
// void SetIntArrayRegion(jintArray array, jsize start, jsize len,const jint* buf)
jint jni_array[arraylength];
for(int j=0;j<arraylength;j++){
jni_array[j]=10-j;
}
const jint* ptr=jni_array;
env->SetIntArrayRegion(intarray_obj,0,arraylength,ptr);
// 获取数组的指针
int* array=env->GetIntArrayElements(intarray_obj, nullptr);
// 遍历数组每个元素
for(int i=0;i<arraylength;i++){
__android_log_print(4, "kanxue->jni", "array[%d]->%d", i,array[i]);
}
//env->SetIntArrayRegion()

}

JNI访问函数

和Java反射类访问类属性的对比:

1.Java反射中获取属性时只需要传入属性名即可,而jni中还需要传入该属性的签名信息

2.Java反射中在对private类型属性访问时需要先取消安全检查,即调用setAccessible(true)才能访问,而jni中不需要

3.Java反射中对普通函数使用Method,而对构造函数使用Contructor,在jni中构造函数也是普通函数,依然使用jmethodID,只不过函数名是<init>

构造函数

对于构造函数的调用来说只能通过NewObject和AllocObject来调用,因为构造函数本身首先不是静态函数,自然不能通过类名进行调用;其次如果按照一般的函数进行调用,需要传入对象,而此时还没有对象,因此只能是一种特例。

Test testobj = (Test) callInit();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extern "C" JNIEXPORT jobject JNICALL
Java_com_kanxue_reflectiontest_MainActivity_callInit(
JNIEnv *env,
jobject /* this */) {
// public Test(String arg, int arg2)
jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");

jmethodID con_mid = env->GetMethodID(TestJclass, "<init>", "(Ljava/lang/String;I)V");

jstring arg0 = env->NewStringUTF("i am from callInit");

jobject obj = env->NewObject(TestJclass, con_mid, arg0, 100);
jmethodID privatetest_mid = env->GetMethodID(TestJclass, "privatetest", "(ILjava/lang/String;)Ljava/lang/String;");
jstring arg2 = env->NewStringUTF("i am from jni-CallObjectMethodA");

return obj;

}

静态函数

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
extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_callStaticFunc(
JNIEnv *env,
jobject /* this */) {
//public static void publicStaticFunc()

jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");

jmethodID publicStaticFunc_mid = env->GetStaticMethodID(TestJclass, "publicStaticFunc", "()V");

env->CallStaticVoidMethod(TestJclass, publicStaticFunc_mid);

//private static void privateStaticFunc()

jmethodID privateStaticFunc_mid = env->GetStaticMethodID(TestJclass, "privateStaticFunc", "()V");

env->CallStaticVoidMethod(TestJclass, privateStaticFunc_mid);

/* public static int publicStaticFunc_int(int a) {
Log.i("kanxue", "i am from publicStaticFunc_int");
return 100+a;
}*/
jmethodID publicStaticFunc_int_mid = env->GetStaticMethodID(TestJclass, "publicStaticFunc_int", "(I)I");

jint result_int = env->CallStaticIntMethod(TestJclass, publicStaticFunc_int_mid, 200);
__android_log_print(4, "kanxue->jni", "publicStaticFunc_int->%d", result_int);
/*public static String publicStaticFunc_string(String arg) {
Log.i("kanxue", "i am from publicStaticFunc_int");
return "publicStaticFunc_string->"+arg;
}*/
jmethodID publicStaticFunc_string_mid = env->GetStaticMethodID(TestJclass, "publicStaticFunc_string", "(Ljava/lang/String;)Ljava/lang/String;");

jstring arg_string = env->NewStringUTF("i am from jni");
jstring result_string = static_cast<jstring>(env->CallStaticObjectMethod(TestJclass, publicStaticFunc_string_mid, arg_string));

const char *content = env->GetStringUTFChars(result_string, nullptr);
__android_log_print(4, "kanxue->jni", "publicStaticFunc_string->%s", content);

}

非静态域

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
extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_getNonStaticField(
JNIEnv *env,
jclass obj, jobject testobj) {

// private String privateField = "i am a privateField";

jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
jfieldID privateField_fid = env->GetFieldID(TestJclass, "privateField", "Ljava/lang/String;");
// jobject GetObjectField(jobject obj, jfieldID fieldID)
jstring privateField_obj = static_cast<jstring>(env->GetObjectField(testobj, privateField_fid));

jstring newString = env->NewStringUTF("Modified by jni");
env->SetObjectField(testobj, privateField_fid, newString);


privateField_obj = static_cast<jstring>(env->GetObjectField(testobj, privateField_fid));
const char *privateField_obj_content = env->GetStringUTFChars(privateField_obj, nullptr);
__android_log_print(4, "kanxue->jni", "privateField_obj->%s", privateField_obj_content);
jfieldID privateField_int_fid = env->GetFieldID(TestJclass, "privateField_int", "I");
env->SetIntField(testobj, privateField_int_fid, 300);
jint privateField_int_value = env->GetIntField(testobj, privateField_int_fid);
__android_log_print(4, "kanxue->jni", "privateField_int_value->%d", privateField_int_value);

// public int[] intarray = null;
jfieldID intarray_fid = env->GetFieldID(TestJclass, "intarray", "[I");
jintArray intarray_obj = static_cast<jintArray>(env->GetObjectField(testobj, intarray_fid));

int arraylength = env->GetArrayLength(intarray_obj);

__android_log_print(4, "kanxue->jni", "arraylength->%d", arraylength);
jint jni_array[arraylength];
for (int j = 0; j < arraylength; j++) {
jni_array[j] = 10 - j;
}
const jint *ptr = jni_array;
env->SetIntArrayRegion(intarray_obj, 0, arraylength, ptr);

int *array = env->GetIntArrayElements(intarray_obj, nullptr);
for (int i = 0; i < arraylength; i++) {

__android_log_print(4, "kanxue->jni", "array[%d]->%d", i, array[i]);
}

}

非静态函数

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
extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_callNonStaticFunc(
JNIEnv *env,
jobject obj/* this */) {

jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");

jmethodID con_mid = env->GetMethodID(TestJclass, "<init>", "(Ljava/lang/String;I)V");

jstring arg0 = env->NewStringUTF("i am from callInit");
// public void publicFunc()
jobject testobj = env->NewObject(TestJclass, con_mid, arg0, 100);
jmethodID publicFunc_mid = env->GetMethodID(TestJclass, "publicFunc", "()V");
env->CallVoidMethod(testobj, publicFunc_mid);
jmethodID privatetest = env->GetMethodID(TestJclass, "privatetest", "(ILjava/lang/String;)Ljava/lang/String;");

jstring arg1 = env->NewStringUTF("i am from jni");

jvalue args[2];
args[0].i = 200;
args[1].l = arg1;
jstring result_obj = static_cast<jstring>(env->CallObjectMethodA(testobj, privatetest, args));
const char *result_ptr = env->GetStringUTFChars(result_obj, nullptr);
__android_log_print(4, "kanxue->jni", "privatetest->%s", result_ptr);

jmethodID privateFunc_array_mid = env->GetMethodID(TestJclass, "privateFunc_array", "(I)[I");

jintArray array_obj = static_cast<jintArray>(env->CallObjectMethod(testobj, privateFunc_array_mid, 100));

jint *array_ptr = env->GetIntArrayElements(array_obj, nullptr);
for (int i = 0; i < env->GetArrayLength(array_obj); i++) {
__android_log_print(4, "kanxue->jni", "array[%d]->%d", i, array_ptr[i]);
}

}

onCreate

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
//protected native void onCreate(Bundle savedInstanceState);
extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_onCreate(
JNIEnv *env,
jobject thiz, jobject bundle) {

/* super.onCreate(savedInstanceState);
Log.i("kanxue","onCreate is Called!");
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
Test testobj = (Test) callInit();
Log.i("kanxue", testobj.flag);*/
jclass AppCompatActivity_jclass1 = env->FindClass("androidx/appcompat/app/AppCompatActivity");
jclass MainActivity_jclass1 = env->FindClass("com/kanxue/reflectiontest/MainActivity");
jclass MainActivity_jclass2 = env->GetObjectClass(thiz);
jclass AppCompatActivity_jclass2 = env->GetSuperclass(MainActivity_jclass2);
// protected void onCreate(Bundle savedInstanceState)
jmethodID superClassOnCreate_mid = env->GetMethodID(AppCompatActivity_jclass2, "onCreate", "(Landroid/os/Bundle;)V");
env->CallNonvirtualVoidMethod(thiz, AppCompatActivity_jclass2, superClassOnCreate_mid, bundle);
jstring arg1 = env->NewStringUTF("kanxue");
// Log.i("kanxue","onCreate is Called")
jstring arg2 = env->NewStringUTF("native onCreate is called!");
jclass Logjclass = env->FindClass("android/util/Log");
// public static int i(String tag,String msg)
jmethodID Log_i_mid = env->GetStaticMethodID(Logjclass, "i", "(Ljava/lang/String;Ljava/lang/String;)I");

jint result0 = env->CallStaticIntMethod(Logjclass, Log_i_mid, arg1, arg2);
__android_log_print(4, "kanxue->jni", "jint result0=env->CallStaticIntMethod(Logjclass,Log_i_mid,arg1,arg2);->%d", result0);

}


/*TextView tv = findViewById(R.id.sample_text);
Test testobj = (Test) callInit();
Log.i("kanxue", testobj.flag);*/
jmethodID setContentView_mid = env->GetMethodID(MainActivity_jclass2, "setContentView", "(I)V");

jclass R_layoutjclass = env->FindClass("com/kanxue/reflectiontest/R$layout");
jfieldID activity_main_fieldid = env->GetStaticFieldID(R_layoutjclass, "activity_main", "I");
jint activity_main_value = env->GetStaticIntField(R_layoutjclass, activity_main_fieldid);

env->CallVoidMethod(thiz, setContentView_mid, activity_main_value);
// TextView tv = findViewById(R.id.sample_text);
jmethodID findViewById_mid = env->GetMethodID(MainActivity_jclass2, "findViewById", "(I)Landroid/view/View;");

jclass R_idjclass = env->FindClass("com/kanxue/reflectiontest/R$id");
jfieldID sample_text_fieldid = env->GetStaticFieldID(R_idjclass, "sample_text", "I");
jint sample_text_value = env->GetStaticIntField(R_idjclass, sample_text_fieldid);
jobject tvobj = env->CallObjectMethod(thiz, findViewById_mid, sample_text_value);

// Test testobj=(Test) callInit();
// Log.i("kanxue",testobj.flag);
jmethodID callInit_mid = env->GetMethodID(MainActivity_jclass2, "callInit", "()Ljava/lang/Object;");
jobject testobj = env->CallObjectMethod(thiz, callInit_mid);
jclass testjclass = env->FindClass("com/kanxue/reflectiontest/Test");
jfieldID flagjfield = env->GetFieldID(testjclass, "flag", "Ljava/lang/String;");
jstring flagvalue = static_cast<jstring>(env->GetObjectField(testobj, flagjfield));

jint result1 = env->CallStaticIntMethod(Logjclass, Log_i_mid, arg1, flagvalue);
__android_log_print(4, "kanxue->jni", " jint result1=env->CallStaticIntMethod(Logjclass,Log_i_mid,arg1,flagvalue);->%d", result1);

env->DeleteGlobalRef(testjclass);

引用

Java当中的内存管理:透明的当新建类的实例时,值需要在创建完这个类的实例之后,拿着这个引用访问它的所有数据成员(属性及方法)就可以了,Java来说有一儿垃圾回收线程即GC线程负责将一些不在使用的对象回收。

C/C++中的内存管理:需要编码人员自己进行内存管理,如在C++中new一个对象,使用完了还要做一次delete操作,malloc一次同样也要调用free来释放响应的内存,否则就会有内存泄漏。

局部引用

通过NewLocalRef和各种JNI接口创建(FindClass,NewObject,GetObjectClass和NewCharArray等),会阻止GC回收所引用的对象。局部引用只能在当前函数中使用,函数返回后局部引用所引用的对象会被JVM自动释放,或调用DeleteLocalRef手动释放,因此,局部引用不能跨函数使用,不能跨线程使用

  • 函数返回时自动释放
  • 手动调用deletelocalref释放
  • 使用jni提供的一系列函数来管理局部引用的生命周期,ensurelocalcapacity,newlocalref,pushlocalframe,poplocalframe,deletelocalref

如果需要创建更多的引用,可以通过调用ensurelocalcapacity函数,确保当前线程中创建指定数量的局部引用,如果创建成功则返回0否则创建失败,并抛出outofmemoryerror异常

1
2
3
4
5
6
7
8
9
10
11
12
for (int i = 0; i < 2048; i++) {
jstring jstr = env->NewStringUTF("12345");
LOGI(" local Reference is Num %d : ", i );
}

if (env->EnsureLocalCapacity(1024) == 0) {
for (int i = 0; i < len; i++) {
jstring content = env->NewStringUTF("test localreference");
__android_log_print(4, "kanxue->jni", "localreference number->%d",
env->DeleteLocalRef(content);
}
}

全局引用

调用NewGlobalRef基于局部引用创建阻GC回收所引用的对象。全局引用可以跨函数,跨线程使用。ART不会自动释放,必须调用DeleteGlobalRef手动释放,DeleteGlobalRef(g_cls_string),否则会出现内存泄漏。

jclass tmpjclass = env->FindClass("com/kanxue/reflectiontest/Test");
testjclass = static_cast<jclass>(env->NewGlobalRef(tmpjclass));

弱全局引用

调用NewWeakGlobalRef基于局部引用或全局引用创建不会阻止GC回收所引用的对象,可以跨方法,跨线程使用。但与全局引用很重要不同的一点是,弱引用不会组织GC回收它引用的对象,但是引用也不会自动释放,在ART认为应该回收它的时候(比如内存紧张的时候),进行回收而被释放,或调用DeleteWeakGlobalRef手动释放。

Java层函数在调用本地jni代码的时候,会维护一个局部引用表(该引用表并不是无限的),一般jni函数调用结束后,ART会释放这个引用,如果是简单的函数就不需要注意这些问题,让他们自己释放,基本没什么问题,但是如果函数里面有注入大量的循环的操作的话,那么程序可能会因为局部引用太多而出现异常情况。

PushLocalFrame可以为当前函数中需要用到的局部引用创建一个引用堆栈,而PopLocalFrame负责销毁栈中所有的引用。因此push、poplocalframe函数对提供了对局部引用生命周期更方便的管理,而不需要时刻关注获取一个引用后,再调用deletelocalref来释放引用,在调用poplocalframe销毁当前frame中的所有引用前,如果第二个参数的result不为空,会有result生成一个新的局部引用,再把这个新生成的局部引用存储到上一个frame中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
jint testPushAndPopLocalFrame(JNIEnv *env) {
jobject result = nullptr;
if (env->PushLocalFrame(20) == 0) {
for (int i = 0; i < 18; i++) {
jstring tmp = env->NewStringUTF("kanxue");
}
jstring tmp1 = env->NewStringUTF("result1");
jstring tmp2 = env->NewStringUTF("result2");
result = env->PopLocalFrame(NULL);

} else {

//error
}

return 100;
}

动态注册

extern “C” JNIEXPORT 静态注册时,IDA可直接反编译函数,且函数名很长。对于任意一个jni函数来说,在该函数被调用前,必须要完成java函数与so中地址的绑定,这个绑定过程是被动的,即由Dalvik、Art虚拟机在调用前查找并完成地址的绑定,所以静态函数名JAVA+包名+类名+方法名,简单明了单不够安全,名字过长,很容易被反编译软件直接定位地址。也可以是主动的,即由app自己完成地址的动态绑定。

动态注册通过RedisterNatives方法手动完成native方法和so中方法的绑定,虚拟机通过函数映射关系直接找到响应方法。通常我们在JNI_Onload方法中完成动态注册,事实上只需要在jni函数被调用前的任意时机完成注册即可,甚至多次注册到不同地址都可以。

RegisterNatives函数返回值成功则返回JNI_OK(0),失败则返回一个负值,需要三个参数:注册jni函数所属的jclass,JNINativeMethod数组指针,注册的jni函数个数。

image-20220420085920007

MainActivity

1
2
Test testobj=new Test();
getNonStaticField(testobj);

重复绑定

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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
extern "C" void _init(void) {
__android_log_print(4, "jni", "%s", "enter _init");

}

__attribute__ ((constructor(4), visibility ("hidden"))) void initarray_1(void) {
__android_log_print(4, "jni", "%s", "enter initarray_1");
}

__attribute__ ((constructor(3), visibility ("hidden"))) void initarray_2(void) {
__android_log_print(4, "jni", "%s", "enter initarray_2");
}

__attribute__ ((constructor(2), visibility ("hidden"))) void initarray_3(void) {
__android_log_print(4, "jni", "%s", "enter initarray_3");
}

__attribute__ ((constructor(1), visibility ("hidden"))) void initarray_4(void) {
__android_log_print(4, "jni", "%s", "enter initarray_4");
}

//protected native void onCreate(Bundle savedInstanceState);
__attribute__ ((visibility ("hidden"))) void onCreate(
JNIEnv *env,
jobject thiz, jobject bundle) {

/* super.onCreate(savedInstanceState);
Log.i("kanxue","onCreate is Called!");
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
Test testobj = (Test) callInit();
Log.i("kanxue", testobj.flag);*/
jclass AppCompatActivity_jclass1 = env->FindClass("androidx/appcompat/app/AppCompatActivity");

jclass MainActivity_jclass1 = env->FindClass("com/kanxue/reflectiontest/MainActivity");

jclass MainActivity_jclass2 = env->GetObjectClass(thiz);

jclass AppCompatActivity_jclass2 = env->GetSuperclass(MainActivity_jclass2);

jmethodID superClassOnCreate_mid = env->GetMethodID(AppCompatActivity_jclass2, "onCreate",
"(Landroid/os/Bundle;)V");

env->CallNonvirtualVoidMethod(thiz, AppCompatActivity_jclass2, superClassOnCreate_mid, bundle);
jstring arg1 = env->NewStringUTF("kanxue");
jstring arg2 = env->NewStringUTF("native onCreate is called!");
jclass Logjclass = env->FindClass("android/util/Log");
jmethodID Log_i_mid = env->GetStaticMethodID(Logjclass, "i",
"(Ljava/lang/String;Ljava/lang/String;)I");

jint result0 = env->CallStaticIntMethod(Logjclass, Log_i_mid, arg1, arg2);
__android_log_print(4, "kanxue->jni",
"jint result0=env->CallStaticIntMethod(Logjclass,Log_i_mid,arg1,arg2);->%d",
result0);
jmethodID setContentView_mid = env->GetMethodID(MainActivity_jclass2, "setContentView", "(I)V");

jclass R_layoutjclass = env->FindClass("com/kanxue/reflectiontest/R$layout");
jfieldID activity_main_fieldid = env->GetStaticFieldID(R_layoutjclass, "activity_main", "I");
jint activity_main_value = env->GetStaticIntField(R_layoutjclass, activity_main_fieldid);

env->CallVoidMethod(thiz, setContentView_mid, activity_main_value);
jmethodID findViewById_mid = env->GetMethodID(MainActivity_jclass2, "findViewById",
"(I)Landroid/view/View;");

jclass R_idjclass = env->FindClass("com/kanxue/reflectiontest/R$id");
jfieldID sample_text_fieldid = env->GetStaticFieldID(R_idjclass, "sample_text", "I");
jint sample_text_value = env->GetStaticIntField(R_idjclass, sample_text_fieldid);

jobject tvobj = env->CallObjectMethod(thiz, findViewById_mid, sample_text_value);
jmethodID callInit_mid = env->GetMethodID(MainActivity_jclass2, "callInit",
"()Ljava/lang/Object;");
jobject testobj = env->CallObjectMethod(thiz, callInit_mid);
jfieldID flagjfield = env->GetFieldID(testjclass, "flag", "Ljava/lang/String;");
jstring flagvalue = static_cast<jstring>(env->GetObjectField(testobj, flagjfield));

jint result1 = env->CallStaticIntMethod(Logjclass, Log_i_mid, arg1, flagvalue);
__android_log_print(4, "kanxue->jni",
" jint result1=env->CallStaticIntMethod(Logjclass,Log_i_mid,arg1,flagvalue);->%d",
result1);

env->DeleteWeakGlobalRef(testjclass);

}

extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_callNonStaticFunc(
JNIEnv *env,
jobject obj/* this */) {

jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");

jmethodID con_mid = env->GetMethodID(TestJclass, "<init>", "(Ljava/lang/String;I)V");

jstring arg0 = env->NewStringUTF("i am from callInit");

jobject testobj = env->NewObject(TestJclass, con_mid, arg0, 100);
jmethodID publicFunc_mid = env->GetMethodID(TestJclass, "publicFunc", "()V");
env->CallVoidMethod(testobj, publicFunc_mid);
jmethodID privatetest = env->GetMethodID(TestJclass, "privatetest",
"(ILjava/lang/String;)Ljava/lang/String;");

jstring arg1 = env->NewStringUTF("i am from jni");

jvalue args[2];
args[0].i = 200;
args[1].l = arg1;
jstring result_obj = static_cast<jstring>(env->CallObjectMethodA(testobj, privatetest, args));
const char *result_ptr = env->GetStringUTFChars(result_obj, nullptr);
__android_log_print(4, "kanxue->jni", "privatetest->%s", result_ptr);

jmethodID privateFunc_array_mid = env->GetMethodID(TestJclass, "privateFunc_array", "(I)[I");

jintArray array_obj = static_cast<jintArray>(env->CallObjectMethod(testobj,
privateFunc_array_mid, 100));

jint *array_ptr = env->GetIntArrayElements(array_obj, nullptr);
for (int i = 0; i < env->GetArrayLength(array_obj); i++) {
__android_log_print(4, "kanxue->jni", "array[%d]->%d", i, array_ptr[i]);
}


}
extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_callStaticFunc(
JNIEnv *env,
jobject /* this */) {
//public static void publicStaticFunc()

jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");

jmethodID publicStaticFunc_mid = env->GetStaticMethodID(TestJclass, "publicStaticFunc", "()V");

env->CallStaticVoidMethod(TestJclass, publicStaticFunc_mid);


//private static void privateStaticFunc()

jmethodID privateStaticFunc_mid = env->GetStaticMethodID(TestJclass, "privateStaticFunc",
"()V");

env->CallStaticVoidMethod(TestJclass, privateStaticFunc_mid);

/* public static int publicStaticFunc_int(int a) {
Log.i("kanxue", "i am from publicStaticFunc_int");
return 100+a;
}*/
jmethodID publicStaticFunc_int_mid = env->GetStaticMethodID(TestJclass, "publicStaticFunc_int",
"(I)I");

jint result_int = env->CallStaticIntMethod(TestJclass, publicStaticFunc_int_mid, 200);
__android_log_print(4, "kanxue->jni", "publicStaticFunc_int->%d", result_int);
/*public static String publicStaticFunc_string(String arg) {
Log.i("kanxue", "i am from publicStaticFunc_int");
return "publicStaticFunc_string->"+arg;
}*/
jmethodID publicStaticFunc_string_mid = env->GetStaticMethodID(TestJclass,
"publicStaticFunc_string",
"(Ljava/lang/String;)Ljava/lang/String;");

jstring arg_string = env->NewStringUTF("i am from jni");
jstring result_string = static_cast<jstring>(env->CallStaticObjectMethod(TestJclass,
publicStaticFunc_string_mid,
arg_string));

const char *content = env->GetStringUTFChars(result_string, nullptr);
__android_log_print(4, "kanxue->jni", "publicStaticFunc_string->%s", content);


}
extern "C" JNIEXPORT jobject JNICALL
Java_com_kanxue_reflectiontest_MainActivity_callInit(
JNIEnv *env,
jobject /* this */) {

jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");

jmethodID con_mid = env->GetMethodID(TestJclass, "<init>", "(Ljava/lang/String;I)V");

jstring arg0 = env->NewStringUTF("i am from callInit");

jobject obj = env->NewObject(TestJclass, con_mid, arg0, 100);
jmethodID privatetest_mid = env->GetMethodID(TestJclass, "privatetest",
"(ILjava/lang/String;)Ljava/lang/String;");
jstring arg2 = env->NewStringUTF("i am from jni-CallObjectMethodA");

return obj;

}
extern "C" JNIEXPORT jstring JNICALL
Java_com_kanxue_reflectiontest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
JavaVM *thisvm = nullptr;
env->GetJavaVM(&thisvm);

__android_log_print(4, "kanxue->jni", "env->GetJavaVM(&thisvm)->%p", thisvm);
__android_log_print(4, "kanxue->jni",
"Java_com_kanxue_reflectiontest_MainActivity_stringFromJNI->%p", env);

std::string hello = "Hello from C++";
jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
jfieldID publicStaticField_jfieldID = env->GetStaticFieldID(TestJclass, "publicStaticField",
"Ljava/lang/String;");
jstring publicStaticField_content = (jstring) env->GetStaticObjectField(TestJclass,
publicStaticField_jfieldID);
const char *content_ptr = env->GetStringUTFChars(publicStaticField_content, nullptr);
__android_log_print(4, "kanxue->jni", "jni->%s", content_ptr);

return env->NewStringUTF(hello.c_str());
}

void newObject(JNIEnv *env, jobject obj) {


}

extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_newObject(
JNIEnv *env,
jobject /* this */) {
//NewObject
jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
jmethodID con_mid = env->GetMethodID(TestJclass, "<init>", "(Ljava/lang/String;)V");
jstring arg = env->NewStringUTF("I am From Jni");
jobject testobj = env->NewObject(TestJclass, con_mid, arg);
if (testobj != nullptr) {
__android_log_print(4, "kanxue->jni", "jni->%s", "NewObject success!");
}
//AllocObject
jobject testobj2 = env->AllocObject(TestJclass);
jstring arg1 = env->NewStringUTF("I am From Jni->AllocObject");
env->CallNonvirtualVoidMethod(testobj2, TestJclass, con_mid, arg1);
if (testobj2 != nullptr) {

__android_log_print(4, "kanxue->jni", "jni->%s", "AllocObject success!");
}
jmethodID privatetest_mid = env->GetMethodID(TestJclass, "privatetest",
"(ILjava/lang/String;)Ljava/lang/String;");
jstring arg2 = env->NewStringUTF("i am from jni");
jobject result = env->CallObjectMethod(testobj2, privatetest_mid, 100, arg2);


}


extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_getStaticField(
JNIEnv *env,
jobject /* this */) {
//private static
jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
// public static String publicStaticField = "i am a publicStaticField";
// jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
jfieldID publicStaticField_fid = env->GetStaticFieldID(TestJclass, "publicStaticField",
"Ljava/lang/String;");
/*jstring publicStaticField_obj = static_cast<jstring>(env->GetStaticObjectField(TestJclass,
publicStaticField_fid));
const char *publicStaticField_content = env->GetStringUTFChars(publicStaticField_obj, nullptr);
__android_log_print(4, "kanxue->jni", "publicStaticField_obj->%s", publicStaticField_content);*/

jstring setjstring = env->NewStringUTF("modified by jni");
// void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value)
env->SetStaticObjectField(TestJclass, publicStaticField_fid, setjstring);
jstring publicStaticField_obj = static_cast<jstring>(env->GetStaticObjectField(TestJclass,
publicStaticField_fid));
const char *publicStaticField_content = env->GetStringUTFChars(publicStaticField_obj, nullptr);
__android_log_print(4, "kanxue->jni", "publicStaticField_obj->%s", publicStaticField_content);

//private static String privateStaticField = "i am a privateStaticField";
jfieldID privateStaticField_fid = env->GetStaticFieldID(TestJclass, "privateStaticField",
"Ljava/lang/String;");
jstring privateStaticField_obj = static_cast<jstring>(env->GetStaticObjectField(TestJclass,
privateStaticField_fid));
const char *privateStaticField_content = env->GetStringUTFChars(privateStaticField_obj,
nullptr);
__android_log_print(4, "kanxue->jni", "privateStaticField_obj->%s", privateStaticField_content);

// public static int publicStaticField_int = 100;

jfieldID publicStaticField_int_fid = env->GetStaticFieldID(TestJclass, "publicStaticField_int",
"I");


env->SetStaticIntField(TestJclass, publicStaticField_int_fid, 200);

jint publicStaticField_int_value = env->GetStaticIntField(TestJclass,
publicStaticField_int_fid);

__android_log_print(4, "kanxue->jni", "publicStaticField_int_value->%d",
publicStaticField_int_value);


}

__attribute__ ((visibility ("hidden"))) void cccc(
JNIEnv *env,
jclass obj, jobject testobj) {

// private String privateField = "i am a privateField";

jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
jfieldID privateField_fid = env->GetFieldID(TestJclass, "privateField", "Ljava/lang/String;");
// jobject GetObjectField(jobject obj, jfieldID fieldID)
jstring privateField_obj = static_cast<jstring>(env->GetObjectField(testobj, privateField_fid));

jstring newString = env->NewStringUTF("Modified by jni");
env->SetObjectField(testobj, privateField_fid, newString);


privateField_obj = static_cast<jstring>(env->GetObjectField(testobj, privateField_fid));
const char *privateField_obj_content = env->GetStringUTFChars(privateField_obj,
nullptr);
__android_log_print(4, "kanxue->jni", "privateField_obj->%s", privateField_obj_content);
jfieldID privateField_int_fid = env->GetFieldID(TestJclass, "privateField_int", "I");
env->SetIntField(testobj, privateField_int_fid, 300);
jint privateField_int_value = env->GetIntField(testobj, privateField_int_fid);
__android_log_print(4, "kanxue->jni", "privateField_int_value->%d", privateField_int_value);

// public int[] intarray = null;
jfieldID intarray_fid = env->GetFieldID(TestJclass, "intarray", "[I");
jintArray intarray_obj = static_cast<jintArray>(env->GetObjectField(testobj, intarray_fid));

int arraylength = env->GetArrayLength(intarray_obj);

__android_log_print(4, "kanxue->jni", "arraylength->%d", arraylength);
jint jni_array[arraylength];
for (int j = 0; j < arraylength; j++) {

jni_array[j] = 10 - j;
}
const jint *ptr = jni_array;
env->SetIntArrayRegion(intarray_obj, 0, arraylength, ptr);

int *array = env->GetIntArrayElements(intarray_obj, nullptr);
for (int i = 0; i < arraylength; i++) {

__android_log_print(4, "kanxue->jni", "array[%d]->%d", i, array[i]);
}


}

__attribute__ ((visibility ("hidden"))) void dddd(
JNIEnv *env,
jclass obj, jobject testobj) {

// private String privateField = "i am a privateField";

jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
jfieldID privateField_fid = env->GetFieldID(TestJclass, "privateField", "Ljava/lang/String;");
// jobject GetObjectField(jobject obj, jfieldID fieldID)
jstring privateField_obj = static_cast<jstring>(env->GetObjectField(testobj, privateField_fid));

jstring newString = env->NewStringUTF("Modified by jni");
env->SetObjectField(testobj, privateField_fid, newString);


privateField_obj = static_cast<jstring>(env->GetObjectField(testobj, privateField_fid));
const char *privateField_obj_content = env->GetStringUTFChars(privateField_obj,
nullptr);
__android_log_print(4, "kanxue->jni", "privateField_obj->%s", privateField_obj_content);
jfieldID privateField_int_fid = env->GetFieldID(TestJclass, "privateField_int", "I");
env->SetIntField(testobj, privateField_int_fid, 300);
jint privateField_int_value = env->GetIntField(testobj, privateField_int_fid);
__android_log_print(4, "kanxue->jni", "privateField_int_value->%d", privateField_int_value);

// public int[] intarray = null;
jfieldID intarray_fid = env->GetFieldID(TestJclass, "intarray", "[I");
jintArray intarray_obj = static_cast<jintArray>(env->GetObjectField(testobj, intarray_fid));

int arraylength = env->GetArrayLength(intarray_obj);

__android_log_print(4, "kanxue->jni", "arraylength->%d", arraylength);
jint jni_array[arraylength];
for (int j = 0; j < arraylength; j++) {

jni_array[j] = 10 - j;
}
const jint *ptr = jni_array;
env->SetIntArrayRegion(intarray_obj, 0, arraylength, ptr);

int *array = env->GetIntArrayElements(intarray_obj, nullptr);
for (int i = 0; i < arraylength; i++) {

__android_log_print(4, "kanxue->jni", "array[%d]->%d", i, array[i]);
}


}

jint testPushAndPopLocalFrame(JNIEnv *env) {
jobject result = nullptr;
if (env->PushLocalFrame(20) == 0) {
for (int i = 0; i < 18; i++) {
jstring tmp = env->NewStringUTF("kanxue");
}
jstring tmp1 = env->NewStringUTF("result1");
jstring tmp2 = env->NewStringUTF("result2");
result = env->PopLocalFrame(NULL);

} else {

//error
}

return 100;


}

// 重复绑定
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
globalVM = vm;
int result = 0;
__android_log_print(4, "kanxue->jni", "JNI_OnLoad(JavaVM *vm, void *reserved)->%p", vm);
__android_log_print(4, "kanxue->jni", "jni->%s", "JNI_OnLoad is called");
JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) == JNI_OK) {
__android_log_print(4, "kanxue->jni", "jni->%s",
"vm->GetEnv((void**)&env,JNI_VERSION_1_6) success");
}

JNINativeMethod jniNativeMethod[] = {{"onCreate", "(Landroid/os/Bundle;)V", (void *) onCreate},
{"getNonStaticField", "(Ljava/lang/Object;)V", (void *) cccc}
};
jclass MainActivityjclass = env->FindClass("com/kanxue/reflectiontest/MainActivity");

env->RegisterNatives(MainActivityjclass, jniNativeMethod,
sizeof(jniNativeMethod) / sizeof(JNINativeMethod));

JNINativeMethod jniNativeMethod2[] = {{"onCreate", "(Landroid/os/Bundle;)V", (void *) onCreate},
{"getNonStaticField", "(Ljava/lang/Object;)V", (void *) dddd}
};
env->RegisterNatives(MainActivityjclass, jniNativeMethod2,
sizeof(jniNativeMethod2) / sizeof(JNINativeMethod));

jclass tmpjclass = env->FindClass("com/kanxue/reflectiontest/Test");
testjclass = static_cast<jclass>(env->NewWeakGlobalRef(tmpjclass));


jint result_jint = testPushAndPopLocalFrame(env);
__android_log_print(4, "kanxue->jni", "testPushAndPopLocalFrame->%d",
result_jint);
result = JNI_VERSION_1_6;
return result;


}

Dalvik

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
709static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
710 const char* signature, void* fnPtr)
711{
712 if (fnPtr == NULL) {
713 return false;
714 }
715
716 // If a signature starts with a '!', we take that as a sign that the native code doesn't
717 // need the extra JNI arguments (the JNIEnv* and the jclass).
718 bool fastJni = false;
719 if (*signature == '!') {
720 fastJni = true;
721 ++signature;
722 ALOGV("fast JNI method %s.%s:%s detected", clazz->descriptor, methodName, signature);
723 }
724
725 Method* method = dvmFindDirectMethodByDescriptor(clazz, methodName, signature);
726 if (method == NULL) {
727 method = dvmFindVirtualMethodByDescriptor(clazz, methodName, signature);
728 }
729 if (method == NULL) {
730 dumpCandidateMethods(clazz, methodName, signature);
731 throwNoSuchMethodError(clazz, methodName, signature, "static or non-static");
732 return false;
733 }
734
735 if (!dvmIsNativeMethod(method)) {
736 ALOGW("Unable to register: not native: %s.%s:%s", clazz->descriptor, methodName, signature);
737 throwNoSuchMethodError(clazz, methodName, signature, "native");
738 return false;
739 }
740
741 if (fastJni) {
742 // In this case, we have extra constraints to check...
743 if (dvmIsSynchronizedMethod(method)) {
744 // Synchronization is usually provided by the JNI bridge,
745 // but we won't have one.
746 ALOGE("fast JNI method %s.%s:%s cannot be synchronized",
747 clazz->descriptor, methodName, signature);
748 return false;
749 }
750 if (!dvmIsStaticMethod(method)) {
751 // There's no real reason for this constraint, but since we won't
752 // be supplying a JNIEnv* or a jobject 'this', you're effectively
753 // static anyway, so it seems clearer to say so.
754 ALOGE("fast JNI method %s.%s:%s cannot be non-static",
755 clazz->descriptor, methodName, signature);
756 return false;
757 }
758 }
759
760 if (method->nativeFunc != dvmResolveNativeMethod) {
761 /* this is allowed, but unusual 一个JNI函数注册到不同的地址,即被注册了多次 */
762 ALOGE("dvmRegisterJNIMethod: %s.%s:%s was already registered", clazz->descriptor, methodName, signature);
763 }
764
765 method->fastJni = fastJni;
766 dvmUseJNIBridge(method, fnPtr);
767
768 ALOGV("JNI-registered %s.%s:%s", clazz->descriptor, methodName, signature);
769 return true;
770}
806void dvmUseJNIBridge(Method* method, void* func) {
807 method->shouldTrace = shouldTrace(method);
808
809 // Does the method take any reference arguments?
810 method->noRef = true;
811 const char* cp = method->shorty;
812 while (*++cp != '\0') { // Pre-increment to skip return type.
813 if (*cp == 'L') {
814 method->noRef = false;
815 break;
816 }
817 }
818
819 DalvikBridgeFunc bridge = gDvmJni.useCheckJni ? dvmCheckCallJNIMethod : dvmCallJNIMethod;
820 dvmSetNativeFunc(method, bridge, (const u2*) func);
821}
4559void dvmSetNativeFunc(Method* method, DalvikBridgeFunc func,
4560 const u2* insns)
4561{
4562 ClassObject* clazz = method->clazz;
4563
4564 assert(func != NULL);
4565
4566 /* just open up both; easier that way */
4567 dvmLinearReadWrite(clazz->classLoader, clazz->virtualMethods);
4568 dvmLinearReadWrite(clazz->classLoader, clazz->directMethods);
4569
4570 if (insns != NULL) {
4571 /* update both, ensuring that "insns" is observed first */
4572 method->insns = insns;
4573 android_atomic_release_store((int32_t) func,
4574 (volatile int32_t*)(void*) &method->nativeFunc);
4575 } else {
4576 /* only update nativeFunc */
4577 method->nativeFunc = func;
4578 }
4579
4580 dvmLinearReadOnly(clazz->classLoader, clazz->virtualMethods);
4581 dvmLinearReadOnly(clazz->classLoader, clazz->directMethods);
4582}

method->nativeFunc = func;动态注册就是为了完成java函数在Dalvik中的method结构体中nativeFunc指针的绑定。

接下来通过手动修改aosp源码添加log

Jni.cpp

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
2459static jint RegisterNatives(JNIEnv* env, jclass jclazz,
2460 const JNINativeMethod* methods, jint nMethods)
2461{
2462 ScopedJniThreadState ts(env);
2463
2464 ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(ts.self(), jclazz);
2465
2466 if (gDvm.verboseJni) {
2467 ALOGI("[Registering JNI native methods for class %s]",
2468 clazz->descriptor);
2469 }
2470
2471 for (int i = 0; i < nMethods; i++) {
ALOGE("[Registering Class:%s,methodname:%s,sig:%s,addr:%p]",clazz->descriptor,methods[i].name,methods[i].signature, methods[i].fnPtr);
2472 if (!dvmRegisterJNIMethod(clazz, methods[i].name,
2473 methods[i].signature, methods[i].fnPtr))
2474 {
2475 return JNI_ERR;
2476 }
2477 }
2478 return JNI_OK;
2479}


709static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
710 const char* signature, void* fnPtr)
711{
712 if (fnPtr == NULL) {
713 return false;
714 }
ALOGE("[dvmRegisterJNIMethod Class:%s,methodname:%s,sig:%s,addr:%p]",clazz->descriptor,methodName,signature, fnPtr);
715
716 // If a signature starts with a '!', we take that as a sign that the native code doesn't
717 // need the extra JNI arguments (the JNIEnv* and the jclass).
718 bool fastJni = false;
719 if (*signature == '!') {
720 fastJni = true;
721 ++signature;
722 ALOGV("fast JNI method %s.%s:%s detected", clazz->descriptor, methodName, signature);
723 }
...
760 if (method->nativeFunc != dvmResolveNativeMethod) {
761 /* this is allowed, but unusual */
762 ALOGV("Note: %s.%s:%s was already registered", clazz->descriptor, methodName, signature);
763 }
765 method->fastJni = fastJni;
766 dvmUseJNIBridge(method, fnPtr);
767
768 ALOGV("JNI-registered %s.%s:%s", clazz->descriptor, methodName, signature);
769 return true;
770}


806void dvmUseJNIBridge(Method* method, void* func) {
807 method->shouldTrace = shouldTrace(method);
ALOGE("[dvmUseJNIBridge Class:%s,methodname:%s,addr:%p]",clazz->descriptor,method->name,func);
808
809 // Does the method take any reference arguments?
810 method->noRef = true;
811 const char* cp = method->shorty;
812 while (*++cp != '\0') { // Pre-increment to skip return type.
813 if (*cp == 'L') {
814 method->noRef = false;
815 break;
816 }
817 }
819 DalvikBridgeFunc bridge = gDvmJni.useCheckJni ? dvmCheckCallJNIMethod : dvmCallJNIMethod;
820 dvmSetNativeFunc(method, bridge, (const u2*) func);
821}

Class.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
4559void dvmSetNativeFunc(Method* method, DalvikBridgeFunc func,
4560 const u2* insns)
4561{
4562 ClassObject* clazz = method->clazz;
4563
4564 assert(func != NULL);
4565
4566 /* just open up both; easier that way */
4567 dvmLinearReadWrite(clazz->classLoader, clazz->virtualMethods);
4568 dvmLinearReadWrite(clazz->classLoader, clazz->directMethods);
4570 if (insns != NULL) {
4571 /* update both, ensuring that "insns" is observed first */
4572 method->insns = insns;
4573 android_atomic_release_store((int32_t) func,
4574 (volatile int32_t*)(void*) &method->nativeFunc);
4575 } else {
4576 /* only update nativeFunc */
ALOGE("[dvmSetNativeFunc Class:%s,methodname:%s,addr:%p]",method->clazz->descriptor,method->name,func);
4577 method->nativeFunc = func;
4578 }
4579
4580 dvmLinearReadOnly(clazz->classLoader, clazz->virtualMethods);
4581 dvmLinearReadOnly(clazz->classLoader, clazz->directMethods);
4582}

编译好后,重新刷system.img即可。加固app查看日志信息查看Dalvik主动注册原理。

1
2
3
4
5
source build/envsetup.sh
lunch
7 aosp_hammerhead-userdebug
time make -j4
flashboot flash system system.img 启动到bootloader

ART

jni_internal.cc

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
148    static jint RegisterNatives(JNIEnv* env, jclass java_class, const JNINativeMethod* methods,
2149 jint method_count) {
2150 return RegisterNativeMethods(env, java_class, methods, method_count, true);
2151 }

3054 void RegisterNativeMethods(JNIEnv* env, const char* jni_class_name, const JNINativeMethod* methods,
3055 jint method_count) {
3056 ScopedLocalRef<jclass> c(env, env->FindClass(jni_class_name));
3057 if (c.get() == nullptr) {
3058 LOG(FATAL) << "Couldn't find class: " << jni_class_name;
3059 }
3060 JNI::RegisterNativeMethods(env, c.get(), methods, method_count, false);
3061 }


2153 static jint RegisterNativeMethods(JNIEnv* env, jclass java_class, const JNINativeMethod* methods,
2154 jint method_count, bool return_errors) {
2155 if (UNLIKELY(method_count < 0)) {
2156 JavaVmExtFromEnv(env)->JniAbortF("RegisterNatives", "negative method count: %d",
2157 method_count);
2158 return JNI_ERR;
2159 }
2160 CHECK_NON_NULL_ARGUMENT_FN_NAME("RegisterNatives", java_class, JNI_ERR);
2161 ScopedObjectAccess soa(env);
2162 StackHandleScope<1> hs(soa.Self());
2163 Handle<mirror::Class> c = hs.NewHandle(soa.Decode<mirror::Class>(java_class));
2164 if (UNLIKELY(method_count == 0)) {
2165 LOG(WARNING) << "JNI RegisterNativeMethods: attempt to register 0 native methods for "
2166 << c->PrettyDescriptor();
2167 return JNI_OK;
2168 }
2169 CHECK_NON_NULL_ARGUMENT_FN_NAME("RegisterNatives", methods, JNI_ERR);
2170 for (jint i = 0; i < method_count; ++i) {
2171 const char* name = methods[i].name;
2172 const char* sig = methods[i].signature;
2173 const void* fnPtr = methods[i].fnPtr;
2174 if (UNLIKELY(name == nullptr)) {
2175 ReportInvalidJNINativeMethod(soa, c.Get(), "method name", i, return_errors);
2176 return JNI_ERR;
2177 } else if (UNLIKELY(sig == nullptr)) {
2178 ReportInvalidJNINativeMethod(soa, c.Get(), "method signature", i, return_errors);
2179 return JNI_ERR;
2180 } else if (UNLIKELY(fnPtr == nullptr)) {
2181 ReportInvalidJNINativeMethod(soa, c.Get(), "native function", i, return_errors);
2182 return JNI_ERR;
2183 }
2184 bool is_fast = false; ition from kRunnable to kNative at the
2213 // entry.
2214 if (*sig == '!') {
2215 is_fast = true;
2216 ++sig;
2217 }
2218
// 遍历注册前先获取jni函数在内存中ArtMethod对象指针
2222 ArtMethod* m = nullptr;
2223 bool warn_on_going_to_parent = down_cast<JNIEnvExt*>(env)->vm->IsCheckJniEnabled();
2224 for (ObjPtr<mirror::Class> current_class = c.Get();
2225 current_class != nullptr;
2226 current_class = current_class->GetSuperClass()) {
// 通过jni函数名及签名找到当前函数在art虚拟机中的表现显示Arthod,任意一个类的函数在调用前都会被加载,为这个类准备内存变量属性函数等,由class_linker.cc中的LoadClassMembers,先遍历field准备ArtField,其次遍历函数,准备好每个函数中的ArtMethod对象才能够完成调用,不论是java还是jni函数。art_method.h中是具体包括了哪些属性包括偏移,索引等。
2228 m = FindMethod<true>(current_class.Ptr(), name, sig);
2229 if (m != nullptr) {
2230 break;
2231 }
2232
2234 m = FindMethod<false>(current_class.Ptr(), name, sig);
2235 if (m != nullptr) {
2236 break;
2237 }
2238
2239 if (warn_on_going_to_parent) {
2240 LOG(WARNING) << "CheckJNI: method to register \"" << name << "\" not in the given class. "
2241 << "This is slow, consider changing your RegisterNatives calls.";
2242 warn_on_going_to_parent = false;
2243 }
2244 }
2245
2246 if (m == nullptr) {
2247 c->DumpClass(
2248 LOG_STREAM(return_errors
2249 ? ::android::base::ERROR
2250 : ::android::base::FATAL_WITHOUT_ABORT),
2251 mirror::Class::kDumpClassFullDetail);
2252 LOG(return_errors ? ::android::base::ERROR : ::android::base::FATAL)
2253 << "Failed to register native method "
2254 << c->PrettyDescriptor() << "." << name << sig << " in "
2255 << c->GetDexCache()->GetLocation()->ToModifiedUtf8();
2256 ThrowNoSuchMethodError(soa, c.Get(), name, sig, "static or non-static");
2257 return JNI_ERR;
2258 } else if (!m->IsNative()) {
2259 LOG(return_errors ? ::android::base::ERROR : ::android::base::FATAL)
2260 << "Failed to register non-native method "
2261 << c->PrettyDescriptor() << "." << name << sig
2262 << " as native";
2263 ThrowNoSuchMethodError(soa, c.Get(), name, sig, "native");
2264 return JNI_ERR;
2265 }
2266
2267 VLOG(jni) << "[Registering JNI native method " << m->PrettyMethod() << "]";
2268
2269 if (UNLIKELY(is_fast)) {
2276 LOG(WARNING) << "!bang JNI is deprecated. Switch to @FastNative for " << m->PrettyMethod();
2277 is_fast = false;
2279 }
2280
2281 const void* final_function_ptr = m->RegisterNative(fnPtr, is_fast);
2282 UNUSED(final_function_ptr);
2283 }
2284 return JNI_OK;
2285 }



379 const void* ArtMethod::RegisterNative(const void* native_method, bool is_fast) {
380 CHECK(IsNative()) << PrettyMethod();
381 CHECK(!IsFastNative()) << PrettyMethod();
382 CHECK(native_method != nullptr) << PrettyMethod();
383 if (is_fast) {
384 AddAccessFlags(kAccFastNative);
385 }
386 void* new_native_method = nullptr;
387 Runtime::Current()->GetRuntimeCallbacks()->RegisterNativeMethod(this,
388 native_method,
389 /*out*/&new_native_method);
390 SetEntryPointFromJni(new_native_method);
391 return new_native_method;
392 }


495 void SetEntryPointFromJni(const void* entrypoint) {
496 DCHECK(IsNative());
497 SetEntryPointFromJniPtrSize(entrypoint, kRuntimePointerSize);
498 }

495 void SetEntryPointFromJni(const void* entrypoint) {
496 DCHECK(IsNative());
497 SetEntryPointFromJniPtrSize(entrypoint, kRuntimePointerSize);
498 }

500 ALWAYS_INLINE void SetEntryPointFromJniPtrSize(const void* entrypoint, PointerSize pointer_size) {
501 SetDataPtrSize(entrypoint, pointer_size);
502 }

509 ALWAYS_INLINE void SetDataPtrSize(const void* data, PointerSize pointer_size) {
510 DCHECK(IsImagePointerSize(pointer_size));
511 SetNativePointer(DataOffset(pointer_size), data, pointer_size);
512 }

ALWAYS_INLINE void SetNativePointer(MemberOffset offset, T new_value, PointerSize pointer_size) // new_value指针代表设置要绑定的so的函数地址

接下来通过手动修改aosp源码添加log

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
2148    static jint RegisterNatives(JNIEnv* env, jclass java_class, const JNINativeMethod* methods,
2149 jint method_count) {
LOG(WARNING) << "[jni_internal.cc->RegisterNatives]" << method_count;
2150 return RegisterNativeMethods(env, java_class, methods, method_count, true);
2151 }

2153 static jint RegisterNativeMethods(JNIEnv* env, jclass java_class, const JNINativeMethod* methods,
2154 jint method_count, bool return_errors) {
2155 if (UNLIKELY(method_count < 0)) {
2156 JavaVmExtFromEnv(env)->JniAbortF("RegisterNatives", "negative method count: %d",
2157 method_count);
2158 return JNI_ERR; // Not reached except in unit tests.
2159 }
2160 CHECK_NON_NULL_ARGUMENT_FN_NAME("RegisterNatives", java_class, JNI_ERR);
2161 ScopedObjectAccess soa(env);
2162 StackHandleScope<1> hs(soa.Self());
2163 Handle<mirror::Class> c = hs.NewHandle(soa.Decode<mirror::Class>(java_class));
2164 if (UNLIKELY(method_count == 0)) {
2165 LOG(WARNING) << "JNI RegisterNativeMethods: attempt to register 0 native methods for "
2166 << c->PrettyDescriptor();
2167 return JNI_OK;
2168 }
2169 CHECK_NON_NULL_ARGUMENT_FN_NAME("RegisterNatives", methods, JNI_ERR);
2170 for (jint i = 0; i < method_count; ++i) {
2171 const char* name = methods[i].name;
2172 const char* sig = methods[i].signature;
2173 const void* fnPtr = methods[i].fnPtr;
LOG(WARNING) << "[jni_internal.cc->RegisterNativeMethods]" << i <<":methodname:"<< name<<",sig:"<<sig<<",addr:"<<fnPtr;
2174 if (UNLIKELY(name == nullptr)) {
2175 ReportInvalidJNINativeMethod(soa, c.Get(), "method name", i, return_errors);
2176 return JNI_ERR;
2177 } else if (UNLIKELY(sig == nullptr)) {
2178 ReportInvalidJNINativeMethod(soa, c.Get(), "method signature", i, return_errors);
2179 return JNI_ERR;
2180 } else if (UNLIKELY(fnPtr == nullptr)) {
2181 ReportInvalidJNINativeMethod(soa, c.Get(), "native function", i, return_errors);
2182 return JNI_ERR;
2183 }
......
2281 const void* final_function_ptr = m->RegisterNative(fnPtr, is_fast);
2282 UNUSED(final_function_ptr);
2283 }
2284 return JNI_OK;
2285 }




379 const void* ArtMethod::RegisterNative(const void* native_method, bool is_fast) {
// PrettyMethod可以获取到当前的函数名
380 CHECK(IsNative()) << PrettyMethod();
381 CHECK(!IsFastNative()) << PrettyMethod();
382 CHECK(native_method != nullptr) << PrettyMethod();
383 if (is_fast) {
384 AddAccessFlags(kAccFastNative);
385 }
386 void* new_native_method = nullptr;
387 Runtime::Current()->GetRuntimeCallbacks()->RegisterNativeMethod(this,
388 native_method,
389 /*out*/&new_native_method);
LOG(WARNING) << "[art_method.cc->RegisterNative]methodname:" << PrettyMethod() <<":addr:"<< native_method;
390 SetEntryPointFromJni(new_native_method); // 完成具体绑定的工作
391 return new_native_method;
392 }

495 void SetEntryPointFromJni(const void* entrypoint) {
496 DCHECK(IsNative());
LOG(WARNING) << "[art_method.h->ArtMethod::SetEntryPointFromJni]methodname:" << PrettyMethod() <<":addr:"<< entrypoint;
497 SetEntryPointFromJniPtrSize(entrypoint, kRuntimePointerSize); // kRuntimePointerSize表示app运行在32位为4或64位为8模式下指针的大小
498 }

编译刷机

1
2
3
4
source build/envsetup.sh
lunch
23 aosp_sailfish-userdebug
time make -j4

很多加壳时机就是在init/initarray或JNI_Onload中实现。init>initarray>JNI_Onload顺序

文章作者: J
文章链接: http://onejane.github.io/2021/07/25/NDK开发详解之NDK开发简介/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏