原創(chuàng)文章,轉(zhuǎn)載請注明出處,多謝!
如果cpu頻率、調(diào)度 和 compiler filter都一一排除了,沒問題。那接下來就看是否有具體方法耗時。
一、常用的分析手段:
1.systrace
這里可按systrace中各個階段來逐段對比分析,當(dāng)然這里也分冷熱啟。
冷啟動可以拆分如下若干階段:
deliver input / fork process / bind application / activity start / doFrame / drawFrame / SF invalidate&refresh
熱啟動就主要考慮繪制和渲染了。
看是否差距集中在一個某個階段內(nèi),如果是特定區(qū)域的差異那么就來針對具體方法耗時進(jìn)行分析。
找到有差異的階段可以通過加trace,來縮小范圍和細(xì)化具體方法。
各層加trace的方式:
APP:
Trace.beginSection("");
Trace.endSection();
注:抓systrace的時候需要指定對應(yīng)的app進(jìn)程。
系統(tǒng)java層:
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate”);
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
注:前面的tag參數(shù)對應(yīng)的抓systrace的開關(guān)選項。
Native:
#define ATRACE_TAG ATRACE_TAG_ALWAYS
#include <utils/Trace.h> // for c++
#include <cutils/trace.h> // for c
ATRACE_CALL();
or
ATRACE_BEGIN("");
ATRACE_END();
2. TraceView or AS profile cpu
對于app的問題,可以借助traceView 或者 as profile cpu檢查 CPU Activity 和函數(shù)跟蹤來幫助定位耗時方法,不斷縮小范圍來定位問題。
3. 反編譯
對于三方app,沒有源碼,可以通過oatdump反編譯來分析:
adb shell oatdump --oat-file=/data/app/包名/oat/arm64/base.odex > demo.txt
字節(jié)碼命令說明
二、實(shí)戰(zhàn)舉例:高德地圖耗時分析
1.發(fā)現(xiàn)問題
在cpu頻率、調(diào)度 和 compiler filter都一一排除了的前提下,通過systrace來分析具體啟動各階段耗時情況。
Android N:

Android O:

發(fā)現(xiàn)MapContainer執(zhí)行時間比較長,MapContainer 是高德自己實(shí)現(xiàn)的類,應(yīng)該是高德自己的實(shí)現(xiàn)方式在不同 Android 版本上差別比較大。因?yàn)楦叩率侨綉?yīng)用,沒有代碼,所以借助traceView來分析。
注:如果是冷啟動可以使用命令行來抓?。?/p>
1)啟動指定 Activity 同時啟動 trace
am start -n com.stan.note.newdemo/.MainActivity --start-profiler /data/local/tmp/test.trace
am profile com.stan.note.newdemo stop
2)啟動指定 Activity 同時啟動 trace, 自動結(jié)束
am start -n com.stan.note.newdemo/.MainActivity -P /data/local/tmp/test.trace

