Android Hook 技术——反射技术

概要

我们在之前的一篇Android-Framework-Plugin插件话框架-Hook Activity过程 中对 Hook技术 进行过基础讲解,Hook技术 就是以 AOP 切面编程思想去实现的底层代理。
Hook中文名"钩子",它使用的是一种面向切面的编程思想(业内专称 AOP编程思想),主要作用是在事件传递过程中对事件进行拦截、修改、监听,将自身的代码动态性替换进去,当这些方法被调用时,保证执行的是我们代码,已达到我们预期的效果

Hook技术实现

  • 反射技术
  • 动态代理

反射技术

Java反射机制是在运行状态中可获取/修改 类/对象的所有的方法和属性,没有公开私有之分;这种可动态获取/修改类或对象的方法和属性 被称为 JAVA反射机制

反射可实现的功能

  • 可获取并修改类任一的属性值
  • 获取对象的所属类
  • 构造任一类的实例
  • 调用类中任一方法
  • 修改类中任一方法的参数

反射的使用

获取Class

如图所示:

实现方法 使用限制
Object.getClass() 不适用于基础类型,如 int、float 等等
ClassName.class 不适用于无法被Import的类,如private Class 或 Android把一些类加上了 @hide 注解,此类无法被正常调用
Class.forName(包名+类名) 无限制
实例Demo

共有实体类:boy.java

public class Boy {
    int age;
    String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private String girlHobby(String mineHobby) {
        String brief = "My name is" + name + ", I'm " + age + "year old.\n";
        String hobby = "我的爱好是" + mineHobby;
        return brief + hobby;
    }
}

私有实体类:girl.java

package com.thtf.leanpackage.plugin_hook.demo;

class Girl {
    int age;
    String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private String girlHobby(String mineHobby) {
        String brief = "My name is" + name + ", I'm " + age + "year old.\n";
        String hobby = "我的爱好是" + mineHobby;
        return brief + hobby;
    }
}

主运行程序 HookMian.java

package com.thtf.leanpackage.plugin_hook;


import com.thtf.leanpackage.plugin_hook.demo.Boy;

