寫在前面
趕着學趕着寫,實不實用就完事了!
這裡的優化我們針對的是冷啟動時的優化,有關冷啟動等相關概念可以看這篇文章:APP啟動方式分析——冷啟動、熱啟動、溫啟動
而冷啟動的第二階段,建立app對象、啟動主程序、建立MainActivity、渲染視圖、執行onLayout、執行onDraw,這部分是我們能夠真正控制的時間,即Application和Activity生命周期中進行的操作。
目錄
- 啟動時間測量
- traceview
- 開屏白屏——Theme切換
- 異步優化
啟動時間測量
對啟動進行優化,首先我們要學會如何去測量啟動時間
1.adb指令
adb shell am start -W packagename/首屏Activity
adb指令有問題的可以看一下這篇文章:adb問題

運作後是這個樣子,我們會得到三個資料,ThisTime、TotalTime和WaitTime
ThisTime:最後一個Activity啟動耗時
TotalTime:所有Activity啟動耗時
WaitTime:AMS啟動Activity的總耗時
這種方法測量,線上下使用友善,但是不能帶到線上運作,且測量的時間不是精确時間
2.手動埋點
原理:啟動時埋點,啟動結束埋點,計算二者內插補點
誤區:onWindowFocusChanged隻是首幀時間
正确:真實資料展示,Feed第一條展示
這種方法測量的時間精确,且可帶到線上
traceview
traceview可以通過圖形的形式展示執行時間、調用棧等,資訊全面,包含所有線程
使用方式:
Debug.startMethodTracing(getFilesDir() + "/App.trace");
// code...
Debug.stopMethodTracing(getFilesDir() + "/App.trace");
這裡最好手動為其設定trace檔案輸出路徑,如果出現問題找不到trace檔案可以看一下這篇文章:解決使用Debug.startMethodTracing後找不到對應的.trace檔案
打開trace檔案:
這裡的調用關系是上面的函數調用了下面的函數,而對應的顔色:
橙色:系統
綠色:應用自身的函數調用
藍色:第三方
可以通過右鍵->jump to source看對應代碼
traceview測量的運作時間不是準确的運作時間,因為traceview運作時開銷嚴重,整體都會變慢:會抓取目前運作的所有線程的所有執行函數。如果過度依賴traceview的時間分析,可能會帶偏優化方向。
Theme切換
Theme切換是針對啟動時的白屏問題,因為此時是系統運作時間,不可控,是以這裡的優化不是真正的縮短運作時間,而是給人感覺啟動時間快。
具體操作方式:給首屏Activity添加一個style,背景自定義,這樣開屏就會顯示出想要的樣子;首屏Activity的onCreate中,在setContentView前将style改回。
異步優化
此處的異步優化針對的是Application在啟動時需要初始化的n多任務,此處占用了App啟動的很多時間。
核心思想:子線程分擔主線程任務,并行減少時間;将線性的多個任務,改為多線程并行,縮短時間
自定義啟動器
核心思想:充分利用CPU多核的能力;自動梳理任務順序
1)代碼Task化,啟動邏輯抽象為Task
2)根據所有任務依賴關系排序生成一個有向無環圖
3)多線程按照排序後的優先級依次執行
這裡要注意:
1)初始化是否符合異步要求
2)Task之間的先後依賴關系
3)區分CPU密集型和IO密集型
代碼實作
代碼連接配接:LaunchOptimizeManager.java
首先最開始列出所有的任務,為其定義唯一的名稱,
String[] allTasks
用來統計所有的Task(後面要将未執行的Task篩選掉,防止出現我注釋掉了一個Task,導緻需要他先執行的後面的Task無法執行的情況出現)。我們是通過拓撲排序來決定每個Task的執行順序的,
void topologicalSort(List<LaunchTask> tasks)
中的邏輯分為一下幾步:
1.使用多線程的線程池去執行Task:無前置任務的Task才能夠執行,即能夠執行的Task之間不會互相依賴,使用多線程執行可減少執行時間;線程池事先封裝好,友善管理
2.隊列+boolean[],輪詢所有Task
3.每個Task的實際執行就是一個Runnable,再次封裝增加完成的回調
4.AtomicInteger記錄完成數量,作為循環跳出條件
5.增加TIME_OUT防卡死
/**
* @author Johnny Deng
* @version 1.0
* @description 優化啟動
* @date 2020/6/25 17:50
*/
public class LaunchOptimizeManager {
private static final int TIME_OUT = 5000;
public static final String TASK_LEAKCANARY = "task_leakcanary";
public static final String TASK_BMOB = "task_bmob";
public static final String TASK_BUGLY = "task_bugly";
public static final String TASK_ANDFIX = "task_andfix";
private static String[] allTasks = new String[] {
TASK_LEAKCANARY, TASK_BMOB, TASK_BUGLY, TASK_ANDFIX};
/**
* 拓撲排序執行啟動項
*
* @param tasks tasks
*/
public static void topologicalSort(List<LaunchTask> tasks) {
if (tasks == null || tasks.size() == 0) {
return;
}
// 執行時間
long startTime = System.currentTimeMillis();
// 統計目前完成的數量
AtomicInteger count = new AtomicInteger();
ExecutorService executorService = ThreadUtils.getCachedThreadPool();
Set<String> set = new HashSet<>();
LinkedList<LaunchTask> queue = new LinkedList<>();
// 任務執行後,重新整理排序圖
Callback callback = name -> {
synchronized (tasks) {
count.getAndIncrement();
for (LaunchTask task : tasks) {
if (task.pres.size() > 0) {
task.pres.remove(name);
if (task.pres.size() == 0) {
queue.add(task);
tasks.remove(task);
}
}
}
}
};
for (LaunchTask task : tasks) {
set.add(task.name);
task.setCallback(callback);
}
// 排除未執行的Task
if (set.size() < allTasks.length) {
for (String sT : allTasks) {
if (!set.contains(sT)) {
for (LaunchTask task : tasks) {
task.pres.remove(sT);
}
}
}
}
// TODO 環
// 将可以執行的任務加入隊列中
for (int i = tasks.size() - 1; i >= 0; i --) {
LaunchTask task = tasks.get(i);
if (task.pres.isEmpty()) {
queue.add(task);
tasks.remove(task);
}
}
while (!queue.isEmpty() || !tasks.isEmpty() || count.get() < tasks.size()) {
long cur = System.currentTimeMillis();
if (cur - startTime > TIME_OUT) {
LogUtils.e("App Launch up Time out!");
break;
}
if (queue.isEmpty()) {
continue;
}
LaunchTask task = queue.poll();
executorService.submit(task);
}
}
public static class LaunchTask implements Runnable {
private Runnable runnable;
private List<String> pres;
private String name;
private Callback callback;
public LaunchTask(Runnable runnable, List<String> pres, String name) {
this.runnable = runnable;
this.pres = pres;
this.name = name;
}
public void setCallback(Callback callback) {
this.callback = callback;
}
@Override
public void run() {
runnable.run();
if (callback != null) {
callback.onComplete(name);
}
}
}
public interface Callback {
void onComplete(String name);
}
}
最後貼一下項目位址:jio-deng/FFmpegInAndroid
持續更新~