享學課堂特邀作者:周周
轉載請聲明出處!
前言
手把手講解系列文章,是我寫給各位看官,也是寫給我自己的。文章可能過分詳細,但是這是為了幫助到盡量多的人,畢竟工作5,6年,不能老吸血,也到了回饋開源的時候。
這個系列的文章:1、用通俗易懂的講解方式,講解一門技術的實用價值
2、詳細書寫源碼的追蹤,源碼截圖,繪制類的結構圖,盡量詳細地解釋原理的探索過程
3、提供Github 的 可運行的Demo工程,但是我所提供代碼,更多是提供思路,拋磚引玉,請酌情cv
4、集合整理原理探索過程中的一些坑,或者demo的運行過程中的注意事項
5、用gif圖,最直觀地展示demo運行效果如果覺得細節太細,直接跳過看結論即可。本人能力有限,如若發現描述不當之處,歡迎留言批評指正。
學到老活到老,路漫漫其修遠兮。與眾君共勉 !
引子
HOOK系列是 今年年初大概3月份寫的,其中《手把手講解 Android Hook-實現無清單啟動Activity》 Demo地址為:https://github.com/18598925736/ActivityHookDemo/tree/startActivityWithoutRegiste
當時是基于最新的SDK 28 android 9 進行的hook,但是近期有一位朋友提出,在SDK 28的設備上,hook之后會導致作為 LauncherActivity
的生命周期完全失效。并且在SDK 29 android10的設備上,會崩潰。這位朋友解決了崩潰的問題,在此對他( github名為:fangding
)表示感謝!
崩潰的問題我大致看過,也驗證過,沒有問題,已經合并到 開發分支上,很簡單,只是SDK 29改了一些類名,各位可以到 github上去自行查看。現在要解決的是生命周期失效的問題。
聲明一個debug源碼的坑
hook開發的初期,一般不要用真機。因為真機的系統都是經過了手機廠家深度定制的,如果你想要進行代碼debug,使用真機做不到的,因為代碼的行數根本對應不上。推薦使用谷歌原生的模擬器。
本文采用的是 android 9
sdk 28
谷歌原生androidStudio自帶AVD模擬器。
適合閱讀的人群
如果你對hook 有概念,并且對具體如何去hook有 興趣深入了解,那么這篇文章可以幫到你很多。
正文
-
bug 表征
-
源碼探索
-
解決方案
-
完美效果
-
可能隱患
bug 表征
Demo :https://github.com/18598925736/ActivityHookDemo/tree/startActivityWithoutRegiste 請切換到 git時間點:945df9``git checkout945df9
如果結果為:HEADisnow at945df96使用androidX
則切換成功。這里是,已經出現問題的版本節點。
運行Demo,啟動as自帶模擬器 sdk28版本。進行跳轉,
然后發現,生命周期函數并不執行。
跳轉過程中,只出現了
onCreate onStart onResume
, 照理說,跳轉之后應該有onPause onStop
. 并且我回到這個Activity時,應該會有onRestart onStart onResume
, 但是也沒有。
源碼探索
為什么生命周期函數都不執行了?要找到這個原因,我必須先弄清楚一個問題: Activity的生命周期函數到底是由誰來調用的。
前期準備:
這里我不使用Demo工程,而是另外自己新建一個工程,寫一個普通的startActivity跳轉(這個我就不貼代碼了). 因為我們要觀察的是正常跳轉。
開工,進入源碼(SDK 28 注意,app module的sdk也要28,必須用 androidStudio自帶的AVD SDK 28模擬器才能 debug ):(為了確保源碼探索的完整流程,我們從 startActivity開始 . )
這里產生2個分支,但是仔細觀察之后,其實他們最終都走到了同一段邏輯:
Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, child,intent, requestCode, options);
if(ar != null) {mMainThread.sendActivityResult(mToken, child.mEmbeddedID, requestCode,ar.getResultCode(), ar.getResultData());
}
來解析這一段邏輯:
兩句代碼,一個是
mInstrumentation.execStartActivity
看來這一段并沒有涉及到Activity生命周期函數的邏輯。那么,看下一段:
mMainThread.sendActivityResult
,從 mInstrumentation.execStartActivity 執行之后,得到了一個ar
,現在把這個ar
交給mMainThread(ActivityThread類的對象),于是,進入 ActivityThread源碼:
這里的
scheduleTransaction
方法,在ActivityThread的父類ClientTransactionHandler
中:看到sendMessage,就懷疑,這里可能和
Handler
扯上關系了。
還記不記得
ActivityThread
的父類ClientTransactionHandler
scheduleTransaction()
方法中sendMessage(ActivityThread.H.EXECUTE_TRANSACTION,transaction);
用到了ActivityThread.H.EXECUTE_TRANSACTION
. 我們在HextendsHandler
中找到這個switch case的分支:
一共就兩句代碼可能和Activity的生命周期函數調用有關,那么我有理由懷疑就是這一段代碼在執行 生命周期函數. 那么 如何驗證我的猜想是否成立? 答:debug源碼(前面之所以要源碼版本,AVD模擬器版本,項目版本gradle SDK版本都寫成28,就是為了這里debug)
加上斷點之后,開始debug,按下跳轉按鈕, 我們發現了驚人的現象:
一次跳轉,我們debug發現了3個可能和生命周期函數有關的細節:
PauseActivityItem
,ResumeActivityItem
,StopActivityItem
,這3個是不是分別對應了 Activity的3個生命周期函數?繼續探索:找到TransactionExecutor
類的execute()
方法:
我們需要跟蹤的是 transaction參數
(因為這里只有一個參數…不跟蹤它跟蹤誰呢) 然而,這里,使用到這個參數的是兩個方法, executeCallbacks()
和 executeLifecycleState()
, 而,在這兩個方法中,我都找到了類似下面這樣的代碼:
finalActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest();
item.execute(mTransactionHandler, token, mPendingActions);
item.postExecute(mTransactionHandler, token, mPendingActions);
推斷出,生命周期函數一定和 ActivityLifecycleItem
的 execute()
和 postExecute()
有關,還記不記得之前我們debug出來的 PauseActivityItem
, ResumeActivityItem
, StopActivityItem
. 這3個類就是 ActivityLifecycleItem
的子類,進去看看:
debug 一下:
發現了
ActivityThread
,自然就聯想到它的HextendsHandler
,ok, 就快接近真相了,表激動,暫且壓制一下,我們還沒有找到真憑實據。又是一個抽象方法,直接找他的子類
ActivityThread
,
終于接近真相了,這里已經很明顯了,參數是Activity對象,并且執行了 activity.performPause()
.
真相大白,原來一個Activity的生命周期函數 onPause
是這么調用的,繞了一大圈,最終我們重寫的 onPause()
方法才被執行。
小結論
從我們startActivity開始,如果用一張圖來展示 Activity生命周期函數如何被執行。無謂的繞繞繞去的省略中間環節,只展示重點環節,那就是這樣。
解決方案
現在可以運行我的Demo工程,注意 請切換到 git時間點:
945df9
, 按照上面的步驟去debug一下,結果發現,上一章節中圖示的正常現象
PauseActivityItem
,ResumeActivityItem
,StopActivityItem
,在這里并未看到,或者說,這里 EXECUTE_TRANSACTION 分支缺失了。肯定是因為我們hook的代碼導致ActivityThread
的H mH=newH()
,classHextendsHandler
的voidhandleMessage(Messagemsg)
部分switchcase
沒有執行。
既然已經確定是 ActivityThread的 mH 有問題, 那么應該檢查的,則是 hook mH的時候。
現在進入hook代碼:
回顧一下handler的責任鏈模式:
按照我通常的hook思路,mH通常執行的是 第三級 成員函數
handlerMessage(msg)
的邏輯,我hook
一下,給mH的成員變量mCallback
賦值。 然后可以通過return
返回值來控制要不要繼續執行 原handlerMessage(msg)
的邏輯。如果mCallback.handlerMessage(msg)
返回了true,那么就沒有后續了,handlerMessage(msg)
永遠不會執行。如果是returnfalse,handlerMessage(msg)
仍會執行。
已經很接近真兇了。我確定是 mCallback.handlerMessage(msg)
的 returntrue
導致的問題。就檢查一下我hook的時候,哪里 returntrue
了。Debug一下:
這里
list.size()
是0,剛好這里returntrue
了。這里return
一下,是因為源碼中使用了list.get(0)
,我不能讓它在list為0的情況下去get(0)
. 對,就是這么單純,沒有別的想法。
完美效果
行動吧,把 這里的return true 改為 return false。然后再 重新運行,觀察日志,依然是從1跳轉到2。
日志如下:
生命周期函數已經完整。問題解決!
可能隱患
問題解決,可喜可賀。當我興高采烈地想在新買的華為mate30手機上試驗效果時,發現。正常的Activity跳轉和hook之后的跳轉速度截然不同. 肉眼可見的速度差別,hook之后慢了不止一拍。其他手機尚未發現。也不知道是不是華為底層做了什么事情。總之,這又是下一階段應該考慮的問題了。
結語
技術研究就是這樣,問題是無止境的,優化是無止境的,一項技術的誕生,永遠會伴隨這無窮無盡的優化,重構,升級,增強,擴展。學習也是如此,所謂活到老學到老,技術人應該保持對技術的熱情,執著和追求,堅持學習。像是今天Activity 生命周期函數是如何被調用的,以前只是疑惑,現在終于解開謎團,完完全全抽絲剝繭,得到真相。
所謂坑坑更健康,做技術不可以害怕坑坑洞洞,有坑,解決了坑,自己才能有收獲。而且類似這種問題,我解決問題,就是將true改為false,花了1秒。但是我檢查問題,找出真相的過程,花了1天。我相信工作中類似這種問題,不在少數。在沒有足夠了解核心代碼邏輯的情況下,去編程,很有可能出一些細節性的小錯誤,這些錯誤往往是致命的。 所以,雖然SDK系統源碼,一些第三方庫源碼,很大,很復雜,很多彎彎繞繞,各種回掉,各種映射,各種設計模式,看上去很可怕,但是我們沒有退路,退縮不前只會讓35歲被離職的風險加大。花點時間去補充基礎知識的缺失,鼓起勇氣去讀源碼,只要掌握正確的方法,也許能在源碼的世界中找到一片不一樣的天空。
2019又一年過去了,與 各位技術人,共勉!
喜歡本文的話可以關注我們的官方賬號,第一時間獲取資訊。
你的關注是對我們更新最大的動力哦~
最后
給大家送一個小福利
資料都是免費分享的,附送高清腦圖,高清知識點講解教程,以及一些面試真題及答案解析。送給需要的提升技術、準備面試跳槽、自身職業規劃迷茫的朋友們。點我免費領取!!!
轉存中…(img-PvzvTlZK-1623556289498)]
資料都是免費分享的,附送高清腦圖,高清知識點講解教程,以及一些面試真題及答案解析。送給需要的提升技術、準備面試跳槽、自身職業規劃迷茫的朋友們。點我免費領取!!!
[外鏈圖片轉存中…(img-x6M0Sz3i-1623556289498)]