通過(guò)一個(gè)簡(jiǎn)單的例子枯跑,學(xué)習(xí)Hook API技術(shù)在Android 的應(yīng)用惨驶。
因?yàn)榻榻BHook技術(shù)的文章,往往概念性多敛助,難以理解粗卜。
從例子入手,先不管原理纳击,把例子代碼寫(xiě)一遍续扔,會(huì)有意想不到的收獲。
不過(guò)焕数,需要對(duì)Java 反射機(jī)制有一定了解纱昧。
目標(biāo):我們對(duì)一Button的響應(yīng)事件做一些修改, 在原來(lái)的onClick 響應(yīng)的前后加上提示,不改變?cè)淼膐nClick操作
一堡赔、預(yù)備知識(shí)
(1) View.setOnClickListener的源碼實(shí)現(xiàn)
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
可以看出识脆,傳入的參數(shù)l 是賦值給 ListenerInfo 對(duì)象的mOnClickListener
@UnsupportedAppUsage
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
因此,我們的目標(biāo)是先獲取到View 的ListenerInfo 對(duì)象善已,然后改變它的mOnClickListener值
二灼捂、具體過(guò)程
1. 原始OnClickListener的實(shí)現(xiàn)
//1.原始代碼
val clickButton = findViewById<Button>(R.id.clickButton).apply {
setOnClickListener {
showToast("onclick working")
}
}
2. 創(chuàng)建一個(gè)View.OnClickListener的子類(lèi),并且它組合了原始的OnClickListener對(duì)象
//2-1.包裝類(lèi) - 包裝原始的 OnClickListener, 并提供額外操作
inner class HookedOnClickListener(private val origin: View.OnClickListener) :
View.OnClickListener {
override fun onClick(v: View?) {
this@MainActivity.showToast("hook click - before") //額外操作
origin?.onClick(v)
this@MainActivity.showToast("hook click - after") //額外操作
}
}
*說(shuō)明:
這里可以理解為靜態(tài)代理换团,即這是個(gè)代理類(lèi)
origin 即為原始的OnClickListener 對(duì)象
origin?.onClick(v) 即調(diào)用原來(lái)的點(diǎn)擊響應(yīng)
3. 設(shè)置hook
//3.設(shè)置hook
private fun doHookOnClickListener(view: View) {
try {
// (1) 獲取view的 ListenerInfo 對(duì)象 (實(shí)例對(duì)象)
val getListenerInfo = View::class.java.getDeclaredMethod("getListenerInfo")
getListenerInfo.isAccessible = true
val listenerInfo = getListenerInfo.invoke(view)
//(2) 獲取原始的 OnClickListener對(duì)象
val listenerInfoClazz = Class.forName("android.view.View\$ListenerInfo")//類(lèi)對(duì)象
val mOnClickListener: Field = listenerInfoClazz.getDeclaredField("mOnClickListener")
mOnClickListener.isAccessible = true
val originOnClickListener: View.OnClickListener =
mOnClickListener.get(listenerInfo) as View.OnClickListener
//(3) 用自定義的OnClickListener 替換原始的
val hookedOnClickListener = HookedOnClickListener(originOnClickListener)
mOnClickListener.set(listenerInfo, hookedOnClickListener)
} catch (e: Exception) {
showToast("doHookOnClickListener e:$e")
}
}
可以看到悉稠,這里主要是 反射的操作,需要對(duì)反射機(jī)制熟悉
4. 執(zhí)行效果
依次彈出以下Toast:
"hook click - before"
"onclick working"
"hook click - after"
三艘包、文獻(xiàn)參考:
https://cloud.tencent.com/developer/article/1102761