为什么使用hook

有时系统源码无法实现我的需求

有时我的需求A写法太麻烦了,效果也不要好。但是用B写法,稍微改动点源码即可完美实现

N多理由

如之前写的LiveDataBus也用到了hook

hook的前提:清晰的步骤流程、反射、熟悉源码

刚好前段时间重新梳理了触碰流程

那就hook个点击事件

 

一、反射

先介绍反射中重要、常用的方法

参考https://www.jianshu.com/p/9be58ee20dee

获取class

val c = View::class.java
val cc = Class.forName("android.view.View")
//内部类
val ccc = Class.forName("android.view.View\$ListenerInfo")
//只能够调用无参的构造函数,即默认的构造函数;
c.newInstance()  此处只是说明,view无空的构造方法

内部类  java写法:android.view.View$ListenerInfo   但是在kotlin中$是一个函数,所以需要加 防止转义

获取class中的构造方法

//getDeclaredConstructor可以返回指定参数的构造函数,而getConstructor只能返回指定参数的public的构造函数
//此处只是为了展示方法,view没有空的构造方法
val con = View::class.java.getConstructor()
val con1 = View::class.java.getConstructor(Context::class.java)
val con2 = View::class.java.getDeclaredConstructor()

con1.newInstance(this)

获取class中的方法

val m = View::class.java.getDeclaredMethod("方法名")
//对应View中的m方法,形参string    fun m(aa:String){}
val m1 = View::class.java.getDeclaredMethod("m",String::class.java)
val ms = View::class.java.declaredMethods

//方法执行
//a.invoke(b,c)  ==  b对象执行a方法  c为a方法的形参
//invoke(调用底层方法的对象,用于方法调用的参数)
//如上面的m1  则是
//m1.invoke(mTextView,"String形参")

获取class的成员变量

android 反射 获取content 安卓反射hook_内部类

来修改View中的mUserPaddingRight    mTextView为简单的TextView

val f = View::class.java.getDeclaredField("mUserPaddingRight")
//参数or方法  设置为public
f.isAccessible = true
//从mTextView中提取mUserPaddingRight 属性值
val o = f.get(mTextView) as Int
Log.e("mUserPaddingRight1",o.toString())
f.isAccessible = true
f.set(mTextView,100)
val oo = f.get(mTextView) as Int
Log.e("mUserPaddingRight2",oo.toString())

打印结果:0和100

 

二、明确需求、理清步骤

目标:Hook一个点击事件

源码:上面有链接。

思路:setOnClickListener() 设置的点击事件是在View中的内部类ListenerInfo中的mOnClickListener

1、获取View中的ListenerInfo

2、获取ListenerInfo中的mOnClickListener

3、增加或替换点击事件

class MainActivity : AppCompatActivity() {
    private var mTextView:TextView ?= null

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

        mTextView = findViewById<TextView>(R.id.tvFlag)

        mTextView?.setOnClickListener {
            Log.e("onClick","请猛搓")
            Toast.makeText(this@MainActivity,"请猛搓",Toast.LENGTH_LONG).show()
        }
        hook()
    }

    fun hook(){
        //获取View中的getListenerInfo方法
        val getListenerInfoMethod = View::class.java.getDeclaredMethod("getListenerInfo")
        //getListenerInfo方法默认没有修饰符  设置为public
        getListenerInfoMethod.isAccessible = true
        //mTextView执行getListenerInfoMethod代表的方法
        val mListenerInfo = getListenerInfoMethod.invoke(mTextView)

        //获取listenerInfo内部类
        val listenerInfoClass = Class.forName("android.view.View\$ListenerInfo")
        //获取listenerInfo内部类中的mOnClickListener
        val fieldObservers = listenerInfoClass.getDeclaredField("mOnClickListener")
        //从mTextView中的mListenerInfo中  提取出mOnClickListener  如果替换点击事件则不需要此步骤
        val mOnClickListener = fieldObservers.get(mListenerInfo) as View.OnClickListener

        fieldObservers.set(mListenerInfo, MyOnClick(mOnClickListener))
    }


    class MyOnClick(private val onClickListener: View.OnClickListener): View.OnClickListener{

        override fun onClick(v: View?) {
            Log.e("hook","hook到了点击事件哦")
            //onClickListener.onClick(v)  //看你心情是否注释  替换或增加
        }

    }
}

OnClickListener 是个接口,接口我会想到mvp,想到面向接口编程,然后想到代理模式

上面只是简单的用静态代理来处理,也可以用动态代理才处理

详情百度

很多技术可能现在学习了,但是实际项目用的少,过段时间忘记了

很多技术常用,然而不知道是属于什么设计模式,为什么会这样用

不强求,但是起码在需要的时候,知道有这么个东西