Android Hook技术分析2

707 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情

2.拦截点击事件统计点击次数
      a.寻找hook点

      对一个view设置点击事件的调用流程如下:

    hkBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });

    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;
    }

    static class ListenerInfo {
        ......
        /**
         * Listener used to dispatch click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        public OnClickListener mOnClickListener;
        ......
}

      Hook操作onClickListener实例,需要首先获取到onClickListener的拥有者,即:ListenerInfo,最后通过ListenerInfo获取到原始的mOnClickListener。

      b.选择合适的代理方式

      从上面的流程可以发现,要将ListenerInfo类里面的mOnClickListener替换成修改过的代理对象;要替换ListenerInfo里面的字段,得先拿到ListenerInfo,View类中有一个方法getListenerInfo() ,通过它可以拿到这个ListenerInfo,实现如下:

    public static void hookOnClickListener(View view) {
        //step1:反射执行View类的getListenerInfo()方法,拿到view的mListenerInfo对象,这个对象是点击事件mOnClickListener的持有者
        try {
            Class<?> viewClz = Class.forName("android.view.View");
            Method method = viewClz.getDeclaredMethod("getListenerInfo");
            //由于getListenerInfo()方法并不是public的,所以要加这个代码来保证访问权限
            method.setAccessible(true);
            //拿到mListenerInfo,也就是点击事件的持有者
            Object listenerInfo = method.invoke(view);

            //step2:找到mListenerInfo持有的点击事件对象mOnClickListener
            //内部类的表示方法:android.view.View$ListenerInfo
            Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
            Field onClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
            final View.OnClickListener onClickListenerInstance = (View.OnClickListener) onClickListener.get(
                    listenerInfo);

            //step3:创建自己点击事件的OnClickListener代理类
             动态或静态创建OnClickListener的代理类proxyOnClickListener

            //step4:将持有者拥有的点击事件替换成代理对象[将listenerInfo里面的onClickListener变量替换为proxyOnClickListener]
            onClickListener.set(listenerInfo, proxyOnClickListener);
        } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException | NoSuchFieldException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    }

      接下来要创建OnClickListener的代理对象,由于OnClickListener是一个接口,因此可以使用JDK动态代理方式Java动态代理,也可以用静态代理类实现,实现方式如下:

    //方式1:自己实现代理类,将原始的View.OnClickListener对象onClickListenerInstance作为参数传入
    ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
    static class ProxyOnClickListener implements View.OnClickListener{

        private View.OnClickListener listener;
        private int clickCount = 0;
        ProxyOnClickListener(View.OnClickListener listener){
            this.listener = listener;
        }

        @Override
        public void onClick(View v) {
            clickCount++;
            Log.d("Seven", "Hook OnClickListener 2 click count " + clickCount);
            if(this.listener != null){
                this.listener.onClick(v);
            }
    }
            
    //方式2:由于View.OnClickListener是一个接口,所以可以直接用动态代理模式
    //参数:类的加载器,要代理实现的接口(用Class数组表示,支持多接口),代理类的实际逻辑封装在new出来的InvocationHandler内
    Object proxyOnClickListener = Proxy.newProxyInstance(View.OnClickListener.class.getClassLoader(),
                    new Class[]{View.OnClickListener.class}, new InvocationHandler() {
                private int clickCount = 0;
                @Override
                 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                         clickCount++;
                         Log.d("Seven", "Hook OnClickListener 1 click count " + clickCount);
                         return method.invoke(onClickListenerInstance, args);
                }
    });
      c.启动运行
hkBtn.setOnClickListener(this);
HookUtils.hookonClickListener(hkBtn);

      运行结果如下:

10:51:12.190 D/Seven   (26125): Hook OnClickListener 1 click count 1
10:51:21.428 D/Seven   (26125): Hook OnClickListener 1 click count 2
10:51:21.607 D/Seven   (26125): Hook OnClickListener 1 click count 3
10:51:21.770 D/Seven   (26125): Hook OnClickListener 1 click count 4
10:51:21.918 D/Seven   (26125): Hook OnClickListener 1 click count 5
10:51:22.068 D/Seven   (26125): Hook OnClickListener 1 click count 6
10:51:22.217 D/Seven   (26125): Hook OnClickListener 1 click count 7
10:51:22.394 D/Seven   (26125): Hook OnClickListener 1 click count 8
10:51:22.543 D/Seven   (26125): Hook OnClickListener 1 click count 9
10:51:22.722 D/Seven   (26125): Hook OnClickListener 1 click count 10
10:51:22.854 D/Seven   (26125): Hook OnClickListener 1 click count 11
10:51:23.009 D/Seven   (26125): Hook OnClickListener 1 click count 12
10:51:23.158 D/Seven   (26125): Hook OnClickListener 1 click count 13

      至此已经实现了对点击事件的hook。

3.总结

      针对以上两个案例分析,我们可以看到在进行hook时主要分为以下几步:

      a.寻找合适的hook点;

      b.找到Hook点对应的class;

      c.直接通过class或class内部的method找到对应的filed;

      d.替换对象是interface的话,直接用动态代理方式创建对象;否则,直接写个类继承要替换的类;

      e.调用filed.set(obj, proxy)来对变量进行替换;

      f.针对hook点进行相应的处理;

      hook技术涉及到的知识点主要有反射、代理及android源码的熟练程度。