手把手教你Hook Android 点击事件


前言

随着技术的不断创新,Android的需求也是越来越多,Hook技术是走向Android高级开发的必经之路。


提示:以下是本篇文章正文内容,下面案例可供参考

一、什么是Hook?

Hook,简称“勾子”,用通俗易懂的方式来说就是勾住代码的逻辑,通过拦截的手段,插入自己的代码逻辑,在保证原有功能不变的情况下新增自己的逻辑功能。Copy一下大佬的原话:如果把谷歌比喻成 安卓的造物主,那么安卓SDK源码里面就包含了万事万物的本源。中级开发者,只在利用万事万物,浮于表层,而高级开发者能从本源上去改变万事万物,深入核心。

二、Hook的优势

Hook可以使我们在不改变原有代码的逻辑下,新增额外的一些逻辑,这样可以让代码的逻辑不与原有的业务发生冲突;

三、Hook前置条件

1.反射

反射是Java的一个知识点,因为对于源码来说,很多的方法是不能通过直接的手段去访问的,只能通过反射去获取相关的类,然后在进一步获取到相关的方法或者成员等,这里推荐一篇大佬的文章Java反射技术详解

2.代理模式

Java动态代理这里我就不细说啦~

四、Hook实战

这里就拿Android 的点击事件来讲解吧,首先要知道的是我们一般Hook的点都是静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。大致的流程如下:
1、寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。
2、选择合适的代理方式,如果是接口可以用动态代理。
3、偷梁换柱——用代理对象替换原始对象

我们先来看看最简单的点击事件:

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "~~~~", Toast.LENGTH_SHORT).show();
            }
        });

点击一个Button,然后弹出一个吐司,现在要做的是不改变这个OnClickListener的前提下,新增一个Log,参考流程,第一步是要找到合适的Hook对象,让我们先看看setOnClickListener源码:

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

这里面有判断可以不用理,接着往下看:

    @UnsupportedAppUsage
    ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }

我们看到 OnClickListener 对象被保存在了一个叫做 ListenerInfo 的内部类里,其中 mListenerInfo 是 View 的成员变量。ListeneInfo 里面保存了 View 的各种监听事件,所以我们的目标是 Hook OnClickListener,在给 View 设置监听事件后,替换 OnClickListener 对象,注入自定义的操作。

Hook点找到了,我们先创建一个ProxyOnClickListener类,用来新增自己的逻辑;

public class ProxyOnClickListener implements View.OnClickListener {

    View.OnClickListener listener;

    public ProxyOnClickListener(View.OnClickListener listener) {
        this.listener = listener;
    }

    @Override
    public void onClick(View v) {
        //这里就可以插入自己的逻辑了,也就是在这里加入Log到时候点击Button就会打印出来
        if (listener != null){
            listener.onClick(v);
        }
    }
}

然后接着是创建HookHelper类,具体的逻辑我都注释在代码中了:

public class HookHelper {
    /**
     * hook的核心代码
     * 这个方法的唯一目的:用自己的点击事件,替换掉 View原来的点击事件
     *
     * @param v hook的范围仅限于这个view
     */
    public static void Hook(Context context, View v){
        Log.d("DodSDK", "Hook: ");
        try {
            //首先进行反射执行View类的getListenerInfo()方法,拿到v的mListenerInfo对象,这个对象就是点击事件的持有者
            Method method = View.class.getDeclaredMethod("getListenerInfo");
            //由于getListenerInfo()方法并不是public的,所以要加这个代码来保证访问权限
            method.setAccessible(true);
            //这里拿到的就是mListenerInfo对象,也就是点击事件的持有者
            Object mListenerInfo = method.invoke(v);
            //获取到当前的点击事件
            Class<?> clz = Class.forName("android.view.View$ListenerInfo");
            //获取内部类的表示方法
            Field field = clz.getDeclaredField("mOnClickListener");
            //保证访问权限
            field.setAccessible(true);
            //获取真实的mOnClickListener对象
            View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);
            // 用自定义的 OnClickListener 替换原始的 OnClickListener
            View.OnClickListener hookedOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
            //设置到"持有者"中
            field.set(mListenerInfo, hookedOnClickListener);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

最后我们只需要在点击事件后加入这行代码就可以搞定了

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "~~~~", Toast.LENGTH_SHORT).show();
            }
        });
        HookHelper.Hook(MainActivity.this, button);

总结

总的来说,Hook的原则是要牢记的(静态变量和单例),Hook还可以用于别的地方,比如说新建一个Activity,正常来说新建完之后Android都会默认帮你在AndroidManifest 中注册这个Activity,如果没有注册的话,就会报错,我们若想要 启动一个没有在 AndroidManifest 声明的 Activity,那我们只需要在 某个时机,即调用 startActivity 方法之前欺骗 AMS 我们的 activity 已经注册(即替换 intent),这样就不会抛出 ActivityNotFoundException 异常。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值