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

1.爲何要用啓動器

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

public class MyApplication extends Application {

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

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

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

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

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


1.定義task接口

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

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

    void run();

    /** * Task執行所在的線程池,可指定,通常默認 */
    Executor runOnExecutor();

    /** * 存放須要先執行的task任務集合(也就是添加須要先執行的依賴) */
    List<Class<? extends ITask>> dependentArr();

    /** * 開始鎖 * */
    void startLock();

    /** * 解鎖 * */
    void unlock();

    /** * 異步線程執行的Task是否須要在被調用await的時候等待,默認不須要 * * @return */
    boolean needWait();

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

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

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


2.實現task接口

public abstract class Task implements ITask {

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

    /** * 當前Task等待,讓依賴的Task先執行 */
    @Override
    public void startLock() {
        try {
            taskCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /** * 依賴的Task執行完一個 */
    @Override
    public void unlock() {
        taskCountDownLatch.countDown();
    }

    /** * 是否須要儘快執行,解決特殊場景的問題:一個Task耗時很是多可是優先級卻通常,頗有可能開始的時間較晚, * 致使最後只是在等它,這種能夠早開始。 */
    public boolean needRunAsSoon() {
        return false;
    }

    /** * Task的優先級,運行在主線程則不要去改優先級 */
    @Override
    public int priority() {
        return Process.THREAD_PRIORITY_BACKGROUND;
    }

    /** * Task執行在哪一個線程池,默認在IO的線程池; */
    @Override
    public ExecutorService runOnExecutor() {
        return DispatcherExecutor.getIOExecutor();
    }

    /** * 異步線程執行的Task是否須要在被調用await的時候等待,默認不須要 */
    @Override
    public boolean needWait() {
        return false;
    }

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

    @Override
    public boolean runOnMainThread() {
        return false;
    }

    @Override
    public Runnable getTailRunnable() {
        return null;
    }
}

複製代碼

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

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


3.實現啓動器

外部調用post

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

構建啓動器性能

public class TaskManager {

    private static TaskManager sInstance;
    private Context mContext;

    // 維持task和它的依賴Task的依賴關係,這裏是仿照EventBus的存放事件的機制設計
    private HashMap<Class<? extends ITask>, ArrayList<ITask>> dependOfTaskArray = new HashMap<>();

    // 存放已經執行完畢的Task隊列
    private volatile List<Class<? extends ITask>> taskFinishedArray = new ArrayList<>();

    // 存放全部的task
    private List<Task> taskAll = new ArrayList<>();
    private List<Class<? extends Task>> taskAllClazz = new ArrayList<>();

    // 須要在主線程中執行的Task隊列
    private volatile List<Task> mainThreadTaskArray = new ArrayList<>();

    // 主線程須要等待先執行的task數量
    private AtomicInteger mainNeedWaitCount = new AtomicInteger();
    private CountDownLatch mCountDownLatch;

    private static final int WAITTIME_TIME = 996 * 31;
    private List<Future> futureArray = new ArrayList<>();

    private TaskManager(Context context) {
        if (context == null) {
            throw new IllegalArgumentException("Context is null.");
        }
        mContext = context;
    }

    /** * 使用單例模式建立對象 */
    public static TaskManager getInstance(Context context) {

        if (sInstance == null) {
            sInstance = new TaskManager(context);
        }
        return sInstance;
    }

    /** * 添加任務 */
    public TaskManager add(Task task) {

        if (task == null) {
            throw new IllegalArgumentException("task is null !");
        }
        
        // ->> 1
        setDependentOfTask(task);
        
        // ->> 2
        taskAll.add(task);
        taskAllClazz.add(task.getClass());

        // ->> 3
        // 非主線程且須要wait的
        if (ifNeedWait(task)) {
            // 主線程的鎖加一把
            mainNeedWaitCount.getAndIncrement();
        }
        return this;
    }

    /** * 獲取依賴的集合,主要作的爲兩件事 * * 1.是以依賴類爲Key,對應的依賴者的集合爲value添加進map裏面 * 2.在從完成的任務集合裏面查詢,該task所依賴的類是否已經完成,完成的話進行解鎖 * */
    private void setDependentOfTask(Task task) {
        if (task.dependentArr() != null && task.dependentArr().size() > 0) {
            for (Class<? extends ITask> dependTaskClazz : task.dependentArr()) {
                if (dependOfTaskArray.get(dependTaskClazz) == null) {
                    dependOfTaskArray.put(dependTaskClazz, new ArrayList<ITask>());
                }

                // 若是該task所依賴的依賴任務已經加載過了,就解鎖其中已經完成的
                dependOfTaskArray.get(dependTaskClazz).add(task);
                if (taskFinishedArray.contains(dependTaskClazz)) {
                    task.unlock();
                }
            }
        }
    }

    private boolean ifNeedWait(Task task) {
        return !task.runOnMainThread() && task.needWait();
    }

    @UiThread
    public void start() {
        if (Looper.getMainLooper() != Looper.myLooper()) {
            throw new RuntimeException("小子,啓動器必需要在主線程啓動");
        }
        if (taskAll.size() > 0) {

            // 4.->> 效率排序
            taskAll = TaskSortUtil.getSortResult(taskAll, taskAllClazz);
           
            // 5.->> 構建同步鎖
            mCountDownLatch = new CountDownLatch(mainNeedWaitCount.get());

            // 6.->> 分發任務
            dispatchTasks();
            runOnMainThread();
        }
    }

    /** * task分發,根據設定的不一樣規則,分發到不一樣的線程 */
    private void dispatchTasks() {
        for (final Task task : taskAll) {
            // 若是是須要在主線程中運行的,加入到主線程隊列中
            if (task.runOnMainThread()) {
                mainThreadTaskArray.add(task);
            } else {
                // 異步線程中執行,是否執行取決於具體線程池
                Future future = task.runOnExecutor().submit(new TaskRunnable(task, this));
                futureArray.add(future);
            }
        }
    }

    private void runOnMainThread() {
        for (Task task : mainThreadTaskArray) {
            new TaskRunnable(task,this).run();
        }
    }

    @UiThread
    public void startLock() {
        try {
            if (mainNeedWaitCount.get() > 0) {
                mCountDownLatch.await(WAITTIME_TIME, TimeUnit.MILLISECONDS);
            }
        } catch (InterruptedException e) {
        }
    }

    /** * 取消 * */
    public void cancel() {
        for (Future future : futureArray) {
            future.cancel(true);
        }
    }

    /** * 當完成一個任務以後,通知全部依賴它的任務,並解鎖他們 */
    public void unLockForChildren(Task task) {
        ArrayList<ITask> arrayList = dependOfTaskArray.get(task.getClass());
        if (arrayList != null && arrayList.size() > 0) {
            for (ITask subTask : arrayList) {
                subTask.unlock();
            }
        }
    }

   // 7 ->>
    public void finish(Task task) {
        if (ifNeedWait(task)) {
            taskFinishedArray.add(task.getClass());
            mCountDownLatch.countDown();
            mainNeedWaitCount.getAndDecrement();
        }
    }
}
複製代碼

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

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

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

4.TaskRunnable的實現

好了如何執行任務尼?

/** * 任務真正執行的地方 */
public class TaskRunnable implements Runnable {
    private Task task;
    private TaskManager taskManager;

    public TaskRunnable(Task task) {
        this.task = task;
    }

    public TaskRunnable(Task task, TaskManager taskManager) {
        this.task = task;
        this.taskManager = taskManager;
    }

    @Override
    public void run() {
        TraceCompat.beginSection(task.getClass().getSimpleName());
        Process.setThreadPriority(task.priority());

        task.startLock();
        task.run();

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

        if (!task.runOnMainThread()) {
            if(taskManager != null){
                taskManager.unLockForChildren(task);
                taskManager.finish(task);
            }
        }
        TraceCompat.endSection();
    }
}

複製代碼

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

再看下咱們本身的task

public class InitJPushTask extends Task {

    @Override
    public boolean needWait() {
        return true;
    }
    
    @Override
    public List<Class<? extends ITask>> dependentArr() {
        List<Class<? extends ITask>> tasks = new ArrayList<>();
        tasks.add(InitShareTask.class);
        return tasks;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1500);
            System.out.println("InitJPushTask運行完畢,它所在的線程是:"+Thread.currentThread().getName());
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}
複製代碼

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

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

5.總結


6.END

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

相關文章
相關標籤/搜索