卡頓原因
人眼能感覺到的幀率是每秒24幀巧鸭,而屏幕每16毫秒會刷新一次瓶您,也就是每秒會刷新60次。當(dāng)每秒刷新次數(shù)少于60次,即出現(xiàn)掉幀呀袱,則會感覺到卡頓贸毕。
關(guān)于屏幕刷新機(jī)制可以看繪制流程
卡頓原因主要有以下幾點(diǎn):
- 根據(jù)Handler消息分發(fā)機(jī)制,主線程所有操作都是存在于Loop的循環(huán)中夜赵,當(dāng)某條Message執(zhí)行耗時操作(即主線程存在耗時操作)明棍,就會導(dǎo)致消息隊(duì)列中的Message消息被阻塞
- 消息隊(duì)列中存在同步屏障,同步消息得不到執(zhí)行寇僧。
比如當(dāng)點(diǎn)擊一個按鈕后摊腋,通過Handler post 執(zhí)行一個動畫(這是一個同步消息),如果此時存在同步屏障嘁傀,則動畫會等到同步屏障被移出后才會執(zhí)行兴蒸,就會感覺到卡頓 - View布局計(jì)算耗時(如層級太多),無法在16ms內(nèi)計(jì)算完成细办,導(dǎo)致jank掉幀
- 內(nèi)存占用過大橙凳、GC頻繁、CPU占用過多都會導(dǎo)致卡頓笑撞,檢測下是否有內(nèi)存泄漏痕惋、大對象、對象頻繁創(chuàng)建娃殖、io頻繁等情況
ANR
ANR類型
- InputDispatchTimeout:5s內(nèi)無法響應(yīng)用戶輸入事件(例如鍵盤輸入, 觸摸屏幕等).
- BroadcastTimeout:比如前臺廣播在10s內(nèi)未執(zhí)行完成(后臺廣播60s)
- ServiceTimeout:比如前臺服務(wù)在20s內(nèi)未執(zhí)行完成(后臺服務(wù)200s)
- ContentProviderTimeout:內(nèi)容提供者值戳,在publish過超時10s;
原因
主線程(UI線程)里面做了太多的阻塞耗時操作
普通阻塞導(dǎo)致的ANR
CPU滿負(fù)荷
內(nèi)存原因(OOM)
分析
ANR發(fā)生后會在data/anr下生成trace.txt
一般來講直接先看tid=1的堆棧即對應(yīng)主線程,因?yàn)锳NR都是主線程執(zhí)行超時導(dǎo)致
案例一:主線程Binder調(diào)用等待超時
很明顯當(dāng)時在做Binder通信炉爆,并沒有waiting to lock等代表死鎖的字樣堕虹,那么說明這個案例即有可能是在等Binder對端響應(yīng),我們知道Binder通信對于發(fā)起方來說是默認(rèn)是阻塞等待響應(yīng)芬首,只有有了返回結(jié)果后才會繼續(xù)執(zhí)行下去赴捞,當(dāng)然了可以給接口設(shè)置oneway聲明,這樣的話binder請求就是異步請求郁稍,這里不多說
所以赦政,如上這個案例中需要找到對端是哪個進(jìn)程,這個進(jìn)程當(dāng)時在做什么耀怜,這時候就需要找到anr文件夾下另外一個文件binderinfo恢着,這里需要找到與我們發(fā)起方進(jìn)程1461通信的是哪個進(jìn)程
可以看到是1666號這個進(jìn)程,再回到trace中看下财破,這個進(jìn)程當(dāng)時在做什么
可以看到當(dāng)時對端在做消息的讀取掰派,也就是說這里出了問題,很明顯這里我們無法修改左痢,我們這個問題在于主線程執(zhí)行了Binder請求靡羡,對端遲遲未返回便很容易出現(xiàn)這個問題
解決:步中執(zhí)行
案例二:主線程等待鎖
關(guān)鍵字:waiting to lock
main thread在執(zhí)行UploaderChimeraService的onDestroy方法時系洛,需要lock 0x23f65d8b,但這個lock有被upload_periodic GCM Task 拿住略步,這個thread當(dāng)前是在做連接網(wǎng)絡(luò)的動作描扯。從這段信息來看,很有可能與測試時手機(jī)連接的網(wǎng)絡(luò)有關(guān)趟薄,當(dāng)時連接的事google的網(wǎng)絡(luò)绽诚,由于墻的原因,無法連接gms的相關(guān)server有關(guān)
還有一種情況就是死鎖竟趾,即形成了頭尾相連憔购,互相等待的情況
解決:一般會嘗試將鎖改為超時鎖,比如lock的trylock岔帽,超時會自動釋放鎖玫鸟,從而避免一直持有鎖的情況發(fā)生
案例三:卡在IO上
這種情況一般是和文件操作相關(guān),判斷是否是這種情況犀勒,可以看mainlog中搜索關(guān)鍵字"ANR in",看這段信息的最下邊屎飘,比如下面的信息 ANRManager: 100% TOTAL: 2% user + 2.1% kernel + 95% iowait + 0.1% softirq 很明顯,IO占比很高贾费,這個時候就需要查看trace日志看當(dāng)時的callstack钦购,或者在這段ANR點(diǎn)往前看0~4s,看看當(dāng)時做的什么文件操作
解決:對耗時文件操作采取異步操作
案例四:主線程有耗時的動作
這種情況是ANR類型問題里遇到最多的褂萧,比如網(wǎng)絡(luò)訪問押桃,訪問數(shù)據(jù)庫之類的,都很容易造成主線程堵塞导犹,
這里以訪問數(shù)據(jù)庫來說唱凯,這類型引起的ANR,一般來講看當(dāng)時的CPU使用情況會發(fā)現(xiàn)user占比較高谎痢,看trace中主線程當(dāng)時的信息會發(fā)現(xiàn)會有一些比如query像ContentProvider這種數(shù)據(jù)庫的動作磕昼。這種情況下,還可以去看eventlog或者mainlog节猿,在ANR發(fā)生前后打印出來的信息票从,比如訪問數(shù)據(jù)庫這種,在eventlog中搜索"am_anr",然后看前后片段滨嘱,會發(fā)現(xiàn)發(fā)生ANR的這個進(jìn)程有很多數(shù)據(jù)庫相關(guān)的信息峰鄙,說明在發(fā)生ANR前后主線程一直在忙于訪問數(shù)據(jù)庫,這類型的問題常見于圖庫九孩,聯(lián)系人先馆,彩短信應(yīng)用。
解決:一般考慮的是異步解決躺彬,異步解決并不是簡單的new一個線程煤墙,要根據(jù)業(yè)務(wù)場景以及頻率來決定,Android常見的異步AsyncTask, IntentService, 線程池(官方四種或自定義), new thread等宪拥,一般來說不建議直接new thread
案例五:binder線程池被占滿
系統(tǒng)對每個process最多分配15個binder線程仿野,這個是谷歌的設(shè)計(jì)
如果另一個process發(fā)送太多重復(fù)binder請求,那么就會導(dǎo)致接收端binder線程被占滿她君,從而處理不了其它的binder請求
這時候請求端發(fā)起的請求就會阻塞等待了(未設(shè)置異步請求的前提下)脚作,這本身就是系統(tǒng)的一個限制,如果應(yīng)用未按照系統(tǒng)的要求來實(shí)現(xiàn)對應(yīng)邏輯缔刹,那么就會造成問題球涛。
而系統(tǒng)端是不會(也不建議)通過修改系統(tǒng)行為來兼容應(yīng)用邏輯,否則更容易造成其它根據(jù)系統(tǒng)需求正常編寫的應(yīng)用反而出現(xiàn)不可預(yù)料的問題校镐。
判斷Binder是否用完亿扁,可以在trace中搜索關(guān)鍵字"binder_f",如果搜索到則表示已經(jīng)用完鸟廓,然后就要找log其他地方看是誰一直在消耗binder或者是有死鎖發(fā)生
解決:解決的思路就是降低極短時間內(nèi)大量Binder請求的發(fā)生从祝,修復(fù)的手法是發(fā)送BInder請求的函數(shù)中做時間差過濾,限定在500ms內(nèi)最多執(zhí)行一次
案例六:NullPointerException導(dǎo)致ANR
ANR前出現(xiàn)頻繁NE引谜,NE所在的進(jìn)程與ANR的進(jìn)程有交互牍陌,在解決了NE后,ANR也不復(fù)存在员咽,對于這類在ANR前有JE或者NE毒涧,一般思路是先解決JE或NE,因?yàn)镴E/NE發(fā)生時會去dump一大堆異常信息贝室,本身也會加重CPU loading契讲,修改完異常后再來看ANR是否還存在,如果還存在档玻,那么就看trace 堆棧怀泊,如果不存在,則可以基本判定是JE或NE導(dǎo)致
案例七:只存在于Monkey測試下
有些問題是只有在Monkey環(huán)境下才能跑出來误趴,平時的user版本用戶使用是不會出現(xiàn)的霹琼,這種問題的話就沒有改動的意義。
比如下面這個例子:
ActivityManager: Not finishing activity because controller resumed
03-18 07:25:50.901 810 870 I am_anr : [0,25443,android.process.media,1086897733,Input dispatching timed out (Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)]
發(fā)生這個ANR的原因是Contoller將resume的操作給攔截了, 導(dǎo)致Focus不過去, 從而導(dǎo)致ANR凉当,User版本不會有Contoller, 所以不會出現(xiàn)這個 ANR. 所以這個 ANR 可以忽略.
觸發(fā)流程
如下引用該文:理解Android ANR的觸發(fā)原理 分別記錄了由Service枣申、BroadcastReceiver、ContentProvider看杭、Input系統(tǒng)造成的ANR忠藤。
觸發(fā)ANR的過程可分為三個步驟: 埋炸彈, 拆炸彈, 引爆炸彈
Service
Service Timeout是位于”ActivityManager”線程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息時觸發(fā)。
對于Service有兩類:
- 對于前臺服務(wù)楼雹,則超時為SERVICE_TIMEOUT = 20s模孩;
- 對于后臺服務(wù)尖阔,則超時為SERVICE_BACKGROUND_TIMEOUT = 200s
由變量ProcessRecord.execServicesFg來決定是否前臺啟動
埋炸彈
當(dāng)發(fā)起Service啟動請求時,通過Handler發(fā)起SERVICE_TIMEOUT_MSG延遲消息
// ActiveServices
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
...
// 發(fā)送delay消息(SERVICE_TIMEOUT_MSG)
bumpServiceExecutingLocked(r, execInFg, "create");
try {
...
// 創(chuàng)建服務(wù)榨咐,最終執(zhí)行服務(wù)的onCreate()方法
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
} catch (DeadObjectException e) {
mAm.appDiedLocked(app);
throw e;
} finally {
...
}
}
// ActiveServices
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
...
scheduleServiceTimeoutLocked(r.app);
}
// ActiveServices
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
long now = SystemClock.uptimeMillis();
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
//當(dāng)超時后仍沒有remove該SERVICE_TIMEOUT_MSG消息介却,則執(zhí)行service Timeout流程
mAm.mHandler.sendMessageAtTime(msg,
proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}
拆炸彈
當(dāng)service啟動完成,則移除服務(wù)超時消息SERVICE_TIMEOUT_MSG块茁。
// ActivityThread
private void handleCreateService(CreateServiceData data) {
...
java.lang.ClassLoader cl = packageInfo.getClassLoader();
Service service = (Service) cl.loadClass(data.info.name).newInstance();
...
try {
//創(chuàng)建ContextImpl對象
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
//創(chuàng)建Application對象
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
//調(diào)用服務(wù)onCreate()方法
service.onCreate();
//拆除炸彈引線
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (Exception e) {
...
}
}
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) {
...
if (r.executeNesting <= 0) {
if (r.app != null) {
r.app.execServicesFg = false;
r.app.executingServices.remove(r);
if (r.app.executingServices.size() == 0) {
// 當(dāng)前服務(wù)所在進(jìn)程中沒有正在執(zhí)行的service
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
...
}
...
}
引爆炸彈
service.onCreate()執(zhí)行超時齿坷,導(dǎo)致SERVICE_TIMEOUT_MSG消息沒有被移除,則會觸發(fā)ANR
// ActivityManagerService
final class MainHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case SERVICE_TIMEOUT_MSG: {
...
mServices.serviceTimeout((ProcessRecord)msg.obj);
} break;
...
}
...
}
}
// ActiveServices
void serviceTimeout(ProcessRecord proc) {
...
if (anrMessage != null) {
// 當(dāng)存在timeout的service数焊,則執(zhí)行ProcessRecord.appNotResponding
proc.appNotResponding(proc, null, null, false, anrMessage);
}
}
小結(jié):
埋炸彈:當(dāng)發(fā)起Service啟動請求時永淌,通過Handler發(fā)起SERVICE_TIMEOUT_MSG延遲消息
拆炸彈:當(dāng)service啟動完成,則移除服務(wù)超時消息SERVICE_TIMEOUT_MSG
引爆炸彈:service.onCreate()執(zhí)行超時佩耳,導(dǎo)致SERVICE_TIMEOUT_MSG消息沒有被移除遂蛀,則會觸發(fā)ANR
BroadcastReceiver
BroadcastReceiver Timeout是位于”ActivityManager”線程中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息時觸發(fā)。
對于廣播隊(duì)列有兩個: foreground隊(duì)列和background隊(duì)列:
- 對于前臺廣播蚕愤,則超時為BROADCAST_FG_TIMEOUT = 10s答恶;
- 對于后臺廣播,則超時為BROADCAST_BG_TIMEOUT = 60s
broadcast跟service超時機(jī)制大抵相同萍诱,但有一個非常隱蔽的技能點(diǎn)悬嗓,那就是通過靜態(tài)注冊的廣播超時會受SharedPreferences(簡稱SP)的影響,只有XML靜態(tài)注冊的廣播超時檢測過程會考慮是否有SP尚未完成裕坊,動態(tài)廣播并不受其影響包竹。
小結(jié)
埋炸彈:當(dāng)該廣播所有的接收者處理前,通過Handler發(fā)起B(yǎng)ROADCAST_TIMEOUT_MSG延遲消息
拆炸彈:當(dāng)廣播處理完成籍凝,則移除超時消息BROADCAST_TIMEOUT_MSG
引爆炸彈:廣播處理超時周瞎,導(dǎo)致BROADCAST_TIMEOUT_MSG消息沒有被移除,則會觸發(fā)ANR
ContentProvider
ContentProvider Timeout是位于”ActivityManager”線程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息時觸發(fā)饵蒂。
ContentProvider 超時為CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s
小結(jié):
埋炸彈:進(jìn)程創(chuàng)建后声诸,通過Handler發(fā)起CONTENT_PROVIDER_PUBLISH_TIMEOUT延遲消息
拆炸彈:當(dāng)provider成功publish之后,則移除服務(wù)超時消息CONTENT_PROVIDER_PUBLISH_TIMEOUT
引爆炸彈:publish超時退盯,導(dǎo)致CONTENT_PROVIDER_PUBLISH_TIMEOUT消息沒有被移除彼乌,則會觸發(fā)ANR
總結(jié)
當(dāng)出現(xiàn)ANR時,都是調(diào)用到AMS.appNotResponding()方法渊迁,詳細(xì)過程見文章理解Android ANR的信息收集過程. 這里介紹的provider例外.
Timeout時長
對于前臺服務(wù)慰照,則超時為SERVICE_TIMEOUT = 20s;
對于后臺服務(wù)琉朽,則超時為SERVICE_BACKGROUND_TIMEOUT = 200s
對于前臺廣播毒租,則超時為BROADCAST_FG_TIMEOUT = 10s;
對于后臺廣播箱叁,則超時為BROADCAST_BG_TIMEOUT = 60s;
ContentProvider超時為CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s;
超時檢測
Service超時檢測機(jī)制:
- 超過一定時間沒有執(zhí)行完相應(yīng)操作來觸發(fā)移除延時消息墅垮,則會觸發(fā)anr;
BroadcastReceiver超時檢測機(jī)制:
- 有序廣播的總執(zhí)行時間超過 2* receiver個數(shù) * timeout時長惕医,則會觸發(fā)anr;
- 有序廣播的某一個receiver執(zhí)行過程超過 timeout時長,則會觸發(fā)anr;
另外:
- 對于Service, Broadcast, Input發(fā)生ANR之后,最終都會調(diào)用AMS.appNotResponding;
- 對于provider,在其進(jìn)程啟動時publish過程可能會出現(xiàn)ANR, 則會直接殺進(jìn)程以及清理相應(yīng)信息,而不會彈出ANR的對話框. appNotRespondingViaProvider()過程會走appNotResponding(), 這個就不介紹了噩斟,很少使用曹锨,由用戶自定義超時時間.
關(guān)于input的ANR觸發(fā)過程孤个,見Input系統(tǒng)—ANR原理分析
卡頓監(jiān)控
替換 Looper 的 Printer
- 計(jì)算Looper兩次獲取消息的時間差剃允,如果時間太長(比如3s)就說明Handler處理時間過長,直接把堆棧信息打印出來齐鲤,就可以定位到耗時代碼斥废。
- println 方法參數(shù)涉及到字符串拼接,考慮性能問題给郊,所以這種方式只推薦在Debug模式下使用牡肉。基于此原理的開源庫代表是:BlockCanary
// Looper
public void setMessageLogging(@Nullable Printer printer) {
mLogging = printer;
}
public static void loop() {
...
for (;;) {
// 讀取消息
Message msg = queue.next();
if (msg == null) {
return;
}
final Printer logging = me.mLogging;
if (logging != null) {
// 處理消息前回調(diào)一次
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
...
// 處理消息
msg.target.dispatchMessage(msg);
...
if (logging != null) {
// 處理消息后回調(diào)一次
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
基于消息隊(duì)列監(jiān)控卡頓
1.執(zhí)行消息前淆九,通過HandlerThread的Handler發(fā)送一個延遲消息到子線程统锤,時間為定義的閾值
2.執(zhí)行這條消息后,移除消息
3.如果消息耗時炭庙,延遲消息未移出并得到執(zhí)行饲窿,獲取主線程的堆棧信息
這個方案存在問題:
當(dāng)某條消息執(zhí)行如ABC三個方法,而A為耗時的方法焕蹄,但是獲取到的堆椨庑郏可能為B或者C,
而BlockCanary不存在此問題
BlockCanary原理
- 執(zhí)行消息前腻脏,開啟循環(huán)鸦泳,每300ms獲取一次堆棧,并保存下來
- 執(zhí)行完消息永品,停止循環(huán)做鹰,獲取耗時的堆棧
插入空消息到消息隊(duì)列
- 通過一個監(jiān)控線程,每隔1秒向主線程消息隊(duì)列的頭部插入一條空消息鼎姐。
- 假設(shè)1秒后這個消息并沒有被主線程消費(fèi)掉钾麸,說明阻塞消息運(yùn)行的時間在0~1秒之間。換句話說症见,如果我們需要監(jiān)控3秒卡頓喂走,那在第4次輪詢中,頭部消息依然沒有被消費(fèi)的話谋作,就可以確定主線程出現(xiàn)了一次3秒以上的卡頓芋肠。
插樁
編譯過程插樁(例如使用AspectJ),在方法入口和出口加入耗時監(jiān)控的代碼遵蚜。
插樁前
public void test(){
doSomething();
}
插樁后
public void test(){
long startTime = System.currentTimeMillis();
doSomething();
long methodTime = System.currentTimeMillis() - startTime;//計(jì)算方法耗時
}
缺點(diǎn):
- 無法監(jiān)控系統(tǒng)方法
- apk體積會增大
需要注意:
- 過濾簡單的方法
- 只需要監(jiān)控主線程執(zhí)行的方法
Profiler
Traceview 已棄用帖池,應(yīng)使用 CPU Profiler 來執(zhí)行以下操作:檢查通過使用 Debug 類對應(yīng)用進(jìn)行插樁檢測而捕獲的 .trace 文件奈惑、記錄新方法跟蹤信息、保存 .trace 文件以及檢查應(yīng)用進(jìn)程的實(shí)時 CPU 使用情況睡汹。
cpu profile是Android Profiler里面的一個功能
FPS檢測
gpu呈現(xiàn)模式分析
打開開發(fā)者模式肴甸,選擇gpu呈現(xiàn)模式分析∏舭停可以在屏幕上看到豎條原在,代表每一幀所耗費(fèi)的時間,不同的顏色代表在繪制的不同階段的耗時彤叉,可以據(jù)此判斷哪些階段存在問題庶柿。橫向的綠線代表16.67ms的基線,只有耗費(fèi)的時長小于16.67ms才不會出現(xiàn)丟幀的情況秽浇。
Choreographer
基于Choreographer浮庐,通過Choreographer.postFrameCallBack獲取每一幀的回調(diào),可以計(jì)算出每一秒的幀數(shù)FPS
- Choreographer是一個ThreadLocal變量柬焕,UI線程里可以任務(wù)是一個單例审残,接受VSync信號進(jìn)行界面的渲染
- 界面不發(fā)生改變時,VSync信號也會傳遞
Window.OnFrameMetricsAvailableListener
OnFrameMetricsAvailableListener是Android在api 24版本加入的強(qiáng)大新功能斑举,通過設(shè)置OnFrameMetricsAvailableListener回調(diào)可以獲得每一幀每一個階段的耗時搅轿,以及總的耗時,并且可以獲得當(dāng)前幀是否是第一次繪制懂昂,android官方認(rèn)為第一幀由于存在大量初始化的代碼介时,其第一幀的數(shù)據(jù)不應(yīng)記在FPS內(nèi)。
線上卡頓監(jiān)控
Matrix
原理總結(jié)
Looper(FrameTracer)
- 通過反射獲取主線程Looper中的Printer(originPrinter)凌彬,并替換成自己的Printer
Printer originPrinter = ReflectUtils.get(looper.getClass(), "mLogging", looper);
looper.setMessageLogging(new LooperPrinter(originPrinter));
- 通過Printer獲取到每一個Message的開始及結(jié)束調(diào)用時機(jī)沸柔,以及耗時
- 計(jì)算幀率
Choreographer(FrameTracer)
反射獲取Choreographer中CALLBACK_INPUT、CALLBACK_ANIMATIO铲敛、CALLBACK_TRAVERSAL(繪制)三種類型回調(diào)的隊(duì)列CallbackQueue褐澎,并注冊三種類型的回調(diào)到隊(duì)列的頭部
由于回調(diào)在頭部,可以監(jiān)聽并計(jì)算出前兩種類型回調(diào)的執(zhí)行耗時
配合Looper的Printer方案伐蒋,通過Message的結(jié)束時機(jī)工三,可以計(jì)算出第三種類型的執(zhí)行耗時
注:VSync信號回調(diào)是通過Handler發(fā)送同步屏障+異步消息的機(jī)制執(zhí)行的
- 計(jì)算幀率
慢方法(EvilMethodTracer)
- ASM埋點(diǎn),記錄方法耗時及調(diào)用棧先鱼,通過一個long數(shù)組記錄是否是進(jìn)入方法及方法id及時間戳
注:通過一個映射表記錄俭正,記錄方法名與方法id的對應(yīng)關(guān)系,可以節(jié)省日志大小
- 通過Looper及Choreographer監(jiān)聽主線程Message或者每一幀的耗時焙畔,如果大于設(shè)定闕值(默認(rèn)700ms)掸读,則通過long計(jì)算出方法調(diào)用棧及耗時,并進(jìn)行裁切
- 通過mmap機(jī)制保存日志
冷啟動(StartupTracer)
- 繼承Application,實(shí)現(xiàn)onCreate方法儿惫;在ASM埋點(diǎn)中澡罚,會在onCreate方法中插入埋點(diǎn),此時記錄應(yīng)用啟動開始時間
- 反射獲取ActivityThread中的mH(Handler)肾请,反射替換mH中的callback留搔,在回調(diào)中的方法之后,記錄應(yīng)用啟動結(jié)束時間(Application啟動時間)
注:根據(jù)應(yīng)用啟動流程铛铁,應(yīng)用啟動時隔显,AMS會通過s應(yīng)用進(jìn)程的ApplicationThread Binder對象通知應(yīng)用進(jìn)程創(chuàng)建Application,ApplicationThread通過mH(Handler)將消息發(fā)送到主線程創(chuàng)建Application避归,所以在mH中的callback中荣月,即可獲取到創(chuàng)建完Application之后的時間
通過步驟1、2梳毙,即可計(jì)算出Application啟動時間
ASM埋點(diǎn)中,如果Class為Activity捐下,檢查是否有實(shí)現(xiàn)onWindowFocusChanged方法账锹,沒有則插入onWindowFocusChanged方法,并插入回調(diào)方法坷襟,通知Matrix奸柬,即為Activity啟動完成時機(jī)
通過步驟1、4婴程,即可計(jì)算出冷啟動到第一屏的啟動時間
熱啟動(StartupTracer)
- 通過Application.registerActivityLifecycleCallbacks注冊Activity生命周期回調(diào)廓奕,監(jiān)聽onCreate時機(jī)
- 熱啟動時間則為onWindowFocusChanged的時間-onCreate時間
ANR(LooperAnrTracer)
- 通過Looper,在每一個Message的執(zhí)行前档叔,通過Handler發(fā)送一個5s的延時消息桌粉,在Message執(zhí)行之后,將延時消息移除衙四;當(dāng)延時消息得到處理铃肯,則認(rèn)定為發(fā)生了ANR,輸出內(nèi)存占用信息及方法調(diào)用棧耗時等信息
- 步驟1的延時時間設(shè)為2s传蹈,則認(rèn)為發(fā)生卡頓
JVMTI(JVM tool interface)
- JVMTI位于jpda 最底層押逼,是Java 虛擬機(jī)所提供的native編程接口。 JVMTI可以提供性能分析惦界、debug挑格、內(nèi)存管理、線程分析等功能沾歪。
- VMTI是一套本地編程接口漂彤,因此使用JVMTI,需要與c/c++ 以及JNI打交道。事實(shí)上显歧,開發(fā)時一般采用建立一個Agent的方式來使用JVMTI仪或,Agent使用jvmti函數(shù),設(shè)置一些回調(diào)函數(shù)士骤,并從Java虛擬機(jī)中得到當(dāng)前的運(yùn)行態(tài)信息范删,并作出自己的判斷, 最后還可能操作虛擬機(jī)的運(yùn)行態(tài)。把Agent編譯成一個動態(tài)鏈接庫之后拷肌,可以再Java程序啟動時來加載它(比如IDE調(diào)試時使用的libjdwp.so 就是采用這種方式)到旦,當(dāng)然也可以通過Attach方式,中途加入(比如jmap, jps,jstack 等等)巨缘。
- 通過JVMTI可以實(shí)現(xiàn)對象創(chuàng)建添忘,GC,ANR若锁,OOM等的監(jiān)控
- Android 9.0 加入了JVMTI的支持