LeakCanary源碼分析

原理通過注冊activity和fragment的callback監(jiān)聽來觀察當前頁面是否destroy
Application中
監(jiān)聽activity 調用 registerActivityLifecycleCallbacks(this)
監(jiān)聽Fragment如下 :

 // 注冊 Activity 生命周期監(jiān)聽器,以監(jiān)聽每個 Activity 的 Fragment 生命周期
        registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                if (activity is FragmentActivity) {
                    // 對于 FragmentActivity,注冊 Fragment 生命周期回調
                    activity.supportFragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() {
                        override fun onFragmentCreated(fm: FragmentManager, f: Fragment, savedInstanceState: Bundle?) {
                            Log.d("FragmentLifecycle", "Fragment ${f.javaClass.simpleName} onCreated in ${activity.javaClass.simpleName}")
                        }

                        override fun onFragmentStarted(fm: FragmentManager, f: Fragment) {
                            Log.d("FragmentLifecycle", "Fragment ${f.javaClass.simpleName} onStarted in ${activity.javaClass.simpleName}")
                        }

                        override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {
                            Log.d("FragmentLifecycle", "Fragment ${f.javaClass.simpleName} onResumed in ${activity.javaClass.simpleName}")
                        }

                        override fun onFragmentPaused(fm: FragmentManager, f: Fragment) {
                            Log.d("FragmentLifecycle", "Fragment ${f.javaClass.simpleName} onPaused in ${activity.javaClass.simpleName}")
                        }

                        override fun onFragmentStopped(fm: FragmentManager, f: Fragment) {
                            Log.d("FragmentLifecycle", "Fragment ${f.javaClass.simpleName} onStopped in ${activity.javaClass.simpleName}")
                        }

                        override fun onFragmentDestroyed(fm: FragmentManager, f: Fragment) {
                            Log.d("FragmentLifecycle", "Fragment ${f.javaClass.simpleName} onDestroyed in ${activity.javaClass.simpleName}")
                        }
                    }, true)
                }
            }

            override fun onActivityStarted(activity: Activity) {}
            override fun onActivityResumed(activity: Activity) {}
            override fun onActivityPaused(activity: Activity) {}
            override fun onActivityStopped(activity: Activity) {}
            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
            override fun onActivityDestroyed(activity: Activity) {}
        })


當任意一個 Fragment 被創(chuàng)建、啟動、恢復、暫停、停止或銷毀時,都會通過 FragmentLifecycleCallbacks 打印日志。
通過這種方式,你可以在全局范圍內監(jiān)聽每個 Activity 中的 Fragment 的生命周期。

在onDestroy方法中檢測到對象的弱引用還有沒有值,有值說明沒有被回收存在內存泄漏,沒有值說明已經被回收了,當前對象不存在泄漏

存在問題:

  1. 性能開銷(只能檢測debug環(huán)境 線上會很卡頓(線上檢測方式可以用KOOM類庫快手開源的))


    image.png

內存和CPU負擔:LeakCanary 在進行內存快照(Heap Dump)和分析時會消耗額外的內存和CPU資源。對于較大的應用或復雜的場景,生成和分析堆內存快照可能會導致明顯的性能下降,尤其是在低性能設備上。
內存使用增加:LeakCanary 本身會消耗一定的內存來跟蹤對象,并且 Heap Dump 文件會占用設備存儲空間,這可能導致在調試環(huán)境中內存壓力增大。

  1. 誤報問題
    假陽性(False Positive):由于 LeakCanary 基于弱引用和GC回收機制來判斷對象是否被泄漏,在某些情況下,系統(tǒng)可能延遲回收對象或者強制回收未及時發(fā)生,導致 LeakCanary 誤報內存泄漏。
    復雜場景的誤報:對于某些復雜的引用關系(如系統(tǒng)級別的引用或長期存在的靜態(tài)引用),LeakCanary 可能無法準確判斷是否為實際內存泄漏,尤其是在多線程或異步操作較多的情況下。

  2. 嵌套的fragment會被漏掉檢查
    比如app首頁有三個tab(fragment) 在tab1中 嵌套了一個viewpager,viewpager中又有很多fragment,LeakCanary源碼中原理是通過activity的fragmentManager中監(jiān)聽第一層的fragment,無法監(jiān)聽到第二層第三層的所以會導致漏掉檢查。

4 activity也會存在此問題
LeakCanary觀察的是能夠執(zhí)行到onDestroy生命周期的類,如果觀察不到那就會漏掉,申請情況下會觀察不到呢 以下舉例說明:

