美文网首页Andorid的好东西
Java 利用反射遍历类和对象信息

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

作者: 1999c1b720cd | 来源:发表于2018-07-23 00:08 被阅读90次

    背景

    项目中遇到需要监控 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

    拦截主线程消息处理

    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 利用反射遍历类和对象信息

        本文链接:https://www.haomeiwen.com/subject/hcmrmftx.html