hook入門篇

引子

Hook技術(shù)在android開發(fā)領(lǐng)域算是一項(xiàng)黑科技做粤,那么一個(gè)新的概念進(jìn)入視線抬伺,我們最關(guān)心的3個(gè)問(wèn)題就是怎顾,它是什么读慎,有什么用,怎么用

本系列將由淺入深 手把手講解這三大問(wèn)題

本文是第一篇槐雾,入門篇

正文大綱

一. hook的定義
二. 實(shí)用價(jià)值
三. 前置技能
四. Hook通用思路
五. 案例實(shí)戰(zhàn)
六. 效果展示

Demo地址

https://github.com/18598925736/OnClickListenerHookDemo

正文

一. hook的定義

hook夭委,鉤子。勾住系統(tǒng)的程序邏輯募强。
在某段SDK源碼邏輯執(zhí)行的過(guò)程中株灸,通過(guò)代碼手段攔截執(zhí)行該邏輯,加入自己的代碼邏輯擎值。


二. 實(shí)用價(jià)值

hook是中級(jí)開發(fā)通往高級(jí)開發(fā)的必經(jīng)之路慌烧。
如果把谷歌比喻成 安卓的造物主,那么安卓SDK源碼里面就包含了萬(wàn)事萬(wàn)物的本源鸠儿。
中級(jí)開發(fā)者杏死,只在利用萬(wàn)事萬(wàn)物,浮于表層捆交,而高級(jí)開發(fā)者能從本源上去改變?nèi)f事萬(wàn)物,深入核心腐巢。

最有用的實(shí)用價(jià)值:
hook是安卓面向切面(AOP)編程的基礎(chǔ),可以讓我們?cè)?code>不變更原有業(yè)務(wù)的前提下品追,插入額外的邏輯.
這樣,既保護(hù)了原有業(yè)務(wù)的完整性冯丙,又能讓額外的代碼邏輯不與原有業(yè)務(wù)產(chǎn)生耦合.
(想象一下肉瓦,讓你在一個(gè)成熟的app上面給每一個(gè)按鈕添加埋點(diǎn)接口遭京,不說(shuō)一萬(wàn)個(gè),就說(shuō)成百上千個(gè)控件讓你埋點(diǎn),讓你寫一千次埋點(diǎn)調(diào)用泞莉,你是不是要崩潰哪雕,hook可以輕松實(shí)現(xiàn))

學(xué)好了hook,就有希望成為高級(jí)工程師,
完成初中級(jí)無(wú)法完成的開發(fā)任務(wù),
升職,加薪,出任CEO鲫趁,迎娶白富美斯嚎,走上人生巔峰,夠不夠?qū)嵱茫?/strong>


三. 前置技能

  • java反射 熟練掌握類Class,方法Method挨厚,成員Field的使用方法
    源碼內(nèi)部堡僻,很多類和方法都是@hide的,外部直接無(wú)法訪問(wèn),所以只能通過(guò)反射疫剃,去創(chuàng)建源碼中的類钉疫,方法,或者成員.
  • 閱讀安卓源碼的能力
    hook的切入點(diǎn)都在源碼內(nèi)部巢价,不能閱讀源碼牲阁,不能理清源碼邏輯,則不用談hook.
    其實(shí)使用 androidStudio來(lái)閱讀源碼有個(gè)坑壤躲,城菊,有時(shí)候會(huì)看到源碼里面 "一片飄紅",看似是有什么東西沒有引用進(jìn)來(lái)柒爵,其實(shí)是因?yàn)橛胁糠衷创a沒有對(duì)開發(fā)者開放役电,解決起來(lái)很麻煩,
    所以棉胀,推薦從安卓官網(wǎng)下載整套源碼法瑟,然后使用 SourceInsight 查看源碼。
    如果不需要跳來(lái)跳去的話唁奢,直接用 安卓源碼網(wǎng)站 一步到位

四. Hook通用思路

無(wú)論多么復(fù)雜的源碼霎挟,我們想要干涉其中的一些執(zhí)行流程,最終的殺招只有一個(gè): “偷梁換柱”.
“偷梁換柱”的思路麻掸,通常都是一個(gè)套路:

1. 根據(jù)需求確定 要hook的對(duì)象
2. 尋找要hook的對(duì)象的持有者酥夭,拿到要hook的對(duì)象
(持有:B類 的成員變量里有 一個(gè)是A的對(duì)象,那么B就是A的持有者,如下)

class B{ 
  A a;
}
class A{}

