前言
講解的內容大體包含,異步優化,啓動優化,卡頓優化,內存優化,ARTHook, 監控耗時盲區,網絡,電量,瘦身及APP容災方案等
性能優化的系統學習方法
330頁 PDF Android進階核心筆記(對應思維導圖)
性能優化對應學習思維導圖(對應核心筆記)
性能優化學習資料視頻(系統,對應導圖和筆記)
學習資料
學習視頻
既然我們選擇了學習Android,那麼我們能做的就是不斷提升自身的能力,我學每一塊都會自己做一張導圖,然後對於去找學習資料,視頻。這樣我學習下來,不僅有程序,有系統,學習效率還高。“精”一個點,就努力做到最好,這就是我對於Android的學習態度。如果也是走上了Android這條路,如果現在的你對性能優化這塊感興趣,但是沒有一個明確的學習思路,我給你分享我實戰經驗總結,幫助你少走一點彎路。
有需要的朋友 私信【進階】我免費分享給你,還有其他Android方面想學習的知識,可以留言。最多的我會發文章,並且分享對應的資料給大家。
或者直接點擊下面鏈接即可領取
Android學習PDF+架構視頻+面試文檔+源碼筆記
如果你有其他需要的話,也可以在 GitHub 上查看,下面的資料也會陸續上傳到Github。
1.簡介
本篇文章是該系列文章中的第一篇,主要介紹的是在一些一線大廠的實際項目中,如果APP發生卡頓是如何進行定位問題的。主要介紹 程序的耗費時間
2.測量時間方式
首先,如果要查看頁面加載花費的時間有3種方式
adb命令查看
手動打點的方式
traceView
3.adb命令
只需要一行命令,就可以查看加載頁面的時間。
adb shell am start -W 包名/包名.Activity
複製代碼
使用後會顯示
ThisTime: 代表啓動最後一個Activity的耗時
TotalTime: 代表啓動所有的Activity的耗時
WaitTime: 代表AMS啓動所有的Activity的耗時
注意點:該命令只能是獲取配置了的Activity, 其他的無效,因爲Android組件中有個 exported 屬性,沒有intent-filter時exported 屬性默認爲false,此組件只能由本應用用戶訪問,配備了intent-filter後此值改變爲true,允許外部調用。
缺點: adb命令只能查看配置了的Activity,其他的無法查看,並且也無法精準的查看其方法具體耗費的時間,所以因爲其侷限性 並不能很好的解決我們問題,我們採用手動打點的方式查看。
4.手動打點
先看代碼
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initBugly();
initBaiduMap();
initJPushInterface();
initShareSDK();
...
}
private void initBugly() throws InterruptedException {
Thread.sleep(1000); // 模擬耗費的時間
}
private void initBaiduMap() throws InterruptedException {
Thread.sleep(2000); // 模擬耗費的時間
}
private void initJPushInterface() throws InterruptedException {
Thread.sleep(3000); // 模擬耗費的時間
}
private void initShareSDK() throws InterruptedException {
Thread.sleep(500); // 模擬耗費的時間
}
}
代碼不用我說,項目中很常見,現在的問題是APP啓動加載很慢, 那麼如何精準的查詢到具體耗時的方法?
long startTime = System.currentTimeMillis();
initBugly();
Log.d("lybj", "initBugly()方法耗時:"+ (System.currentTimeMillis() - startTime));
long startTime = System.currentTimeMillis();
initBaiduMap();
Log.d("lybj", "initBaiduMap()方法耗時:"+ (System.currentTimeMillis() - startTime));
...
這樣可以嗎?當然不行,耦合性太大,每一個方法都加,那麼測試完了,刪除代碼也是個體力活,一不小心刪錯一個,就會造成災難性的問題,在實際項目中,比如中石油的一些項目,就會採用 AOP 的方式來測量方法的耗費時長。
4.1 AOP
AOP : Aspect Oriented Programming的縮寫,意爲:面向切面編程
優點:
針對同一問題的統一處理
無侵入添加代碼
這裏我們使用的是Aspectj
4.2 Aspectj 的使用
1.添加依賴
根目錄的build.gradle裏
buildscript {
...
dependencies {
...
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
}
}
app項目的build.gradle及新建的module的build.gradle裏添加
apply plugin: 'android-aspectjx'
dependencies {
...
implementation 'org.aspectj:aspectjrt:1.8.+'br/>}
2.創建切面
@Aspect
public class PerformanceAop {
@Around("call(* com.bj.performance.MyApplication.**(..))")
public void getTime(ProceedingJoinPoint joinPoint){
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.d("lybj", methodName + "方法耗時:"+ (System.currentTimeMillis() - startTime));
}
}
看,根本無需修改任何工程代碼,就可以獲取運行時長了,點擊運行顯示
AspectJ語法參考
缺點: 如果項目比較龐大,上百個方法,總不能全部打掉,然後一個一個的分析到底是哪個地方運行時間過長吧,所以我們需要一個比較直觀的工具,一眼就能看到具體哪個方法運行時間過長。
- traceView的使用
5.1 特點
圖形的形式展示其執行時間調用棧
信息全面,包含所有進程
5.2 使用方式
Debug.startMethodTracing("文件名");
Debug.stopMethodTracing();
在代碼中相應位置的地方打入埋點即可, startMethodTracing 有3個構造參數分別是
tracePath:文件名/路徑
bufferSize:文件的容量大小
flag:TRACE_COUNT_ALLOCS 只有默認的這一種
代碼運行完成後,會在
mnt/sdcard/Android/data/包名/files
生成一個.trace後綴的文件,可以用Profiler添加打開它。
當然也可以使用Profiler的錄製功能,但是因爲要測量啓動時間,點擊錄製手速並不會那麼的精準,所以採用埋點的方式獲取trace文件進行分析。
5.3 性能分析
打開文件後爲上圖所示
5.4 時間模式
上圖標籤 1 所示:wallclock time 和 cpu time
Wall Clock Time:從進程開始運行到結束,時鐘走過的時間,這其中包含了進程在阻塞和等待狀態的時間。
Thread Time :就是CPU執行用戶指令所用的時間。
注意:如果線程A執行函數b,但是因爲函數b加了鎖,線程A進入等待狀態,那麼wallclock time也是要計算時間的,而Thread time則只是計算CPU花費在它身上的時間。
一般根據經驗來講wall duration時間久說明耗時多,然後看Thread time 這個值說明cpu花費在其身上的時間多不多。不多的話基本可以直接異步(因爲不搶佔CPU)多的話需要合理調度好執行順序。
5.5 Call Chart
上圖標籤 2 所示:Call Chart
它的作用就是可以很直觀的查看到底是哪裏耗時比較久,x軸爲調用的時間線,越寬的表示耗時越久,y軸爲調用的深度,也就是調用的子方法。父類在最上面,很明顯initBottomTab()方法調用是最耗時的。
鼠標懸浮可以查看耗費時間,雙擊可以跳轉到相應代碼
橙色:系統方法
藍色:第三方API(包括java語言的api)
綠色:App自身方法
簡易圖如下:
5.6 Flame Chart
上圖標籤 3 所示:Flame Chart 又稱之爲火焰圖
y 軸表示調用棧,每一層都是一個函數。調用棧越深,火焰就越高,頂部就是正在執行的函數,下方都是它的父函數。 x 軸表示抽樣數,如果一個函數在 x 軸佔據的寬度越寬,就表示它被抽到的次數多,即執行的時間長。注意,x 軸不代表時間,而是所有的調用棧合併後,按字母順序排列的。
火焰圖就是看頂層的哪個函數佔據的寬度最大。只要有"平頂"(plateaus),就表示該函數可能存在性能問題。
練習1:
假如火焰圖如上圖所示,我們需要分析哪個函數嗎?
答:最頂層的函數g()佔用 CPU 時間最多。d()的寬度最大,但是它直接耗用 CPU 的部分很少。b()和c()沒有直接消耗 CPU。因此,如果要調查性能問題,首先應該調查g(),其次是i()。 另外,從圖中可知a()有兩個分支b()和h(),這表明a()裏面可能有一個條件語句,而b()分支消耗的 CPU 大大高於h()。
與Call Chart區別
方法D對B(B1、B2和B3)進行多次調用,其中一些調用B對C(C1和C3)進行調用。如果用Call Chart表示,則爲
因爲B1、B2和B3共享相同的序列調用者(A→D→B)聚合,如下所示。同樣,C1和C3聚合,因爲它們共享相同的序列調用者(A→D→B→C)注意不包括C2, 因爲它有不同的調用者序列(A→D→C)。
那麼如果使用火焰圖,則表示:
也就是說,收集相同的調用序列的相同方法被收集並表示爲火焰圖中的一個較長的欄(而不是將它們顯示爲多個更短的條)
5.7 Top Down
上圖標籤 4 所示: Top Down 顯示一個函數調用列表,在該列表中展開函數節點會顯示函數的被調用方
看上圖右邊區域:
Self:方法調用用於執行自己的代碼而不是它的子類的時間量。
Children:方法調用花費的時間用於執行其被調用者,而不是其自己的代碼
Total:方法的Self和Children的時間的總和。這表示應用程序執行方法調用的總時間量
5.8 Bottom Up
上圖標籤 5 所示: Bottom Up 顯示一個函數調用列表,在該列表中展開函數節點將顯示函數的調用方。
Bottom Up選項卡對於那些消耗最多(或最少)CPU時間的方法的排序方法很有用。可以檢查每個節點,以確定哪些調用者在調用這些方法上花費最多的CPU時間。
Self:方法調用用於執行自己的代碼而不是它的子類的時間量。
Children:方法調用花費的時間用於執行其被調用者,而不是其自己的代碼
Total:方法的Self和Children的時間的總和。這表示應用程序執行方法調用的總和
簡略圖如下
缺點: 在項目中用到的,最多的還是Call Chart 和 Top Down, 但是traceView的原理就是抓取所有線程的所有函數裏的信息,所以會導致程序變慢, 所以常用的是SysTrace
SysTrace 作者用的不多,有興趣的朋友可以自行百度,用法和traceView的使用差不多,就不在分析了