activity A啟動activity B
在 Android 中,當 ActivityA 啟動 ActivityB 時,ActivityA 的生命周期會隨著 ActivityB 的啟動過程發(fā)生變化。具體來說:

ActivityB 的啟動:當 ActivityB 被啟動時,它的生命周期依次執(zhí)行 onCreate()、onStart() 和 onResume()。

ActivityA 的 onStop():當 ActivityB 完成 onResume(),也就是 ActivityB 顯示在前臺并獲得焦點時,此時 ActivityA 會進入后臺,然后 ActivityA 的 onStop() 方法被調用。
源碼分析:為什么ActivityB 完成 onResume()后才執(zhí)行ActivityA 的 onStop()
因為AMS的管理機制,當 ActivityB 執(zhí)行onResume()的時候 消息隊列中會add一個idlehandler 告訴Activity主線程空閑了,然后會通知ams ams收到這個消息后會通知ActivityA該執(zhí)行onStop了.
如果在執(zhí)行ActivityB onResume()的時候一直在刷新UI 比如動畫此時Activity主線程沒有空閑,所以就不會執(zhí)行idlehandler ams就不會通知ActivityA 執(zhí)行 onStop() 所以就會導致ActivityA無法被回收。 當 ActivityB 執(zhí)行onResume()的時候 AMS會設置一個定時機制10s后會通知ActivityA 執(zhí)行onStop方法
(補充和詳細分析:

  1. AMS 與消息隊列的調度機制
    當 ActivityB 執(zhí)行到 onResume() 時,它會獲取焦點并進入前臺。此時,AMS 會將一個 IdleHandler 添加到 ActivityThread 的消息隊列中,這個 IdleHandler 負責通知 AMS ActivityB 進入前臺且 ActivityA 已經不再處于活躍狀態(tài),因此可以讓 ActivityA 執(zhí)行 onStop()。

這里的關鍵點是:消息隊列的空閑狀態(tài)。如果 ActivityB 在 onResume() 階段執(zhí)行耗時任務(例如動畫、UI 刷新等),消息隊列可能不會很快進入空閑狀態(tài),導致 IdleHandler 沒有機會執(zhí)行,從而延遲 ActivityA 的 onStop()。

  1. AMS 定時機制
    AMS 確實會設置一個超時機制(通常是 10 秒左右),以確保 ActivityA 能及時執(zhí)行 onStop()。這個定時機制是為了防止某些情況下 ActivityThread 長時間無法進入空閑狀態(tài),導致 onStop() 被過度延遲。如果超時后還沒有進入空閑狀態(tài),AMS 會強制要求 ActivityA 執(zhí)行 onStop() 以避免資源浪費。

  2. 進一步分析和建議
    UI 線程占用問題:如果 ActivityB 在 onResume() 期間執(zhí)行了大量的 UI 刷新操作或者動畫,使得主線程忙碌而無法空閑,確實會延遲 IdleHandler 的執(zhí)行。為了解決這種情況,可以考慮將耗時操作(如動畫或復雜 UI 刷新)放在合適的地方進行,比如在 onPostResume()、onWindowFocusChanged() 或者通過 Handler 延遲執(zhí)行,這樣可以確保主線程有足夠的時間空閑,從而讓 IdleHandler 觸發(fā) onStop()。

定時機制的作用:AMS 的定時機制是作為安全網的一部分,用來保證生命周期的正確性。然而,依賴定時機制并不是最佳實踐,因為這可能會導致性能問題或內存泄漏。應盡量避免阻塞 onResume() 的主線程操作。

總的來說,你的理解是正確的,但需要注意的是,延遲 onStop() 并不是常態(tài),應該盡量優(yōu)化 Activity 的啟動和 UI 刷新操作,避免占用主線程資源過多導致生命周期回調被阻塞。)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容

  • Activity的啟動速度是很多開發(fā)者關心的問題,當頁面跳轉耗時過長時,App就會給人一種非常笨重的感覺。在遇到某...
    ModestStorm閱讀 1,396評論 0 0
  • 1 LeakCanary簡介 LeakCanary 是 Square 公司的一個開源庫。通過它可以在 App 運行...
    Joker_Wan閱讀 359評論 0 2
  • LeakCanary是一個檢測內存泄漏的工具,使用非常簡單。主要用來檢測Activity和Fragment內存泄漏...
    三木仔閱讀 1,011評論 1 2
  • 使用 LeakCanary 已經有了最新的2.0 版本,可以去獲取最新版本https://github.com/s...
    莫庫施勒閱讀 834評論 0 1
  • 最近在準備android面試,整理了下相關的面試題,分為如下三個部分:android部分、Java部分、算法面試題...
    JasmineBen閱讀 7,159評論 10 137