3. 定義“要hook的對(duì)象”的代理類脊奋,并且創(chuàng)建該類的對(duì)象
4. 使用上一步創(chuàng)建出來(lái)的對(duì)象熬北,替換掉要hook的對(duì)象

上面的4個(gè)步驟可能還是有點(diǎn)抽象,那么,下面用一個(gè)案例诚隙,詳細(xì)說(shuō)明每一個(gè)步驟.


五. 案例實(shí)戰(zhàn)

這是一個(gè)最簡(jiǎn)單的案例:
我們自己的代碼里面讶隐,給一個(gè)view設(shè)置了點(diǎn)擊事件,現(xiàn)在要求在不改動(dòng)這個(gè)點(diǎn)擊事件的情況下,添加額外的點(diǎn)擊事件邏輯.

View v = findViewById(R.id.tv);
        v.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "別點(diǎn)啦久又,再點(diǎn)我咬你了...", Toast.LENGTH_SHORT).show();
            }
        });

這是view的點(diǎn)擊事件巫延,toast了一段話效五,現(xiàn)在要求,不允許改動(dòng)這個(gè)OnClickListener,要在toast之前添加日志打印 Log.d(...).

乍一看炉峰,無(wú)從下手.看hook如何解決.

按照上面的思路來(lái):

第一步:根據(jù)需求確定 要hook的對(duì)象畏妖;
我們的目的是在OnClickListener中,插入自己的邏輯.所以疼阔,確定要hook的戒劫,是v.setOnClickListener()方法的實(shí)參。

第二步:尋找要hook的對(duì)象的持有者竿开,拿到要hook的對(duì)象
進(jìn)入v.setOnClickListener源碼:發(fā)現(xiàn)我們創(chuàng)建的OnClickListener對(duì)象被賦值給了getListenerInfo().mOnClickListener

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

繼續(xù)索引:getListenerInfo() 是個(gè)什么玩意谱仪?繼續(xù)追查:

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

結(jié)果發(fā)現(xiàn)這個(gè)其實(shí)是一個(gè)偽單例,一個(gè)View對(duì)象中只存在一個(gè)ListenerInfo對(duì)象.
進(jìn)入ListenerInfo內(nèi)部:發(fā)現(xiàn)OnClickListener對(duì)象 被ListenerInfo所持有.

static class ListenerInfo {
...
public OnClickListener mOnClickListener;
...
}

到這里為止否彩,完成第二步疯攒,找到了點(diǎn)擊事件的實(shí)際持有者:ListenerInfo .

第三步:定義“要hook的對(duì)象”的代理類,并且創(chuàng)建該類的對(duì)象
我們要hook的是View.OnClickListener對(duì)象列荔,所以敬尺,創(chuàng)建一個(gè)類 實(shí)現(xiàn)View.OnClickListener接口.

static class ProxyOnClickListener implements View.OnClickListener {
   View.OnClickListener oriLis;

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

   @Override
   public void onClick(View v) {
       Log.d("HookSetOnClickListener", "點(diǎn)擊事件被hook到了");
       if (oriLis != null) {
           oriLis.onClick(v);
       }
   }
}

然后,new出它的對(duì)象待用贴浙。

ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);

可以看到砂吞,這里傳入了一個(gè)View.OnClickListener對(duì)象,它存在的目的,是讓我們可以有選擇地使用到原先的點(diǎn)擊事件邏輯崎溃。一般hook蜻直,都會(huì)保留原有的源碼邏輯.
另外提一句:當(dāng)我們要?jiǎng)?chuàng)建的代理類,是被接口所約束的時(shí)候袁串,比如現(xiàn)在概而,我們創(chuàng)建的ProxyOnClickListener implements View.OnClickListener,只實(shí)現(xiàn)了一個(gè)接口囱修,則可以使用JDK提供的Proxy類來(lái)創(chuàng)建代理對(duì)象

Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(),
       new Class[] >> {View.OnClickListener.class}, new InvocationHandler() {
           @Override
           public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
               Log.d("HookSetOnClickListener", "點(diǎn)擊事件被hook到了");//加入自己的邏輯
               return method.invoke(onClickListenerInstance, args);//執(zhí)行被代理的對(duì)象的邏輯
           }
       });

這個(gè)代理類并不是此次的重點(diǎn)赎瑰,所以一筆帶過(guò).
到這里為止,第三步:定義“要hook的對(duì)象”的代理類破镰,并且創(chuàng)建該類的對(duì)象 完成餐曼。

