java 反射遍历_Java 利用反射遍历类和对象信息

背景

项目中遇到需要监控 System.loadLibrary 方法调用,实现如果链接失败则弹出对话框的「问题」。「解决方案」就是 hook java.lang.Runtime#nativeLoad 方法对应的 JNI 函数入口 art#ArtMethod#ptr_sized_fields#data_ 字段,从而先跳转到我们 hook 的函数再执行系统的 JNI 函数。这个需要在运行时拿到一个 Java 方法的相关信息,比如 Method 类、ArtMethod 类、JNI 函数信息等。反射是能读写一个类、对象的内部数据的一种手段,在此做记录。

问题

我们能不能拿到系统某些 private 字段的信息 ?

我们能不能执行系统某些 private 方法?

我们能不能拦截某些系统的行为?

接下来记录一下使用反射拦截主线程消息队列例子

反射的使用方法

拿到 Class 对象

通过 java.lang.Class#forName(java.lang.String) 拿类名对应的类

通过 java.lang.Object#getClass 拿对象所属的类

通过 java.lang.Class#getSuperclass 拿父类

拿到 Field / Method 对象

拿当前类(不包含父类)字段 java.lang.Class#getDeclaredField

拿当前类方法 java.lang.Class#getDeclaredMethod

读写字段 / 执行方法

读字段 java.lang.reflect.Field#get

写字段 java.lang.reflect.Field#set

执行方法 java.lang.reflect.Method#invoke

拦截主线程消息处理

2ad0aa4152f4887376bad89a85efde40.png

Android Message 处理流程

Android 主线程是设计成事件驱动,Java 层的核心是生产者/消费者模型

主线程属于消费者,负责从 android.os.MessageQueue 中取消息执行

当前进程内的所有线程都可以作为生产者向 android.os.MessageQueue 中投递消息,这样就让某些代码在主线程执行

主线程执行到 android.os.Looper#loop 中时就进入了轮询过程,取消息,执行消息

由于 android.os.Looper#sThreadLocal 变量是 java.lang.ThreadLocal 类型,所以每个 java.lang.Thread 对象至多有一个 android.os.Looper 对象

因此我们需要拿到主线程的 android.os.MessageQueue 和 android.os.Looper 对象

利用 android.os.Looper#getMainLooper 接口可以很简单拿到 Looper 对象

拿到 Looper 对象后可以利用反射遍历对象的所有字段,拿到 android.os.Looper#mQueue 字段

接下来我们模拟 android.os.Looper#loop 内部的实现,反射执行 android.os.MessageQueue#next 方法取得一条消息

找到消息所属的 Handler 对象 android.os.Message#getTarget

给 Handler 派发消息 android.os.Handler#dispatchMessage

这样我们在应用层就能够看到主线程的每条消息,从而改变系统的某些行为,比如某些消息不执行

// 拿到 Looper 对象

Looper mainLooper = Looper.getMainLooper();

// 拿到 MessageQueue 对象

MessageQueue queue = (MessageQueue) Reflection.field(null, null, mainLooper, "mQueue");

while (true){

// 取消息

Message msg = (Message) Reflection.call(null, null, queue, "next", new Class[]{});

if (msg != null){

Log.d(TAG, "run: " + msg);

// 这里拦截消息

...

Handler handler = msg.getTarget();

// 派发消息

handler.dispatchMessage(msg);

}

}

练习题

利用反射遍历类层次结构

// 拿到 Class 对象

Class c = xx;

while (c != null) {

// 读取 c 信息

...

// 指向父类

c = c.getSuperclass();

}

利用反射遍历对象的所有字段和值

Class c = xx;

while (c != null) {

Field[] fields = c.getDeclaredFields();

for (Field f : fields) {

try {

f.setAccessible(true);

Log.d(TAG, f + " = " + f.get(obj));

} catch (Exception e) {

e.printStackTrace();

}

}

c = c.getSuperclass();

}

利用反射修改 private 字段和执行 private 方法

Class c = xx;

// 遍历类结构

while (c != null) {

// 遍历当前类所有字段

for (Field f : c.getDeclaredFields()) {

// 查找指定字段名

if (f.getName().equals(fieldName)) {

f.setAccessible(true);

// 修改字段的值

f.set(o, value);

return;

}

}

c = c.getSuperclass();

}

Class c = xx;

while (c != null) {

Method m = null;

try{

// 在当前类查找

m = c.getDeclaredMethod(name, parameterTypes);

} catch (Throwable e){

Log.e(TAG, "call: ", e);

}

if (m != null){

m.setAccessible(true);

// 执行方法

value = m.invoke(o, args);

break;

}

c = c.getSuperclass();

}

总结

在读写系统私有信息方面,反射是一个不错的技术手段。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值