一線大廠大型APP性能優化系列-自定義啓動器(三)

1.爲何要用啓動器

爲何要作啓動器?直接寫它不香嗎?來先回顧下噁心的代碼結構java

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        // 一堆耗時方法,嚴重影響啓動
        initBugly();
        initBaiduMap();
        initJPushInterface();
        initShareSDK();
    }
}
複製代碼

面對這些比較噁心的啓動方法,爲了加快啓動,咱們通常會採用線程池的方式啓動,一線大廠資深APP性能優化系列-異步優化與拓撲排序(二)算法

可是若是有的方法本身須要依賴的方法執行完畢才能執行,好比 initJPushInterface() 可能須要先執行完畢 GetDeviceID() 執行完畢才能進行再執行,那麼把它們都放入線程池裏面並行執行就會產生問題,另外有的方法好比initBugly(); 必須先執行完它以後,主線程才能執行完畢,再跳轉頁面。那麼由於這些問題,若是隻是用線程池來並行,就會致使代碼寫起來過於複雜。性能優化

這也就是爲何要推出啓動器的緣由,固然阿里作的仍是不錯的,可是狗東用阿里作的啓動器感受怪怪的,因此跟着做者一塊兒從零搭建一個啓動器吧。bash


1.定義task接口

首先,咱們要定義本身的一些task, 就是用來執行耗時方法的。先定義個接口吧。併發

/** * @author: lybj * @date: 2020/5/14 * @Description: */
public interface ITask {

    void run();

    /** * Task執行時所在的線程池,能夠指定,通常使用默認 */
    Executor runOn();

    /** * 存放須要先執行的task任務集合 */
    List<Class<? extends Task>> dependsOn();

    /** * 該task若是是異步任務,是否須要先等待本身完成後, * 主線程在繼續,也就是是否須要在主線程調用await的時候等待 */
    boolean needWait();

    /** * 是否在主線程執行 */
    boolean runOnMainThread();

    /** * 只能在主進程執行 */
    boolean onlyOnMainProcess();

    /** * Task主任務執行完成以後須要執行的任務 */
    Runnable getTailRunnable();

    /** * Task執行過程當中的回調 */
    void setTaskCallBack(TaskCallBack callBack);
}
複製代碼

好了,這些基本夠用了。異步


2.實現task接口

public abstract class Task implements ITask {

    private volatile boolean mIsWaiting; // 是否正在等待
    private volatile boolean mIsRunning; // 是否正在執行
    private volatile boolean mIsFinished; // Task是否執行完成
    private volatile boolean mIsSend; // Task是否已經被分發

    // 當前Task依賴的Task數量(須要等待被依賴的Task執行完畢才能執行本身),默認沒有依賴
    private CountDownLatch mDepends = new CountDownLatch(dependsOn() == null ? 0 : dependsOn().size());

    /** * 依賴的Task執行完一個 */
    public void satisfy() {
        mDepends.countDown();
    }
    