第四步:使用上一步創(chuàng)建出來(lái)的對(duì)象,替換掉要hook的對(duì)象鲜漩,達(dá)成 偷梁換柱的最終目的.
利用反射源譬,將我們創(chuàng)建的代理點(diǎn)擊事件對(duì)象,傳給這個(gè)view
field.set(mListenerInfo, proxyOnClickListener);

這里孕似,貼出最終代碼:

/**
 * hook的輔助類
 * hook的動(dòng)作放在這里
 */
public class HookSetOnClickListenerHelper {

    /**
     * hook的核心代碼
     * 這個(gè)方法的唯一目的:用自己的點(diǎn)擊事件瓶佳,替換掉 View原來(lái)的點(diǎn)擊事件
     *
     * @param v hook的范圍僅限于這個(gè)view
     */
    public static void hook(Context context, final View v) {//
        try {
            // 反射執(zhí)行View類的getListenerInfo()方法,拿到v的mListenerInfo對(duì)象鳞青,這個(gè)對(duì)象就是點(diǎn)擊事件的持有者
            Method method = View.class.getDeclaredMethod("getListenerInfo");
            method.setAccessible(true);//由于getListenerInfo()方法并不是public的霸饲,所以要加這個(gè)代碼來(lái)保證訪問(wèn)權(quán)限
            Object mListenerInfo = method.invoke(v);//這里拿到的就是mListenerInfo對(duì)象,也就是點(diǎn)擊事件的持有者

            //要從這里面拿到當(dāng)前的點(diǎn)擊事件對(duì)象
            Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");// 這是內(nèi)部類的表示方法
            Field field = listenerInfoClz.getDeclaredField("mOnClickListener");
            final View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);//取得真實(shí)的mOnClickListener對(duì)象

            //2. 創(chuàng)建我們自己的點(diǎn)擊事件代理類
            //   方式1:自己創(chuàng)建代理類
            //   ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
            //   方式2:由于View.OnClickListener是一個(gè)接口臂拓,所以可以直接用動(dòng)態(tài)代理模式
            // Proxy.newProxyInstance的3個(gè)參數(shù)依次分別是:
            // 本地的類加載器;
            // 代理類的對(duì)象所繼承的接口(用Class數(shù)組表示厚脉,支持多個(gè)接口)
            // 代理類的實(shí)際邏輯,封裝在new出來(lái)的InvocationHandler內(nèi)
            Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    Log.d("HookSetOnClickListener", "點(diǎn)擊事件被hook到了");//加入自己的邏輯
                    return method.invoke(onClickListenerInstance, args);//執(zhí)行被代理的對(duì)象的邏輯
                }
            });
            //3. 用我們自己的點(diǎn)擊事件代理類胶惰,設(shè)置到"持有者"中
            field.set(mListenerInfo, proxyOnClickListener);
            //完成
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 還真是這樣,自定義代理類
    static class ProxyOnClickListener implements View.OnClickListener {
        View.OnClickListener oriLis;

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

        @Override
        public void onClick(View v) {
            Log.d("HookSetOnClickListener", "點(diǎn)擊事件被hook到了");
            if (oriLis != null) {
                oriLis.onClick(v);
            }
        }
    }
}

這段代碼閱讀起來(lái)的可能難點(diǎn):

  • Method,Class,Field的使用

    method.setAccessible(true);//由于getListenerInfo()方法并不是public的傻工,所以要加這個(gè)代碼來(lái)保證訪問(wèn)權(quán)限
    field.set(mListenerInfo, proxyOnClickListener);//把一個(gè)proxyOnClickListener對(duì)象,設(shè)置給mListenerInfo對(duì)象的field屬性.

  • Proxy.newProxyInstance的使用
    Proxy.newProxyInstance的3個(gè)參數(shù)依次分別是:
    本地的類加載器;
    代理類的對(duì)象所繼承的接口(用Class數(shù)組表示孵滞,支持多個(gè)接口)
    代理類的實(shí)際邏輯中捆,封裝在new出來(lái)的InvocationHandler內(nèi)
    到這里,最后一步坊饶,也完成了.


六. 效果展示

先給出Demo:GithubDemo
當(dāng)我點(diǎn)擊這個(gè) hello World

image.png

彈出一個(gè)Toast泄伪,并且:在日志中可以看到

image.png

同時(shí)我并沒有改動(dòng)setOnClickListener的代碼,我只是在它的后面排拷,加了一行HookSetOnClickListenerHelper.hook(this, v);

v.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "別點(diǎn)啦久窟,再點(diǎn)我咬你了...", Toast.LENGTH_SHORT).show();
            }
        });

 HookSetOnClickListenerHelper.hook(this, v);//這個(gè)hook的作用梅掠,是 用我們自己創(chuàng)建的點(diǎn)擊事件代理對(duì)象算撮,替換掉之前的點(diǎn)擊事件泽谨。

