摘要:理解本文需要一定的字節(jié)碼指令基礎(chǔ),可以閱讀筆者的另一篇文章大話圖說字節(jié)碼指令只為讓你懂利用字節(jié)碼插樁技術(shù)可以很方便地幫助我們實現(xiàn)很多手術(shù)刀式的代碼設(shè)計,如無埋點統(tǒng)計上報輕量級等。
理解本文需要一定的Java字節(jié)碼指令基礎(chǔ),可以閱讀筆者的另一篇文章:大話+圖說:Java字節(jié)碼指令——只為讓你懂
利用Android字節(jié)碼插樁技術(shù)可以很方便地幫助我們實現(xiàn)很多手術(shù)刀式的代碼設(shè)計,如無埋點統(tǒng)計上報、輕量級AOP等。下面我們就通過一次實戰(zhàn),把這門技術(shù)真正用起來。奇葩需求
假設(shè)有這樣一個需求,我們需要在本項目工程的所有組件(Activity/Receiver/Service/Provider)的on系列生命周期類方法執(zhí)行時,調(diào)用一個我們寫好的方法,傳入組件的實例對象,來對組件的相關(guān)狀態(tài)進(jìn)行監(jiān)測,如何實現(xiàn)?
一般的思路有兩種:
通過Java繼承體系,為我們實現(xiàn)的四大組件分別建立基類,在基類父方法里對監(jiān)測方法進(jìn)行調(diào)用。
通過Android API Hook技術(shù),即通過動態(tài)代理等方法替換關(guān)鍵節(jié)點,抓住組件的節(jié)點方法并調(diào)用我們的監(jiān)測方法。
上面的第一種方法比較麻煩,而且控制力較弱,也無法顧及我們所依賴的Jar或者aar中的組件,比如小米推送中自帶的Service和Receiver,是完全無法觸及的。第二種方法則比較強(qiáng)大,但是需要考慮兼容性問題,技術(shù)實現(xiàn)上的成本也比較高,畢竟有一些生命周期的節(jié)點不好找,難免焦頭爛額。
本文對此的實戰(zhàn)即通過字節(jié)碼插樁,在class文件編譯成dex之前(同時也是proguard操作之前),遍歷所有要編譯的class文件并對其中符合條件的方法進(jìn)行修改,注入我們要調(diào)用的監(jiān)測方法的代碼,從而實現(xiàn)這個需求。
HiBeaver 是目前這方面比較完善的字節(jié)碼插樁Gradle插件,目前最新的1.2.4版本支持通過通配符或正則表達(dá)式的方法來匹配目標(biāo)類和目標(biāo)方法,進(jìn)行方法的批量插樁注入和修改,非常靈活易用。對于類似上文提出的需求,實現(xiàn)起來非常方便,唯一前提的僅僅是:知道所有組件的類的全名就可以了。
準(zhǔn)備工作好,基于這些,正式開始實戰(zhàn),牛刀小試一下:
首先建立一個工程,為便于演示,我們引入小米推送(接入方式不再贅述,詳見小米推送文檔),然后完善代碼到如下狀態(tài):
MainActivity內(nèi)容很簡單,注冊了小米推送,有一個TextView點擊后可以跳轉(zhuǎn)到SecondActivity,僅此而已。具體如下:
SecondActivity中一切從簡:
至于DemoMessageReceiver這個類里完全依照小米推送接入文檔中的配置,沒有實質(zhì)改動,不再貼出。
注意到還有一個MonitorUtil的類,內(nèi)容如下:
其中的monitorThis的方法就是我們打算在各個生命周期方法里插入的調(diào)用方法。
開始實戰(zhàn)下面我們就開始實現(xiàn)開頭處提到的需求:通過字節(jié)碼插樁的方法,本工程里的所有組件的生命周期方法return之前調(diào)用我們的monitorThis方法,傳入組件實例等信息作為參數(shù)。
首先,要引入HiBeaver插件:
然后在項目的根build.gradle下面增加classpath如下:
classpath "com.bryansharp:hibeaver:1.2.4"
隨后為我們工程的app/build.gradle增加如下配置:
apply plugin: "hiBeaver" import com.bryansharp.gradle.hibeaver.utils.MethodLogAdapter import org.objectweb.asm.ClassVisitor import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes hiBeaver { modifyMatchMaps = [ //類名稱匹配規(guī)則,*表示任意長度任意字符,|為分隔符,可以理解為或 "*Activity|*Receiver|*Service|!android*": [ //方法名匹配規(guī)則與類名類似,同時也支持正則表達(dá)式匹配(需要加r:);adapter后為一個閉包,進(jìn)行具體的修改 ["methodName": "on**", "methodDesc": null, "adapter": { //下面這些為閉包傳入的參數(shù),可以幫助我們進(jìn)行方法過濾,以及根據(jù)方法參數(shù)來調(diào)整字節(jié)碼修改方式 ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions -> //這里我們有了ClassVisitor實例,其實可以為類添加新的方法。 MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions); MethodVisitor adapter = new MethodLogAdapter(methodVisitor) { @Override void visitCode() { super.visitCode(); //實例對象入棧 methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); //下面兩句我們將方法的名稱和描述作為常量入棧 methodVisitor.visitLdcInsn(name); methodVisitor.visitLdcInsn(desc); //調(diào)用我們的靜態(tài)方法 methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, //下面這個MethodLogAdapter.className2Path(String)為 // hibeaver插件提供的方法,可以將類名轉(zhuǎn)為路徑名 MethodLogAdapter.className2Path("bruce.com.testhibeaver.MonitorUtil"), "monitorThis", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V"); } } return adapter; }] ] ] }
HiBeaver在類名和方法名的匹配上非常靈活,可以非常方便地實現(xiàn)批量匹配,除了完整匹配外,還支持通配符匹配和正則表達(dá)式匹配兩種模式。通配符匹配模式中主要可以使用兩種符號,即 | 和,表示任意長度(>0)的任意字符,而|表示分隔符,這里可以理解為或。因此,上面的:
*Activity|*Receiver|*Service
可以理解為,匹配任意全類名以Activity、Receiver或Service結(jié)尾的類。
一般來講,我們的Android組件在命名上都會遵從這個規(guī)范,即組件類名以相應(yīng)的組件名結(jié)尾,對于個別不遵從這個原則的,也可以通過|分隔符來把特殊情況納入進(jìn)去。
除此之外,如果存在更復(fù)雜的匹配規(guī)則,上述通配符已經(jīng)無法滿足,hiBeaver也支持正則表達(dá)式進(jìn)行全類名匹配,只需要在表達(dá)式前加上“r:”就可以。比如:
r:.*D[a-zA-Z]*Client
表示匹配符合“.*D[a-zA-Z]*Client”這個正則表達(dá)式的類名。
更進(jìn)一步地,HiBeaver 未來 還將支持根據(jù)類的繼承關(guān)系進(jìn)行匹配,比如:
>ext>android.support.v4.app.FragmentActivity
表示匹配所有繼承android.support.v4.app.FragmentActivity的類,而:
>imp>android.os.Handler.Callback
表示匹配所有實現(xiàn)android.os.Handler.Callback接口的類。
不過,目前這兩個特性還沒有支持,僅提上了其項目的issue中。
回到剛剛的配置中,下面的methodName方法的匹配規(guī)則與類名匹配用法一樣,**和*是一樣的效果,on**即表示名字以on開頭的方法。
好了,編譯運行工程,過程中在Gradle Console中可以看到hibeaver進(jìn)行字節(jié)碼插樁輸出如下(局部):
程序運行起來,插樁成功,成功調(diào)用了monitorThis方法,但赫然發(fā)現(xiàn)輸出如下:
調(diào)用了三個onCreate和若干的onCreateView!這是為什么?我們的MainActivity也沒有這個onCreateView的方法??!
結(jié)合之前Gradle編譯日志,在仔細(xì)一琢磨,突然明白了:
原來,我們的*Activity規(guī)則會匹配所有的Activity結(jié)尾的類,包括一些android v4支持包中的類,什么AppCompatActivity、FragmentActivity等繼承鏈上的Activity通通被hook了一遍,難怪會有那么多輸出了,可辛苦了我們的monitorThis方法。
既然如此,如何是好?針對于當(dāng)前的需求,我們當(dāng)然不想匹配v4包里的組件類。
所幸的是,HiBeaver中還有另一種排除匹配,運用!符號改造如下即可:
*Activity|*Receiver|*Service|!android*
這樣就表示,匹配前三種之一(或的關(guān)系)且不匹配第四個android*的全類名。
改好后,再次運行,并點擊跳轉(zhuǎn)到SecondActivity:
可以看到log輸出一下子少多了,證明沒有再注入v4包里的類,同時,小米的組件也被正常注入了,我把網(wǎng)斷掉,可以看到小米的Receiver被喚起:
再開啟調(diào)試,打開網(wǎng),斷點也可以正常進(jìn)入:
同時,每次HiBeaver進(jìn)行字節(jié)碼插樁后還會把修改過、實際使用的字節(jié)碼保存到build/HiBeaver目錄下,以便于查看:
如下圖為修改后的MainActivity類:
修改后的小米推送里的某Receiver:
這樣,無論是進(jìn)行節(jié)點控制還是研究其運行機(jī)制都大大地方便了。
HiBeaver
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/66774.html
摘要:全稱應(yīng)用性能管理監(jiān)控后面我會通過一系列的文章來介紹的原理框架設(shè)計與實現(xiàn)等等。在應(yīng)用構(gòu)建期間,通過修改字節(jié)碼的方式來進(jìn)行字節(jié)碼插樁就是實現(xiàn)自動化的方案之一。 showImg(https://segmentfault.com/img/bVbbRX6?w=1995&h=1273); 歡迎關(guān)注微信公眾號:BaronTalk,獲取更多精彩好文! 一. 前言 性能問題是導(dǎo)致 App 用戶流失的罪魁...
摘要:最新最全的開源項目合集掘金是由整理并維護(hù)的安卓相關(guān)開源項目庫集合。準(zhǔn)備的插件開發(fā)必開發(fā)者福利史上最全開發(fā)和安全系列工具掘金取證工具一個工具箱,用于分析手機(jī)元數(shù)據(jù)。 最新最全的 Android 開源項目合集 - Android - 掘金awesome-github-android-ui 是由OpenDigg整理并維護(hù)的安卓UI相關(guān)開源項目庫集合。我們會定期同步OpenDigg上的項目到這...
閱讀 1453·2021-09-28 09:44
閱讀 2520·2021-09-28 09:36
閱讀 1190·2021-09-08 09:35
閱讀 1993·2019-08-29 13:50
閱讀 821·2019-08-29 13:29
閱讀 1142·2019-08-29 13:15
閱讀 1735·2019-08-29 13:00
閱讀 3003·2019-08-26 16:16