1. android線上輕量級APM性能監測方案
Github 鏈接 Collie
如何衡量一個APP性能好壞?直觀感受就是:啟動快、流暢、不閃退、耗電少等感官指標,反應到技術層麵包裝下就是:FPS(幀率)、界面渲染速度、Crash率、網路、CPU使用率、電量損耗速度等,一般挑其中幾個關鍵指標作為APP質量的標尺。目前也有多種開源APM監控方案,但大部分偏向離線檢測,對於線上監測而言顯得太重,可能會適得其反,方案簡單對比如下:
還有其他多種APM檢測工具,功能復雜多樣,但其實很多指標並不是特別重要,實現越復雜,線上風險越大,因此,並不建議直接使用。而且,分析多家APP的實現原理,其核心思路基本相同,且門檻也並不是特別高,建議自研一套,在靈活性、安全性上更有保障,更容易做到輕量級。本文主旨就是 圍繞幾個關鍵指標 :FPS、內存(內存泄漏)、界面啟動、流量等,實現 輕量級 的線上監測。
Crash統計與聚合有比較通用的策略,比如Firebase、Bugly等,不在本文討論范圍
每個APP的網路請求一般都存在統一的Hook點,門檻很低,且各家請求協議與SDK有別,很難實現統一的網路請求監測,其次,想要真正定位網路請求問題,可能牽扯整個請求的鏈路,更適合做一套網路全鏈路監控APM,也不在討論范圍。
線上監測的重點就聚焦後面幾個,下面逐個拆解如何實現。
直觀上說界面啟動就是:從點擊一個圖標到看到下一個界面首幀,如果這個過程耗時較長,用戶會會感受到頓挫,影響體驗。從場景上說,啟動耗時間簡單分兩種:
本文粒度較粗,主要聚焦Activity,這里有個比較核心的時機:Activity首幀可見點,這個點究竟在什麼時候?經分析測試發現,不同版本表現不一,在Android 10 之前這個點與onWindowFocusChanged回調點基本吻合,在Android 10 之後,系統做了優化,將首幀可見的時機提前到onWindowFocusChanged之前,可以簡單看做onResume(或者onAttachedToWindow)之後,對於一開始點擊icon的點,可以約等於APP進程啟動的點,拿到了上面兩個時間點,就可以得到冷啟動耗時。
APP進程啟動的點可以通過載入一個空的ContentProvider來記錄,因為ContentProvider的載入時機比較靠前,早於Application的onCreate之前,相對更准確一點,很多SDK的初始也採用這種方式,實現如下:
這樣就得到了冷啟動的開始時間,如何得到第一個Activity界面可見的時間呢?大概回執流程如下
網上有一些認為可以監聽onAttachedToWindow或者OnWindowFocusChange,onAttachedToWindow的問題是可能太過靠前,還沒有Draw, OnWindowFocusChange的缺點可能是太過滯後。其實可以簡單認為在view draw以後,View的繪制就算完成,雖然到展示還可能相差一個VSYNC等待圖層合成,但是對於性能監測的評定,誤差一個固定值可以接受:
在onResume函數中插入一條消息可以嗎,理論上來說,太過靠前,這條消息在執行的時候,還沒Draw,因為請求VSYNC的同步柵欄是在是在Onresume結束後才插入的,無法攔截之前的Message,但是由於VSYNC可能存在復用,Onresume中插入的消息也有可能會在繪制之後執行,這個不是完全一定的,比如點擊MaterialButton啟動一個Activity,第二個Activity的setView觸發的VSYNC就可能復用MaterialButton的波紋觸發的VSYNC,從而導致第二個Activity的performTraval復用第一個VSYNC執行,從而發生在onResume插入消息之前,如下
綜上所述, 將指標定義在第一次View的Draw執行可能比較靠譜 。具體可以再DecorView上插入一個透明View,監聽器onDraw回調即可,如果覺得不夠優雅,就退一步,監聽OnWindowFocusChange的回調,也勉強可以接受, OnWindowFocusChange一定是在Draw之後的。如此就可以檢測到冷啟動耗時。APP啟動後,各Activity啟動耗時計算邏輯類似,首幀可見點沿用上面方案即可,不過這里還缺少上一個界面暫停的點,經分析測試,錨在上一個Actiivty pause的時候比較合理,因此Activity啟動耗時定義如下:
同樣為了減輕對業務入侵,也依賴來實現:補全上方缺失
到這里就獲取了兩個比較關鍵的啟動耗時,不過,時機使用中可能存在各種異常場景:比如閃屏頁在onCreate或者onResume中調用了finish跳轉首頁,對於這種場景就需要額外處理,比如在onCreate中調用了finish,onResume可能不會被調用,這個時候就要在 onCreate之後進行統計,同時利用用Activity.isFinishing()標識這種場景,其次,啟動耗時對於不同配置也是不一樣的,不能用絕對時間衡量,只能橫向對比,簡單線上效果如下:
FPS是圖像領域中的定義,指畫面每秒傳輸幀數,每秒幀數越多,顯示的動作就越流暢。FPS可以作為衡量流暢度的一個指標,但是,從各廠商的報告來看,僅用FPS來衡量是否流暢並不科學。電影或視頻的FPS並不高,30的FPS即可滿足人眼需求,穩定在30FPS的動畫,並不會讓人感到卡頓,但如果FPS 很不穩定的話,就很容易感知到卡頓,注意,這里有個詞叫 穩定 。舉個 極端 例子:前500ms刷新了59幀,後500ms只繪制一幀,即使達到了60FPS,仍會感知卡頓,這里就突出 穩定 的重要性。不過FPS也並不是完全沒用,可以用其上限定義流暢,用其下限可以定義卡頓,對於中間階段的感知,FPS無能為力,如下示意:
上面那個是極端例子,Android 系統中,VSYNC會杜絕16ms內刷新兩次,那麼在中間的情況下怎麼定義流暢?比如,FPS降低到50會卡嗎?答案是不一定。50的FPS如果是均分到各個節點,用戶是感知不到掉幀的,但,如果丟失的10幀全部在一次繪制點,那就能明顯感知卡頓,這個時候, 瞬時幀率 的意義更大,如下
Matrix給的卡頓標准:
總之,相比1s平均FPS,瞬時掉幀程度的嚴重性更能反應界面流暢程度,因此FPS監測的重點是偵測瞬時掉幀程度。
在應用中,FPS對動畫及列表意義較大, 監測開始的時機 放在界面啟動並展示第一幀之後,這樣就能跟啟動完美銜接起來,
偵測停止的時機也比較簡單在onActivityPaused:界面失去焦點,無法與用戶交互的時候
如何偵測瞬時FPS?有兩種常用方式
360的實現依賴Choreographer VSYNC回調,具體實現如下:循環添加Choreographer.FrameCallback
這種監聽有個問題就是,監聽過於頻繁,因為在無需界面刷新的時候Choreographer.FrameCallback還是不斷循環執行,浪費CPU資源,對線上運行採集並不友好,相比之下BlockCanary的監聽單個Message執行要友善的多,而且同樣能夠涵蓋UI繪制耗時、兩幀之間的耗時,額外執行負擔較低,也是本文採取的策略,核心實現參照Matrix:
為Looper設置一個LooperPrinter,根據回傳信息頭區分消息執行開始於結束,計算Message耗時:原理如下
自定義LooperPrinter如下:
利用回調參數">>>>"與"<<<"的 區別即可診斷出Message執行耗時,從而確定是否導致掉幀。以上實現針對所有UI Message,原則上UI線程所有的消息都應該保持輕量級,任何消息超時都應當算作異常行為,所以,直接拿來做掉幀監測沒特大問題的。但是,有些特殊情況可能對FPS計算有一些誤判,比如,在touch時間里往UI線程塞了很多消息,單條一般不會影響滾動,但多條聚合可能會帶來影響,如果沒跳消息執行時間很短,這種方式就可能統計不到,當然這種業務的寫法本身就存在問題,所以先不考慮這種場景。
Choreographer有個方法addCallbackLocked,通過這個方法添加的任務會被加入到VSYNC回調,會跟Input、動畫、UI繪制一起執行,因此可以用來作為鑒別是否是UI重繪的Message,看看是不是重繪或者觸摸事件導致的卡頓掉幀。Choreographer源碼如下:
該方法不為外部可見,因此需要通過反射獲取,
然後在每次執行結束後,重新將callback添加回Choreographer的Queue,監聽下一次UI繪制。
這樣就能檢測到每次Message執行的時間,它可以直接用來計算 瞬時幀率 ,
瞬時掉幀小於2次可以認為沒有發生抖動,如果出現了單個Message執行過長,可認為發生了掉幀,流暢度與瞬時幀率監測大概就是這樣。不過,同啟動耗時類似,不同配置結果不同,不能用絕對時間衡量,只能橫向對比,簡單線上效果如下:
內存泄露有個比較出名的庫LeakCanary,實現原理也比較清晰,就是利用弱引用+ReferenceQueue,其實只用弱引用也可以做,ReferenceQueue只是個輔助作用,LeakCanary除了泄露檢測還有個堆棧Dump的功能,雖然很好,但是這個功能並不適合線上,而且,只要能監聽到Activity泄露,本地分析原因是比較快的,沒必要將堆棧Dump出來。因此,本文只實現Activity泄露監測能力,不在線上分析原因。而且,參考LeakCanary,改用一個WeakHashMap實現上述功能,不在主動暴露ReferenceQueue這個對象。WeakHashMap最大的特點是其key對象被自動弱引用,可以被回收,利用這個特點,用其key監聽Activity回收就能達到泄露監測的目的。核心實現如下:
線上選擇監測沒必要實時,將其延後到APP進入後台的時候,在APP進入後台之後主動觸發一次GC,然後延時10s,進行檢查,之所以延時10s,是因為GC不是同步的,為了讓GC操作能夠順利執行完,這里選擇10s後檢查。在檢查前分配一個4M的大內存塊,再次確保GC執行,之後就可以根據WeakHashMap的特性,查找有多少Activity還保留在其中,這些Activity就是泄露Activity。
內存檢測比較簡單,弄清幾個關鍵的指標就行,這些指標都能通過 Debug.MemoryInfo獲取
這里關心三個就行,
一般而言total是大於nativ+dalvik的,因為它包含了共享內存,理論上我們只關心native跟dalvik就行,以上就是關於內存的監測能力,不過內存泄露不是100%正確,暴露明顯問題即可,效果如下:
流量監測的實現相對簡單,利用系統提供的TrafficStats.getUidRxBytes方法,配合Actvity生命周期,即可獲取每個Activity的流量消耗。具體做法:在Activity start的時候記錄起點,在pause的時候累加,最後在Destroyed的時候統計整個Activity的流量消耗,如果想要做到Fragment維度,就要具體業務具體分析了,簡單實現如下
Android電量狀態能通過一下方法實時獲取,只是對於分析來說有點麻煩,需要根據不同手機、不同配置做聚合,單處採集很簡單
不過並不能獲取絕對電量,只能看百分比,因為對單個Activity來做電量監測並不靠譜,往往都是0,可以在APP推到後台後,對真個在線時長的電池消耗做監測,這個可能還能看出一些電量變化。
沒想好怎麼弄,顯不出力
APP端只是完成的數據的採集,數據的整合及根系還是要依賴後台數據分析,根據不同配置,不同場景才能制定一套比較合理的基線,而且,這種 基線肯定不是絕對 的,只能是相對的,這套基線將來可以作為頁面性能評估標准,對Android而言,挺難,機型太多。
GITHUB鏈接 Collie
2. android Activity調用onDestory後會不會銷毀activity裡面自定義view的引用的bitmap
bitmap不會立即被釋放掉,
onDestory不會立即釋放當前activity的所有資源,
3. Android中volley網路請求listener會造成內存泄露,怎麼解決
看下MySingleton.
getInstance拿了一個Context,在AskActivity里就是這個activity本身.
那麼考慮,某個時間點,Android決定把這個activity終止掉了,那麼在Android的lifecycle里看來就沒有這個activity的reference了.
在某個時間點重新"喚醒"這個activity的時候,以為在lifecycle的層面已經沒有這個activity的cache了,所以可能覺得create/new一個新的.
但實際上這個App的進程並沒有結束,也就是MySingleton里reference的還是老的AskActivity.
所以,這時候就有了兩個AskActivity.
而在Android的機制里之後後者才是有效的.
所以前者算leak了?
一個想法,不一定對.
如果對的話,解決辦法是把singleton跟activity的onCreate方法和onDestroy方法綁在一起.
因為實際上這個singleton的邏輯是對應一個activity/context唯一的語義.
4. Android之Activity全面解析,有些知識點容易忘記
Activity作為安卓四大組件之一,是最重要也是用得最多的組件,涉及的知識點非常多,有些知識點平時開發很少用到,但在某些場景下需要特別注意,本文詳細整理了Activity涉及的知識點,供開發參考。
針對Activity可以提出很多問題,如:
Activity 的生命周期?
Activity 之間的通信方式?
Activity 各種情況下的生命周期?
橫豎屏切換時 Activity 的生命周期?
前台切換到後台,然後再回到前台時 Activity 的生命周期?
彈出 Dialog 的時候按 Home 鍵時 Activity 的生命周期?
兩個Activity之間跳轉時的生命周期?
下拉狀態欄時 Activity 的生命周期?
Activity 與 Fragment 之間生命周期比較?
Activity 的四種 LaunchMode(啟動模式)的區別?
Activity 狀態保存與恢復?
Activity的轉場動畫有哪些實現方式?
Activity的生命周期中怎麼獲取控制項寬高?
onNewIntent的執行時機?
如何連續退出多個Activity?
如何把Acitivty設置成Dialog樣式 ,android:theme="@android:style/Theme.Dialog"
關於橫豎屏切換的生命周期,對應不同的手機,由於廠商定製的原因,會有不同的效果,如設置了configChanges="orientation」在有些手機會執行各個生命周期,但有些手機卻不會執行。
網上常見的結論如下:
但實際的測試如下:
可以看出,不同廠商的手機切屏生命周期會有差異。
從API 13以上,當設備在橫豎切屏時,「屏幕尺寸」也會發生變化,因此為了杜絕切屏導致頁面銷毀重建,需要加上screenSize,使用設置4,即 android:configChanges="orientation|keyboardHidden|screenSize" .
Activity的四種狀態如下:
在activity處於paused或者stoped狀態下,如果系統內存緊張,可能會被銷毀,當重回該activity時會重建,正常返回和被回收後返回的生命周期如下:
如果是回收後返回,onCreate的參數savedInstanceState不為空。
有哪些場景會觸發onNewIntent回調呢?跟啟動模式有關,首先該Activity實例已經存在,再次啟動才可能觸發。一種情況是啟動模式是singleTask或者singleInstance,無論該activity在棧中哪個位置,都會觸發onNewIntent回調,並且把上面其他acitivity移除,另一種情況是啟動模式是singleTop或者以FLAG_ACTIVITY_SINGLE_TOP啟動,並且該activity實例在棧頂,會觸發onNewIntent,如果不在棧頂是重新創建的,不會觸發。
在實際業務開發中,往往碰到需要連續退出多個activity實例,下面整理了幾種常見方法:
● 發送特定廣播
1、在需要處理連續退出的activity注冊該特定廣播;
2、發起退出的activity發送該特定廣播;
3、接收到該廣播的activity 調用finish結束頁面。
● 遞歸退出
1、用startActivityForResult啟動新的activity;
2、前一個頁面finish時,觸發onActvityResult回調,再根據requestCode和resultCode處理是否finish,達到遞歸退出的效果。
● FLAG_ACTIVITY_CLEAR_TOP
通過intent.setFlag(Intent.FLAG_ACTIVITY_CLEAR_TOP)啟動新activity,如果棧中已經有該實例,則會把該activity之上的所有activity關閉,達到singleTop啟動模式的效果。
● 自定義activity棧
1、自定義activity列表,新打開activity則加入棧中,關閉則移除棧;
2、需要退出多個activity時,則循環從棧中移除activity實例,並調用finish。
在討論Activity啟動模式經常提到任務棧,那到底什麼是任務棧?
任務是一個Activity的集合,它使用棧的方式來管理其中的Activity,這個棧又被稱為返回棧(back stack),棧中Activity的順序就是按照它們被打開的順序依次存放的。返回棧是一個典型的後進先出(last in, first out)的數據結構。下圖通過時間線的方式非常清晰地向我們展示了多個Activity在返回棧當中的狀態變化:
taskAffinity 任務相關性,可以用於指定一個Activity更加願意依附於哪一個任務,在默認情況下,同一個應用程序中的所有Activity都具有相同的affinity, 名字為應用的包名。當然了,我們可以為每個 Activity 都單獨指定 taskAffinity 屬性(不與包名相同)。taskAffinity 屬性主要和 singleTask 啟動模式和 allowTaskReparenting 屬性配對使用,在其他情況下沒有意義。
taskAffinity 有下面兩種應用場景:
分為顯示啟動和隱式啟動。
(1)顯示啟動
直接指定待調整的Activity類名。
(2)隱式啟動
Intent 能夠匹配目標組件的 IntentFilter 中所設置的過濾信息,如果不匹配將無法啟動目標 Activity。IntentFilter 的過濾信息有 action、category、data。
IntentFilter 需要注意的地方有以下:
● 一個 Activity 中可以有多個 intent-filter
● 一個 intent-filter 同時可以有多個 action、category、data
● 一個 Intent 只要能匹配任何一組 intent-filter 即可啟動對應 Activity
● 新建的 Activity 必須加上以下這句,代表能夠接收隱式調用
<category android:name="android.intent.category.DEFAULT" />
只要匹配一個action即可跳轉,注意的是action要區分大小寫。
規則:如果intent中有category,則所有的都能匹配到intent-filter中的category,intent中的category數量可用少於intent-filter中的。另外,單獨設置category是無法匹配activity的,因為category屬性是一個執行Action的附加信息。
intent不添加category會匹配默認的,即 「android:intent.category.DEFAULT」
如果上面例子,如果去掉intent.setAction("action_name"),則會拋出異常:
規則:類似action,但data有復雜的結構,只要匹配一個data並且與data中所有屬性都一致就能匹配到Activity,只要有1個屬性不匹配,都無法找到activity。
data的結構:
data 主要是由 URI 和 mimeType 組成的。
URI 可配置很多信息,的結構如下:
與url類似,例如:
mineType:指資源類型包括文本、圖片、音視頻等等,例如:text/plain、 image/jpeg、video/* 等
下面看下data匹配的例子:
只匹配scheme
只匹配scheme也是能匹配到activity的。
匹配scheme、host、port
將上面的data改為
匹配mineType
如果有mineType,則不能僅設置setData或setMineType了,因為setData會把mineType置為null,而setMineType會把data置為null,導致永遠無法匹配到activity,要使用setDataAndType。
使用scheme的默認值contentfile
注意該方法需要在startAtivity方法或者是finish方法調用之後立即執行,不能延遲,但可以在子線程執行。
而在windowAnimationStyle中存在四種動畫:
activityOpenEnterAnimation // 打開新的Activity並進入新的Activity展示的動畫
activityOpenExitAnimation // 打開新的Activity並銷毀之前的Activity展示的動畫
activityCloseEnterAnimation //關閉當前Activity進入上一個Activity展示的動畫
activityCloseExitAnimation // 關閉當前Activity時展示的動畫
overridePendingTransition的方式比較生硬,方法也比較老舊了,不適用於MD風格,google提供了新的轉場動畫ActivityOptions,並提供了兼容包ActivityOptionsCompat。
我們知道在onCreate和onResume裡面直接獲取到控制項寬高為0,那有什麼辦法獲取到控制項的實際寬高?只要有onWindowFocusChanged、view.post、ViewTreeObserver三種方式獲取。
當用戶點擊桌面圖標啟動APP時,背後的流程如下:
我們看到的手機桌面是Launch程序的界面,點擊應用圖標會觸發點擊事件,調用startActivity(intent),然後通過Binder IPC機制,與ActivityManagerService(AMS)通訊,AMS執行一系列操作,最終啟動目前應用,大概流程如下:
通過PackageManager的resolveIntent()收集跳轉intent對象的指向信息,然後通過grantUriPermissionLocked()方法來驗證用戶是否有足夠的許可權去調用該intent對象指向的Activity。如果有許可權,則在新的task中啟動目標activity,如果發現沒有進程,則先創建進程。
如果進程不存在,AMS會調用startProcessLocked創建新的進程,在該方法中,會通過socket的通訊方式通知zygote進程孵化新的進程並返回pid,在新的進程中會初始化ActivityThread,並依次調用Looper.prepareLoop()和Looper.loop()來開啟消息循環。
創建好進程後下一步要將Application和進程綁定起來,AMS會調用上一節創建的ActivityThread對象的bindAppliction方法完成綁定工作,該方法會發送一條BIND_APPLICATION的消息,最終會調用handleBindApplication方法處理消息,並調用makeApplication方法處理消息,載入APP的classes到內存中。
通過前面的步驟,系統已經擁有了該Application的進程,後續的啟動則是從已存在其他進程中啟動Acitivity,即調用realStartAcitvityLocked,該方法會調用Application的主線程對象ActivityThread的sheleLaunchActivity方法,在方法中會發送LAUNCH_ACTIVITY到消息隊列,最終通過handleLaunchActivity處理消息,完成Acitivty的啟動。
Activity
Activity 的 36 大難點,你會幾個?「建議收藏」
[譯]Android Application啟動流程分析
5. android,想在工具類中顯示對話框,如何獲取主activity的context
在android中context可以作很多操作,但是最主要的功能是載入和訪問資源。在android中有兩種context,一種是
application
context,一種是activity
context,通常我們在各種類和方法間傳遞的是activity
context。
比如一個activity的onCreate:
protected
void
onCreate(Bundle
state)
{
super.onCreate(state);
TextView
label
=
new
TextView(this);
//傳遞context給view
control
label.setText("Leaks
are
bad");
setContentView(label);
}
把activity
context傳遞給view,意味著view擁有一個指向activity的引用,進而引用activity佔有的資源:view
hierachy,
resource等。
這樣如果context發生內存泄露的話,就會泄露很多內存。
這里泄露的意思是gc沒有辦法回收activity的內存。
Leaking
an
entire
activity是很容易的一件事。
當屏幕旋轉的時候,系統會銷毀當前的activity,保存狀態信息,再創建一個新的。
6. Android-LeakCanary原理解析
在分析LeakCanary原理之前,首先需要了解ReferenceQueue在LeakCanary的作用。
WeakReference在創建時,如果指定一個ReferenceQueue對象,在垃圾回收檢測到被引用的對象的可達性更改後,垃圾回收器會將已注冊的引用對象添加到ReferenceQueue對象中,等待ReferenceQueue處理。但是如果當GC過後引用對象仍然不被加入ReferenceQueue中,就可能存在內存泄露問題。這里ReferenceQueue對象中,存的其實就是WeakReference對象,而不是WeakReference中引用的要被回收的對象。即GC過後,WeakReference引用的對象被回收了,那麼WeakReference引用的對象就是null,那麼該WeakReference對象就會被加入到ReferenceQueue隊列中。
所以我們可以通過監聽 Activity.onDestroy() 回調之後,通過弱引用(WeakReference)對象、ReferenceQueue和 GC來觀測Activity引用的內存泄露情況,如果發現了未被回收的Activity對象,在找到該Activity對象是否被其他對象所引用,如果被其他對象引用,就進行 heap mp生成完整的內存引用鏈(最短引用鏈),並通過notification等方式展示出來。
LeakCanary2.+的啟動,與LeakCanary1.+的不同,1.+版本的啟動,需要在Application的onCreate中手動調用LeakCanary.install方法進行啟動;而2.+版本的啟動則不需要,而是依賴ContentProvider,因為ContentProvider會在Application之前被載入,所以ContentProvider的onCreate方法會在Application的onCreate方法之前被調用,所以在ContentProvider的onCreate方法中完成初始化工作。
在源碼中leakcanary-leaksentry中有一個LeakSentryInstaller,LeakSentryInstaller其實就是ContentProvider的一個子類,在其onCreate方法中就會調用InternalLeakSentry.install(application)進行初始化工作。
然後在AndroidManifest.xml中注冊該ContentProvider。在這里注冊,那麼打包項目時,會將每個庫和library中的AndroidManifest.xml合並到最終的app的androidManifest中。
LeakCanary的初始化是在InternalLeakSentry的install方法,即在ContentProvider的onCreate中調用。
這里的listener是LeakSentryListener介面,而實現LeakSentryListener介面的類,其實就是InternalLeakCanary,InternalLeakCanary是在leakcanary-android-core下的,InternalLeakCanary是單例模式的,採用的是kotlin單例,即用object關鍵字修飾類。
這里使用的RefWatcher對象,是在InternalLeakSentry中進行初始化的,然後在調用ActivityDestroyWatcher和FragmentDestroyWatcher的install方法的時候,傳入。
在監測Activity和Fragment的生命周期進行內存回收以及是否泄露的過程,就是調用RefWatcher.watch方法進行,該方法是使用Synchronized修飾的同步方法。RefWatcher.watch的方法,一般是在Activity和Fragment生命周期執行到onDestroy的時候調用。根據生命周期監聽觸發回調,然後調用RefWatcher.watch方法。
VisibilityTracker其實就是在InternalLeakCanary.onLeakSentryInstalled方法中通過調用application.registerVisibilityListener方法的時候,添加的Application.ActivityLifecycleCallbacks,這里採用適配器模式,使用適配器模式的目的,其實就是不需要重寫所有方法,只在VisibilityTracker中重寫需要使用的方法。
VisibilityTracker的目的其實就是監聽Activity的生命周期變化,即是否是執行到了onStart和onStop,如果是onStop的時候,則做內存泄露監測工作。
VisibilityTracker與ActivityDestroyWatcher有點區別,ActivityDestroyWatcher是最終Activity執行onDestroy的時候進行內存泄露分析
本方法是在InternalLeakCanary.onLeakSentryInstalled給application添加生命周期回調的時候,根據onStart和onStop生命周期的變化來進行Heap Dump(heap mp文件(.hprof))
當生命周期執行到onStop的時候,會向該Application的擴展函數registerVisibilityListener的參數listener這個高階函數傳入boolean參數為false
看InternalLeakCanary#onLeakSentryInstalled方法中對application添加的生命周期監聽,這是調用了application的擴展函數,該擴展函數是在VisibilityTracker中定義的。
其實registerVisibilityListener方法內部調用的就是application的方法,傳入的是Application.ActivityLifecycleCallbacks對象,這里傳入的是VisibilityTracker,其實VisibilityTracker就是Application.ActivityLifecycleCallbacks的子類實現。
HeapDumpTrigger.方法的調用,就是根據上述傳給VisibilityTracker的listener函數來回調調用的,listener接收的是false的時候,就會調用scheleRetainedInstanceCheck,接收的是false的時候是生命周期執行到onStop的時候。
這里的delayMillis默認是5s,因為該參數接收的是LeakSentry.config.watchDurationMillis,這個值初始默認值是5s。