首先,我們進行優(yōu)化的目標是:
1) 流暢: 冷/熱啟動快,打開頁面快,某一個業(yè)務邏輯快。
2) 穩(wěn)定:內存占用小,代碼結構合理。
3)省電:CPU資源占用小
4)安裝包?。?沒有無用資源。
基于以上目標,進行了一系列的優(yōu)化,總結如下。
冷啟動優(yōu)化
- 最簡單直接的辦法,設置主題圖片,然后在MainActivity的 onCreate前將主題圖片去除
<item name="android:windowBackground">
@drawable/tp_ic_start_activity_background</item>
目前我們就是采用上述方案,但是如果想要在APP發(fā)布后再去修改 那么上述方案就不行了,我們可以使用一個獨立的Activity來放置需要顯示的圖片。
以上俗稱 “Splash Screen”
這種方案其實并沒有做到實際的優(yōu)化,只是改善了用戶體驗。
- 將Application啟動的耗時任務放到工作線程中執(zhí)行,如何找出啟動過程中比較耗時的任務是關鍵。
SDK中提供了跟蹤方法執(zhí)行耗時的工具,比如:
在Application的onCreate中加入
Debug.startMethodTracing("Dialer_Coldstart"/*跟蹤文件名*/);
在MainActivity的onWindowFocusChanged中加入
Debug.stopMethodTracing();
冷啟動完成后,會在手機生成一個trace文件
/storage/emulated/0/Android/data/com.android.dialer/files/Dialer_Coldstart.trace
使用AndroidStudio打開該文件后

直接輸入包名,就可以快速定位耗時的函數(shù)在哪里,藍色的條越長,耗時越多。
在上面分析發(fā)現(xiàn)DialtactsActivity.onCreate占用時間比較長,因此我們也可以在onCreate里面加入
Debug.startMethodTracing("Dialer_Coldstart"/*跟蹤文件名*/);
...
Debug.stopMethodTracing();
重新生成trace文件進行分析,分析起來更方便。
一般情況下,當發(fā)現(xiàn)耗時任務時,有如下2中處理方法:
a. 該任務不需要在主線程中執(zhí)行的:可以將其放到異步線程中執(zhí)行,注意最好維護一個全局的線程池,避免野線程的存在;同時也要考慮線程并發(fā)帶來的數(shù)據(jù)安全問題。
b. 該任務必須在主線程中執(zhí)行的:
比如包含2個Fragment的ViewPager,每個Fragment的inflateView都必須在主線程中執(zhí)行,考慮到冷啟動只需要先初始化一個Fragment就可以了,因此,另一個Fragment可以延遲到界面穩(wěn)定顯示后(獲取用戶選擇該頁面后),再去加載。這樣就可以節(jié)省一個Fragment的加載時間。
具體可以采用ViewStub或者 一個空的FrameLayout實現(xiàn)。
熱啟動優(yōu)化
Android為了提升用戶體驗,在用戶點擊返回按鈕退出應用時,只是關閉了Activity,并不會殺掉應用,這樣在下次啟動該應用時,可以省去創(chuàng)建進程的時間。
參考對應的思想,我們可以重寫返回按鈕的邏輯,在點擊返回時,不銷毀Activity,僅僅是將Activity切換到后臺,這樣在下次打開應用時,連Activity都不用重建,從而達到秒開。
重寫 onBackPressed:
if (!moveTaskToBack(true)) {
super.onBackPressed();
}
注意,在onStop時釋放資源。
內存優(yōu)化
電話APP對內存占用不高,分析只是發(fā)現(xiàn)一處內存占用的問題----為了加快通話背景展示,緩存了桌面的背景或者聯(lián)系人頭像,目前修改了高斯模糊的流程,去除了圖片的緩存,減少了4M左右內存。
線程優(yōu)化
使用 Android Device Monitor 可以查看具體的線程


相比于Helloworld程序,我把可疑的線程圈了出來,經過簡單分析如下:
- NonUiExecutor 線程是我們自己實現(xiàn)的一個線程池中的線程,線程池核心線程數(shù) 為5
private static final ExecutorService sDefaultParallelExecutor =
Executors.newFixedThreadPool(
5,
new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = new Thread(r, "NonUiExecutor");
thread.setPriority(4);
return thread;
}
}
);
因此NonUiExecutor線程最多存在在5個,而且會一直保持著,之所以引入這個線程池,是為了縮減線程不停創(chuàng)建和銷毀所帶來的損耗。也是為了統(tǒng)一規(guī)范系統(tǒng)的異步任務,避免野線程的出現(xiàn)。
- 除了考慮上面穩(wěn)定時 線程的情況, 還需要優(yōu)化在進行某些業(yè)務邏輯時,隨意創(chuàng)建線程執(zhí)行異步任務所帶來的性能損耗。
比如將APP切換到后臺,再切換到前臺,線程列表就出現(xiàn)了異常:

從線程的命名可以看出,是使用了AsyncTask來執(zhí)行異步的任務,但是又沒有指定線程池,因此,需要跟蹤該業(yè)務邏輯。將AsyncTask提交到上面sDefaultParallelExecutor中執(zhí)行,這就要求我們將sDefaultParallelExecutor暴露出來給使用者。
資源優(yōu)化
打開Android Studio,點擊菜單Analyze -> Run Inspection By Name -> 輸入Unused resources 就可以查找出沒有被引用的資源。
注意,需要檢查代碼中是否有類似如下獲取資源的代碼,避免誤刪
Resources r = getResources();
int id = r.getIdentifier("test1", "drawable", getContext().getPackageName());
r.getDrawable(id, null);
目前電話電話APP還沒進行該項優(yōu)化。
業(yè)務邏輯優(yōu)化
關于業(yè)務邏輯主要分2部分
- 檢查耗時的任務,當發(fā)現(xiàn)一段業(yè)務很卡,可以使用冷啟動里面介紹的MethodTrace工具生成trace文件進行分析,
另外AndroidStudio也提供了圖形界面的支持:

點擊小紅點開始Record a method trace. 再點一下就停止。會在地下生成一個函數(shù)調用堆棧耗時表,大體上和分析冷啟動差不多。
2)對于分析結果的處理,除了減少耗時調用外,對于必須但耗時的操作,我們可以提前緩存,采用空間換時間的做法。
注意,考慮內存中數(shù)據(jù)的時效性。
比如上面的onCreateView函數(shù),分析發(fā)現(xiàn)其實就是inflateView耗時,我們就可以考慮是否可以提前地去inflateView,然后緩存在內存中,等到用戶點擊了撥號盤后,那么就不需要再infalteView,從而加快了撥號盤的彈出時間。但是,有一個問題,如果用戶一直不點擊撥號盤,那么這部分內存則是浪費的。