     /** * 當前Task等待,讓依賴的Task先執行 */
    public void waitToSatisfy() {
        try {
            mDepends.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    /** * 異步線程執行的Task是否須要在被調用await的時候等待(也就是是否須要主線程等你執行完再執行),默認不須要 * * @return */
    @Override
    public boolean needWait() {
        return false;
    }

    /** * 當前Task依賴的Task集合(須要等待被依賴的Task執行完畢才能執行本身),默認沒有依賴 * * @return */
    @Override
    public List<Class<? extends Task>> dependsOn() {
        return null;
    }
}
複製代碼

很簡單,主要作的是:
1.根據dependsOn() 定義一個柵欄ide

很好理解,傳入的task(咱們的耗時任務),由於須要依賴,好比TaskA,必須得等TaskB,TaskC加載完畢才能加載TaskA,那麼dependsOn()返回的就是TaskB,TaskC,也就是在TaskA中加了幾個同步鎖(鎖的數量就是TaskA所須要依賴的Task數量),每次執行satisfy()就減小一把鎖。oop


3.實現啓動器

外部調用post

TaskDispatcher instance = TaskDispatcher.createInstance();
        instance.addTask(new InitBuglyTask()) // 默認添加,併發處理
                .addTask(new InitBaiduMapTask())  // 在這裏須要先處理了另一個耗時任務initShareSDK,才能再處理它
                .addTask(new InitJPushTask())  // 等待主線程處理完畢,再進行執行
                .start();
        instance.await();
複製代碼

構建啓動器性能

public class TaskDispatcher {

    private static Context mContext;
    private static boolean sHasInit;
    private static boolean sIsMainProcess;
   
    // 存放依賴
    private HashMap<Class<? extends Task>, ArrayList<Task>> mDependedHashMap = new HashMap<>();
    
    // 存放全部的task
    private List<Task> mAllTasks = new ArrayList<>();
    private List<Class<? extends Task>> mClsAllTasks = new ArrayList<>();
    
    // 調用了await的時候還沒結束的且須要等待的Task隊列
    private List<Task> mNeedWaitTasks = new ArrayList<>();
    
    // 已經結束了的Task隊列
    private volatile List<Class<? extends Task>> mFinishedTasks = new ArrayList<>(100);

    // 須要在主線程中執行的Task隊列
    private volatile List<Task> mMainThreadTasks = new ArrayList<>();
    
    // 保存須要Wait的Task的數量
    private AtomicInteger mNeedWaitCount = new AtomicInteger();
    private CountDownLatch mCountDownLatch;
   
   
    /** * 注意:每次獲取的都是新對象 */
    public static TaskDispatcher getInstance(Context context) {

        if (context != null) {
            mContext = context;
            sHasInit = true;
            sIsMainProcess = Utils.isMainProcess(mContext);
        }
        return new TaskDispatcher();
    }

    /** * 添加任務 */
    public TaskDispatcher addTask(Task task) {

        if (task != null) {
        
            // ->> 1
            collectDepends(task);
            
            // ->> 2
            mAllTasks.add(task);
            mClsAllTasks.add(task.getClass());
            // ->> 3
            if (ifNeedWait(task)) {
                mNeedWaitTasks.add(task);
                mNeedWaitCount.getAndIncrement();
            }
        }
        return this;
    }

    /** * 存放相關依賴信息 * */
    private void collectDepends(Task task) {

        // 若是存在依賴
        if (task.dependsOn() != null && task.dependsOn().size() > 0) {

            // 獲取依賴
            for (Class<? extends Task> cls : task.dependsOn()) {
                if (mDependedHashMap.get(cls) == null) {
                    mDependedHashMap.put(cls, new ArrayList<Task>());
                }
                mDependedHashMap.get(cls).add(task);
                if (mFinishedTasks.contains(cls)) {
                    task.satisfy();
                }
            }
        }
    }
    
    /** * task 是否須要主線程等其完成再執行 * */
    private boolean ifNeedWait(Task task) {
        return !task.runOnMainThread() && task.needWait();
    }
    
    @UiThread
    public void start() {
    
        if (Looper.getMainLooper() != Looper.myLooper()) {
            throw new RuntimeException("小子,啓動器必需要在主線程啓動");
        }
        if (mAllTasks.size() > 0) {
        
            // 4.->> 查看被依賴的信息
            printDependedMsg();
            
            // 5.->> 拓撲排序並返回
            mAllTasks = TaskSortUtil.getSortResult(mAllTasks, mClsAllTasks);
            
            // 6.->> 構建同步鎖
            mCountDownLatch = new CountDownLatch(mNeedWaitCount.get());

            // 7.->> 分發task
            dispatchTasks();
            executeTaskMain();
        }
    }
    
    /** * 查看被依賴的信息 */
    private void printDependedMsg() {
        DispatcherLog.i("needWait size : " + (mNeedWaitCount.get()));
        if (false) {
            for (Class<? extends Task> cls : mDependedHashMap.keySet()) {
                DispatcherLog.i("cls " + cls.getSimpleName() + " " + mDependedHashMap.get(cls).size());
                for (Task task : mDependedHashMap.get(cls)) {
                    DispatcherLog.i("cls " + task.getClass().getSimpleName());
                }
            }
        }
    }
    
    /** * task分發,根據設定的不一樣規則,分發到不一樣的線程 */
    private void dispatchTasks() {
        for (Task task : mAllTasks) {
        
            if (task.runOnMainThread()) {
                mMainThreadTasks.add(task);

                if (task.needCall()) {
                    task.setTaskCallBack(new TaskCallBack() {
                        @Override
                        public void call() {

                            TaskStat.markTaskDone();
                            task.setFinished(true);
                            satisfyChildren(task);
                            markTaskDone(task);
                    }
                });
            }
        } else {
            // 異步線程中執行,是否執行取決於具體線程池
            Future future = task.runOn().submit(new DispatchRunnable(task,this));
            mFutures.add(future);
        }
    }
    
    /** * 從等待隊列中移除,添加進結束隊列 */
    public void markTaskDone(Task task) {
    
        // 8 ->>
        if (ifNeedWait(task)) {
            mFinishedTasks.add(task.getClass());
            mNeedWaitTasks.remove(task);
            mCountDownLatch.countDown();
            mNeedWaitCount.getAndDecrement();
        }
    }
    
    private void executeTaskMain() {
        mStartTime = System.currentTimeMillis();
        for (Task task : mMainThreadTasks) {
            long time = System.currentTimeMillis();
            new DispatchRunnable(task,this).run();
        }
    }
複製代碼

首先是經過getInstance()構造了一個實例對象,而後經過addTask() 添加咱們的Task, 若是它不爲空的話

根據上面的角標,逐一介紹

  1. 調用collectDepends(),遍歷該task所依賴的所有task,而且以它所依賴的task爲Key, 自己爲Value中集合元素的一員添加進去,而後判斷,該task中的依賴是否已經加載過了,若是加載過了,調用該task的satisfy()方法減該task的一把鎖。
  2. 而後將這個task和它的class文件添加到2個集合中,方便後面使用。
  3. 若是該Task須要主線程等其完成再執行的話,則添加到等待隊列中,等待隊列計數器+1
  4. 打印該task所依賴的信息
  5. 拓撲排序,經典的算法,用於描述依賴關係的排序,在上一章節有過介紹也給出過源碼,這裏就再也不贅述
  6. 這裏實際上就是構建一把鎖,這個鎖注意並不在Task裏面,Task裏面的鎖,注意是爲了先執行依賴的Task,執行完畢,再執行本身,而這裏的鎖是在啓動器上,其做用是讓主線程等待,優先執行那些必需要先執行完畢才能讓主線程繼續執行完畢,再跳轉頁面的task
  7. 根據須要分發不一樣的線程去執行,若是是須要在主線程中執行,那就先存儲起來,若是是須要在一部現場中執行,那就直接調用task.runOn()方法來異步執行耗時task,runOn()可複寫,不寫爲默認線程池
  8. 若是該線程須要在主線程中執行,將它從等待隊列中移除,添加進結束隊列,若是該task須要主線程等待的話,主線程的同步鎖-1,等待隊列數-1

4.DispatchRunnable的實現

好了如何執行任務尼?

public class DispatchRunnable implements Runnable {
    private Task mTask;
    private TaskDispatcher mTaskDispatcher;

    public DispatchRunnable(Task task) {
        this.mTask = task;
    }
    public DispatchRunnable(Task task,TaskDispatcher dispatcher) {
        this.mTask = task;
        this.mTaskDispatcher = dispatcher;
    }

    @Override
    public void run() {

        Process.setThreadPriority(mTask.priority());

        long startTime = System.currentTimeMillis();

        mTask.setWaiting(true);
        mTask.waitToSatisfy();

        long waitTime = System.currentTimeMillis() - startTime;
        startTime = System.currentTimeMillis();

        // 執行Task
        mTask.setRunning(true);
        mTask.run();

        // 執行Task的尾部任務
        Runnable tailRunnable = mTask.getTailRunnable();
        if (tailRunnable != null) {
            tailRunnable.run();
        }

        if (!mTask.needCall() || !mTask.runOnMainThread()) {
            printTaskLog(startTime, waitTime);

            TaskStat.markTaskDone();
            mTask.setFinished(true);
            if(mTaskDispatcher != null){
                mTaskDispatcher.satisfyChildren(mTask);
                
                // --> 8
                mTaskDispatcher.markTaskDone(mTask);
            }
        }
        TraceCompat.endSection();
    }
}
複製代碼

好了,是否是很簡單? 優先執行須要依賴的Task, 而後再執行本身,等都執行完畢後,調用mTaskDispatcher.markTaskDone(mTask); 將該task從等待隊列中移除,添加進結束隊列,若是該task須要主線程等待的話,主線程的同步鎖-1,等待隊列數-1

再看下咱們本身的task

public class InitJPushTask extends Task {

    @Override
    public boolean needWait() {
        return true;
    }

    @Override
    public List<Class<? extends Task>> dependsOn() {
        List<Class<? extends Task>> tasks = new ArrayList<>();
        tasks.add(GetDeviceIdTask.class);
        return tasks;
    }

    @Override
    public void run() {
        // 模擬InitJPush初始化
        try {
            Thread.sleep(1500);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}
複製代碼

咱們本身的這個Task就寫完看,這是一個須要先執行完畢GetDeviceIdTask, 而後須要執行完畢本身,才能容許Application去加載頁面的任務,看是否是很是簡單,看下Application的改造

TaskDispatcher instance = TaskDispatcher.createInstance();
        instance.addTask(new InitBuglyTask()) // 默認添加,併發處理
                .addTask(new InitBaiduMapTask())  // 在這裏須要先處理了另一個耗時任務initShareSDK,才能再處理它
                .addTask(new InitJPushTask())  // 等待主線程處理完畢,再進行執行
                .addTask(new GetDeviceIdTask())
                .start();
        instance.await();
複製代碼

5.總結


6.END

這個啓動器目前已經在某廠的一個比較成熟的項目中使用了,目測仍是蠻好用的,確實比以前啓動速度提高了不少,大約能提高到3秒,還有其他的大概18-19章節,包含不少核心的優化及大型APP的容災方案,前幾天有人聯繫我,想讓我把接下來的內容寫書,猶豫了好久,不知道大家是愛看書,仍是喜歡看博客,歡迎在底下留言

相關文章
相關標籤/搜索