public class HookMian {
   public static void main(String[] args) {
       //Object.getClass()
       Boy boy = new Boy();
       Class boyClass1 = boy.getClass();
       System.out.println(boyClass1.getCanonicalName());

       //Class Name.class
       Class boyClass2 = Boy.class;
       System.out.println(boyClass2.getCanonicalName());

       //Class.forName(包名+类名)
       try {
           Class boyClass3 = Class.forName("com.thtf.leanpackage.plugin_hook.demo.Boy");
           Class girlClass = Class.forName("com.thtf.leanpackage.plugin_hook.demo.Girl");
           System.out.println(girlClass.getCanonicalName());
           System.out.println(boyClass3.getCanonicalName());
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
   }
}

通过上面所述的三种方法获取到了Class

通过Class获取类中包含的属性方法

下面我们来列举通过Class的哪些方法获取反射目标类中方法和属性

Class中方法名 使用描述
getDeclaredFields() 获取所有的属性,但不包括从父类继承下来的属性
getDeclaredField(String name) 根据获取name参数指定的属性
getDeclaredMethods() 获取所有的方法,包括 private、public、protected
getDeclaredMethod(String name, Class<?>... parameterTypes) 根据参数name获取特定的方法名,参数parameterTypes代表name方法名中的参数类型,以.class表示,例如String.class,Float.class
getDeclaredConstructors() 返回一个对象数组,反映声明类所有的构造方法
getDeclaredConstructor(Class<?>... parameterTypes) 获取特定的构造方法,参数parameterTypes表示参数类型
getSuperclass() 获取反射目标类的父类
getInterfaces() 获取反射目标类中所有实现的接口
getMethods() 获取自身的所有的 public 方法,包括从父类继承下来的
getMethod(String name, Class<?>... parameterTypes) 根据参数name获取特定的public修饰符下的属性,parameterTypes表示方法传递的参数类型
getFields() 获取自身的所有的 public 属性,包括从父类继承下来的。
getField(String name) 根据参数name获取特定的public修饰符下的属性

Java Demo 案例

package com.thtf.leanpackage.plugin_hook;


import com.thtf.leanpackage.plugin_hook.demo.Boy;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class HookMian {
    public static void main(String[] args) {
        //Object.getClass()
        Boy boy = new Boy();
        Class boyClass1 = boy.getClass();
        System.out.println(boyClass1.getCanonicalName());

        //Class Name.class
        Class boyClass2 = Boy.class;
        System.out.println(boyClass2.getCanonicalName());

        //Class.forName(包名+类名)
        try {
            Class boyClass3 = Class.forName("com.thtf.leanpackage.plugin_hook.demo.Boy");
            Class girlClass = Class.forName("com.thtf.leanpackage.plugin_hook.demo.Girl");
            System.out.println(girlClass.getCanonicalName());
            System.out.println(boyClass3.getCanonicalName());
            //构造器
            Constructor[] boyConstructors = boyClass3.getConstructors();
            for (Constructor c : boyConstructors) {
                System.out.println("getConstructor:" + c.toString());
            }

            boyConstructors = boyClass3.getDeclaredConstructors();
            for (Constructor c : boyConstructors) {
                System.out.println("getDeclaredConstructors:" + c.toString());
            }

            //获取指定方法属性
            Method boyHobby = boyClass3.getDeclaredMethod("boyHobby", String.class);
            Field ageField = boyClass3.getDeclaredField("age");

            System.out.println("获取的方法名:" + boyHobby.getName());
            System.out.println("获取的属性:" + ageField.getName());

            //获取反射类中的公共方法
            Method girlLittleName = girlClass.getMethod("girlLittleName", String.class);
            System.out.println("获取的公共方法名:" + girlLittleName.getName());
            //获取所有方法
            Method[] methods = girlClass.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println("Declared Method :" + method.getName());
            }
            //获取公共方法
            methods=girlClass.getMethods();
            for (Method method : methods) {
                System.out.println("Public Method :" + method.getName());
            }

            //获取所有的属性
            Field[] filed1 = boyClass3.getDeclaredFields();

            for (Field f : filed1) {
                System.out.println("Declared Field :" + f.getName());
            }
            //获取公共的字段属性
            Field[] filed2 = boyClass3.getFields();

            for (Field f : filed2) {
                System.out.println("Field :" + f.getName());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

我们通过反射获取的属性对象类型都是Field类型,而获取的方法对象的类型都是Method类型,接下来我们来讲解一下 Field 和 Method都可以触发那些操作.

Field 和 Method 操作解析

  • Field操作通过反射获取的属性,主要作用读取属性值以及为属性赋值
  • Method操作通过反射获取的方法,反射的核心就在于此;通过调取Methodinvoke()实现对原始方法的代理,从而达到反射的目的.
public class HookMian {
    public static void main(String[] args) {
        try {
            //反射获取Class对象
            Class boyClass3 = Class.forName("com.thtf.leanpackage.plugin_hook.demo.Boy");
            //实现反射类中对属性的操作
            realizeField(boyClass3);
            //实现反射类中对方法的操作
            realizeMthod(boyClass3);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static void realizeField(Class boyClass3) {
        try {
            //实例化反射类对象
            Object boy = boyClass3.newInstance();
            //获取其中一个属性
            Field nameField = boyClass3.getDeclaredField("age");
            //将其状态设置成可访问
            nameField.setAccessible(true);
            //为属性赋值
            nameField.setInt(boy, 15);
            System.out.println(nameField.getInt(boy));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    private static void realizeMthod(Class boyClass) {
        try {
            //实例化
            Object girlInstance = boyClass.newInstance();
            //获取其中的指定的方法
            Method girlHobbyMethod = boyClass.getDeclaredMethod("boyHobby", String.class);
            //设置成可访问状态
            girlHobbyMethod.setAccessible(true);
            //为得到方法传递参数,并获取执行结果
            Object result = girlHobbyMethod.invoke(girlInstance, "11");
            System.out.println("我执行的结果: " + result);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

Andorid反射实例

我们以ViewOnClick事件Hook的实例做讲解,我们先来对View.setOnClickListener()做分析讲解:

我们在Activity中为控件设置onClick事件,进入View类中查看setOnClickListener(@Nullable OnClickListener l)源码分析,我们发现参数OnClickListener 最终存储在了静态内部类ListenerInfo类中的mOnClickListener属性

  public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

   ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }
接下来我们对onClick事件进行反射操作:
  1. 首先我们首先在 主程序 中设置控件的事件监听,然后设置我们的Hook的监听事件,当我们触发了控件的onClick事件由于我们将其进行了Hook代理,所以会触发我们的Hook代理操作
public class HookViewClickActivity extends Activity implements View.OnClickListener {
    private Button hook_btn;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.hook_click_layout);
        hook_btn = (Button) findViewById(R.id.hook_btn);
        hook_btn.setOnClickListener(this);
        try {
            HookHelper.hookOnClickListener(hook_btn);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.hook_btn:
                Toast.makeText(HookViewClickActivity.this, "OnClick 被正常调用了", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

  1. 我们来看具体的Hook操作步骤,主要的就是将我们的Proxy替换掉mOnClickListener属性
package com.thtf.leanpackage.view.hook_click;

import android.view.View;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class HookHelper {
    public static void hookOnClickListener(View view) throws Exception {
        // [1] 通过反射获取 ListenerInfo 对象
        Method listenerInfoMethod = View.class.getDeclaredMethod("getListenerInfo");
        listenerInfoMethod.setAccessible(true);
        //获取getListenerInfo函数执行的返回结果
        Object listenerInfoObj = listenerInfoMethod.invoke(view);

        // [2]反射获取到存储OnClickListener事件的属性 mOnClickListener
        Class<?> listenerInfoClazz = Class.forName("android.view.View$ListenerInfo");
        //获取ListenerInfo的属性mOnClickListener
        Field mOnClickListenerField = listenerInfoClazz.getDeclaredField("mOnClickListener");
        mOnClickListenerField.setAccessible(true);
        //获取mOnClickListener的属性值
        View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListenerField.get(listenerInfoObj);

        // [3]用 Hook代理类 替换mOnClickListener属性
        View.OnClickListener hookedOnClickListener = new HookedClickListenerProxy(originOnClickListener);
        //为mOnClickListener属性 赋值
        mOnClickListenerField.set(listenerInfoObj, hookedOnClickListener);
    }
}

  1. 那么我们在自定义的Proxy类,如何做处理的:
package com.thtf.leanpackage.view.hook_click;

import android.view.View;
import android.widget.Toast;

public class HookedClickListenerProxy implements View.OnClickListener {
    private View.OnClickListener listener;
    public HookedClickListenerProxy(View.OnClickListener listener){
        this.listener=listener;
    }
    @Override
    public void onClick(View v) {
        //自定义操作
        Toast.makeText(v.getContext(),"Hook Click Listener",Toast.LENGTH_SHORT).show();
        if (listener!=null){
            listener.onClick(v);
        }
    }
}

总结

  • 1.获取Class对象是反射机制的入口
  • 2.通过Class对象获取Field,对反射类中属性进行读取或赋值等操作
  • 3.通过Class对象获取Method,并实现Method.invoke()从而实现对反射类中所包含的方法进行 代理操作
此篇文章只对 反射技术 进行讲解,下一篇将着重讲述 动态代理 的实现原理。

This ALL! Thanks EveryBody!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 01 很少去做“十年”这样的设想,十年太长,长到一点概念都没有。 闺蜜有一次问我:“你有没有设想过十年之后,你在做...
    菱若依北梦南栀阅读 1,262评论 0 0
  • 泉水汤浴 你是世界的唯一 恒温左右 意境在其里 梦寐以求 多少人在羡慕 古韵显色 尽是文化与历史 白石泉天意 自然...
    止儿徐子阅读 496评论 3 20
  • 今天和朋友聊天聊到了以后,说以后不知道会怎么样。这仿佛是个话题的总结语句,没人能接着往下接了。或者说我们...
    FierceFighting阅读 215评论 0 0