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

拦截主线程消息处理

Java 利用反射遍历类和对象信息_第1张图片
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();
}

总结

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

你可能感兴趣的:(Java 利用反射遍历类和对象信息)