天天看點

Android性能優化實踐——啟動優化

寫在前面

趕着學趕着寫,實不實用就完事了!

這裡的優化我們針對的是冷啟動時的優化,有關冷啟動等相關概念可以看這篇文章:APP啟動方式分析——冷啟動、熱啟動、溫啟動

而冷啟動的第二階段,建立app對象、啟動主程序、建立MainActivity、渲染視圖、執行onLayout、執行onDraw,這部分是我們能夠真正控制的時間,即Application和Activity生命周期中進行的操作。

目錄

  • 啟動時間測量
  • traceview
  • 開屏白屏——Theme切換
  • 異步優化

啟動時間測量

對啟動進行優化,首先我們要學會如何去測量啟動時間

1.adb指令

adb shell am start -W packagename/首屏Activity

adb指令有問題的可以看一下這篇文章:adb問題

Android性能優化實踐——啟動優化

運作後是這個樣子,我們會得到三個資料,ThisTime、TotalTime和WaitTime

ThisTime:最後一個Activity啟動耗時

TotalTime:所有Activity啟動耗時

WaitTime:AMS啟動Activity的總耗時

這種方法測量,線上下使用友善,但是不能帶到線上運作,且測量的時間不是精确時間

2.手動埋點

原理:啟動時埋點,啟動結束埋點,計算二者內插補點

誤區:onWindowFocusChanged隻是首幀時間

正确:真實資料展示,Feed第一條展示

Android性能優化實踐——啟動優化
Android性能優化實踐——啟動優化

這種方法測量的時間精确,且可帶到線上

traceview

traceview可以通過圖形的形式展示執行時間、調用棧等,資訊全面,包含所有線程

使用方式:

Debug.startMethodTracing(getFilesDir() + "/App.trace");

// code...

Debug.stopMethodTracing(getFilesDir() + "/App.trace");
           

這裡最好手動為其設定trace檔案輸出路徑,如果出現問題找不到trace檔案可以看一下這篇文章:解決使用Debug.startMethodTracing後找不到對應的.trace檔案

Android性能優化實踐——啟動優化

打開trace檔案:

Android性能優化實踐——啟動優化

這裡的調用關系是上面的函數調用了下面的函數,而對應的顔色:

橙色:系統

綠色:應用自身的函數調用

藍色:第三方

可以通過右鍵->jump to source看對應代碼

traceview測量的運作時間不是準确的運作時間,因為traceview運作時開銷嚴重,整體都會變慢:會抓取目前運作的所有線程的所有執行函數。如果過度依賴traceview的時間分析,可能會帶偏優化方向。

Theme切換

Theme切換是針對啟動時的白屏問題,因為此時是系統運作時間,不可控,是以這裡的優化不是真正的縮短運作時間,而是給人感覺啟動時間快。

具體操作方式:給首屏Activity添加一個style,背景自定義,這樣開屏就會顯示出想要的樣子;首屏Activity的onCreate中,在setContentView前将style改回。

Android性能優化實踐——啟動優化
Android性能優化實踐——啟動優化
Android性能優化實踐——啟動優化

異步優化

此處的異步優化針對的是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

持續更新~