ok弧腥,目的達(dá)成v.setOnClickListener已經(jīng)被hook.

前方有坑庞溜,高能提示
我曾經(jīng)嘗試破讨,是不是可以將上面兩段代碼換個(gè)順序. 結(jié)果證明孤页,換了之后尔苦,hook就不管用了,原因是行施,hook方法的作用允坚,是將v已有的 點(diǎn)擊事件,替換成 我們代理的點(diǎn)擊事件悲龟。所以屋讶,在v還沒有點(diǎn)擊事件的時(shí)候進(jìn)行hook,是沒用的


結(jié)語(yǔ)

Hook的水很深须教,這個(gè)只是一個(gè)入門級(jí)的案例皿渗,我寫這個(gè),目的是說(shuō)明hook技術(shù)的套路轻腺,不管我們要hook源碼的哪一段邏輯乐疆,都逃不過(guò) hook通用思路 這“三板斧”,套路掌握了贬养,就有能力學(xué)習(xí)更難的Hook技術(shù).

Hook的學(xué)習(xí)挤土,需要我們大量地閱讀源碼,要對(duì)SDK有較為深入的了解误算,再也不是浮于表面仰美,只會(huì)對(duì)SDK的api進(jìn)行調(diào)用迷殿,而是真正地干涉“造物主谷歌”的既定規(guī)則. 學(xué)習(xí)難度很大,但是收益也不小咖杂,高級(jí)開發(fā)和初中級(jí)開發(fā)的薪資差距巨大庆寺,職場(chǎng)競(jìng)爭(zhēng)力也不可同日而語(yǔ).

高級(jí)開發(fā)之路漫漫長(zhǎng),與眾君共勉!


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诉字,一起剝皮案震驚了整個(gè)濱河市懦尝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壤圃,老刑警劉巖陵霉,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異伍绳,居然都是意外死亡踊挠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門墨叛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)止毕,“玉大人,你說(shuō)我怎么就攤上這事漠趁”饬荩” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵闯传,是天一觀的道長(zhǎng)谨朝。 經(jīng)常有香客問(wèn)我,道長(zhǎng)甥绿,這世上最難降的妖魔是什么字币? 我笑而不...
    開封第一講書人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮共缕,結(jié)果婚禮上洗出,老公的妹妹穿的比我還像新娘。我一直安慰自己图谷,他們只是感情好翩活,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著便贵,像睡著了一般菠镇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上承璃,一...
    開封第一講書人閱讀 49,785評(píng)論 1 290
  • 那天利耍,我揣著相機(jī)與錄音,去河邊找鬼。 笑死隘梨,一個(gè)胖子當(dāng)著我的面吹牛程癌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播出嘹,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼席楚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了税稼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤垮斯,失蹤者是張志新(化名)和其女友劉穎郎仆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兜蠕,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扰肌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了熊杨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片曙旭。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖晶府,靈堂內(nèi)的尸體忽然破棺而出桂躏,到底是詐尸還是另有隱情,我是刑警寧澤川陆,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布剂习,位于F島的核電站,受9級(jí)特大地震影響较沪,放射性物質(zhì)發(fā)生泄漏鳞绕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一尸曼、第九天 我趴在偏房一處隱蔽的房頂上張望们何。 院中可真熱鬧,春花似錦控轿、人聲如沸冤竹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)贴见。三九已至,卻和暖如春躲株,著一層夾襖步出監(jiān)牢的瞬間片部,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留档悠,地道東北人廊鸥。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像辖所,于是被迫代替她去往敵國(guó)和親惰说。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 前言 手把手講解系列文章缘回,是我寫給各位看官吆视,也是寫給我自己的。文章可能過(guò)分詳細(xì)酥宴,但是這是為了幫助到盡量多的人啦吧,畢竟...
    波瀾步驚閱讀 37,257評(píng)論 21 150
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險(xiǎn)厭惡者,不喜歡去冒險(xiǎn)拙寡,但是人生放棄了冒險(xiǎn)授滓,也就放棄了無(wú)數(shù)的可能。 ...
    yichen大刀閱讀 6,041評(píng)論 0 4
  • 公元:2019年11月28日19時(shí)42分農(nóng)歷:二零一九年 十一月 初三日 戌時(shí)干支:己亥乙亥己巳甲戌當(dāng)月節(jié)氣:立冬...
    石放閱讀 6,875評(píng)論 0 2