Hook点击事件


Hook的本质就是就是利用Java反射机制,将源码中一些类的对象替换自己实现的对象,以实现一些特殊的操作.

基本上所有Hook的入门都会从Hook一个View的点击事件开始.

Hook之前,一般都得先看看源码,以找到如何反射,如何替换比较合适.

view.setOnClickListener()

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

ListenerInfoView的一个内部类.

如果我们想在点击View的时候进行一些特殊的操作,其实只要将mOnClickListener替换成我们自己的OnClickListener就行了,

拿到 getListenerInfo()方法

val getListenerInfoMethod = View::class.java.getDeclaredMethod("getListenerInfo")
getListenerInfoMethod.isAccessible = true

执行getListenerInfo()方法,获取View对应的ListenerInfo对象

val listenerInfo = getListenerInfoMethod.invoke(view)

拿到ListenerInfo中的mOnClickListener Field

val listenerInfoClz = Class.forName("android.view.View\$ListenerInfo")
val mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener")
mOnClickListener.isAccessible = true

拿到业务传入的OnClickListener对象

由于我们一般不会去显式的修改业务逻辑,所以需要保存一个原始的OnClickListener对象,以保证业务的正常执行.

val originOnClickListener = mOnClickListener[listenerInfo] as View.OnClickListener

设置一个新的OnClickListener对象,并持有之前的原始OnClickListener对象

// 新的listener
val hookedOnClickListener: View.OnClickListener = HookOnClickListener(originOnClickListener, this)
// 赋值新的listener
mOnClickListener[listenerInfo] = hookedOnClickListener
class HookOnClickListener(private val originListener: View.OnClickListener?, private val context: Context) : View.OnClickListener {
    override fun onClick(v: View) {
        //点击之前
        Log.d(TAG, "onClick: before")

        // 执行原始的点击逻辑
        originListener?.onClick(v)

        //点击之后
        Log.d(TAG, "onClick: after")
    }

    companion object {
        private const val TAG = "hook"
    }

}

到这里基本就OK了,但是观察上面代码,其实需要传入一个View对象,并且需要获取View的原始点击事件,因此hook操作要放到设置点击事件之后,实用性并不是很高.

完整代码

class HackActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_hack)

        packageManager.getInstalledPackages(0)

        btn_hook_OnClick.setOnClickListener {
            ToastUtils.showShortToast("点击了Button")
        }
        hookOnClickListener(btn_hook_OnClick)
    }

    private fun hookOnClickListener(view:View) {
        try {
            //getListenerInfo()
            val getListenerInfoMethod = View::class.java.getDeclaredMethod("getListenerInfo")
            getListenerInfoMethod.isAccessible = true

            // 调用getListenerInfo()方法,得到ListenerInfo对象
            val listenerInfo = getListenerInfoMethod.invoke(view)

            //得到View的mOnClickListener Field
            val listenerInfoClz = Class.forName("android.view.View\$ListenerInfo")
            val mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener")
            mOnClickListener.isAccessible = true

            // 获取到原来的listener值
            val originOnClickListener = mOnClickListener[listenerInfo] as View.OnClickListener

            // 新的listener
            val hookedOnClickListener: View.OnClickListener = HookOnClickListener(originOnClickListener, this)
            // 赋值新的listener
            mOnClickListener[listenerInfo] = hookedOnClickListener
        } catch (t: Throwable) {
            t.printStackTrace()
        }

    }
}

源码地址

https://github.com/jiangkang/KTools


文章作者: 姜康
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 姜康 !
评论
 上一篇
Java反射 Java反射
关于反射原理性的知识可以看看类加载流程,对象实例化流程相关的文章,这里说一下反射的常用使用方法。 获取Class Class.forName ClassA.class classA.getClass Class常见方法 isPrim
2020-07-26
下一篇