通過 TraceView 發(fā)現(xiàn)有兩個相關(guān)的方法非常耗時:
com.autonavi.mao.core.OverlayManager.init ()V
com.autonavi.minimap.commute.CommuteOverlay.init ()V
下面通過oatdump來反編譯這兩個方法
截取一個小片段:
void com.autonavi.minimap.commute.CommuteOverlay.init() (dex_method_idx=20281)
DEX CODE:
0x0000: 1202 | const/4 v2, #+0
0x0001: e530 0800 | iget-object-quick v0, v3, // offset@8
0x0003: 7110 6006 0000 | invoke-static {v0}, android.view.LayoutInflater android.view.LayoutInflater.from(android.content.Context) // method@1632
0x0006: 0c00 | move-result-object v0
0x0007: 6001 a235 | sget v1, I com.autonavi.minimap.R$layout.commute_marker_layout // field@13730
0x0009: e930 1200 1002 | invoke-virtual-quick {v0, v1, v2}, // vtable@18
0x000c: 0c00 | move-result-object v0
這里就想找到art指令中對應(yīng)的函數(shù)并加上trace,來確定是哪個具體函數(shù)耗時。
比如sget指令,對應(yīng)到如下解釋器解釋指令
art/runtime/interpreter/mterp/arm64/op_sget.S
%default { "is_object":"0", "helper":"MterpGet32Static", "extend":"" }
2 /*
3 * General SGET handler wrapper.
4 *
5 * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
6 */
7 /* op vAA, field//BBBB */
8
9 .extern $helper
10 EXPORT_PC
11 FETCH w0, 1 // w0<- field ref BBBB
12 ldr x1, [xFP, #OFF_FP_METHOD]
13 mov x2, xSELF
14 bl $helper
15 ldr x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
16 lsr w2, wINST, #8 // w2<- AA
17 $extend
18 PREFETCH_INST 2
19 cbnz x3, MterpException // bail out
20.if $is_object
21 SET_VREG_OBJECT w0, w2 // fp[AA]<- w0
22.else
23 SET_VREG w0, w2 // fp[AA]<- w0
24.endif
25 ADVANCE 2
26 GET_INST_OPCODE ip // extract opcode from rINST
27 GOTO_OPCODE ip
這部分是匯編指令,具體指令執(zhí)行不耗時,肯定是函數(shù)耗時。函數(shù)調(diào)用$helper 對應(yīng)MterpGet32Static函數(shù)。
在對應(yīng)的函數(shù)處加trace
art/runtime/interpreter/mterp/mterp.cc
#include "base/systrace.h"
extern "C" int MterpSet32Static(uint32_t field_idx,
int32_t new_value,
ArtMethod* referrer,
Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_) {
return MterpSetStatic<int32_t, Primitive::kPrimInt>(field_idx,
new_value,
referrer,
self,
&ArtField::SetInt<false>);
}
在對應(yīng)MterpSetStatic方法加trace.
template <typename return_type, Primitive::Type primitive_type>
ALWAYS_INLINE return_type MterpGetStatic(uint32_t field_idx,
ArtMethod* referrer,
Thread* self,
return_type (ArtField::*func)(ObjPtr<mirror::Object>))
REQUIRES_SHARED(Locks::mutator_lock_) {
ScopedTrace trace(__FUNCTION__);
return_type res = 0; // On exception, the result will be ignored.
ArtField* f =
FindFieldFromCode<StaticPrimitiveRead, false>(field_idx,
referrer,
self,
primitive_type);
if (LIKELY(f != nullptr)) {
ObjPtr<mirror::Object> obj = f->GetDeclaringClass();
res = (f->*func)(obj);
}
return res;
}

2.分析問題
MterpGetStatic 就是去獲取一個類的靜態(tài)成員, 為什么會用掉 85 ms ?apk中dex文件會在art中轉(zhuǎn)化為一個DexFile對象,而每一個 DexFile 對象會對應(yīng)一個 DexCache 對象。DexCache 的作用是用來緩存包含在一個 dex 文件里面的類型 (Type), 方法 (Method), 域 (Field), 字符串 (String) 和靜態(tài)儲存區(qū) (Static Storage) 等信息。
art/runtime/mirror/dex_cache.cc
void DexCache::Init(const DexFile* dex_file,
ObjPtr<String> location,
StringDexCacheType* strings,
uint32_t num_strings,
TypeDexCacheType* resolved_types,
uint32_t num_resolved_types,
MethodDexCacheType* resolved_methods,
uint32_t num_resolved_methods,
FieldDexCacheType* resolved_fields,
uint32_t num_resolved_fields,
MethodTypeDexCacheType* resolved_method_types,
uint32_t num_resolved_method_types,
GcRoot<CallSite>* resolved_call_sites,
uint32_t num_resolved_call_sites)
REQUIRES_SHARED(Locks::mutator_lock_);
上面是 DexCache 的初始化函數(shù), num_resolved_fields 表示 DexCache 中緩存 Field 的個數(shù), 來打印一下這個參數(shù)
N:
03-21 15:57:56.409 5982 5982 E art : syh DexCache::Init classes.dex num_resolved_fields 56285
03-21 15:57:56.433 5982 5982 E art : syh DexCache::Init classes.dex num_resolved_fields 25449
03-21 15:57:56.437 5982 5982 E art : syh DexCache::Init classes.dex num_resolved_fields 63062
03-21 15:57:56.569 5982 5982 E art : syh DexCache::Init classes.dex num_resolved_fields 7802
03-21 15:57:56.917 5982 6097 E art : syh DexCache::Init classes.dex num_resolved_fields 115
03-21 15:57:58.088 5982 6032 E art : syh DexCache::Init classes.dex num_resolved_fields 16
03-21 15:57:58.993 5982 6121 E art : syh DexCache::Init classes.dex num_resolved_fields 27
O:
03-17 14:31:41.834 5358 5358 E zygote : syh DexCache::Init classes.dex num_resolved_fields 1024
03-17 14:31:41.854 5358 5358 E zygote : syh DexCache::Init classes.dex num_resolved_fields 1024
03-17 14:31:41.860 5358 5358 E zygote : syh DexCache::Init classes.dex num_resolved_fields 1024
03-17 14:31:42.051 5358 5358 E zygote : syh DexCache::Init classes.dex num_resolved_fields 1024
03-17 14:31:42.302 5358 5436 E zygote : syh DexCache::Init classes.dex num_resolved_fields 279
03-17 14:31:42.448 5358 5469 E zygote : syh DexCache::Init classes.dex num_resolved_fields 115
03-17 14:31:42.914 5358 5541 E zygote : syh DexCache::Init classes.dex num_resolved_fields 16
03-17 14:31:44.516 5358 5492 E zygote : syh DexCache::Init classes.dex num_resolved_fields 27
Android O 上, 一個 DexCache 最多緩存 1024 個 Field, 而實(shí)際上有上萬個 Filed, 導(dǎo)致 MterpGetStatic 的時候 cache 命中率極低, 最終導(dǎo)致 MterpGetStatic 耗時。
3.解決問題
嘗試調(diào)整cache size與N一致,MterpGetStatic 明顯改善, 單一個 inflate 就快了 80 ms, 優(yōu)化后高德地圖的啟動時間可以減少 166 ms。
