爲何要作啓動器?直接寫它不香嗎?來先回顧下噁心的代碼結構java
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // 一堆耗時方法,嚴重影響啓動 initBugly(); initBaiduMap(); initJPushInterface(); initShareSDK(); } } 複製代碼
面對這些比較噁心的啓動方法,爲了加快啓動,咱們通常會採用線程池的方式啓動,一線大廠資深APP性能優化系列-異步優化與拓撲排序(二),算法
可是若是有的方法本身須要依賴的方法執行完畢才能執行,好比 initJPushInterface() 可能須要先執行完畢 GetDeviceID() 執行完畢才能進行再執行,那麼把它們都放入線程池裏面並行執行就會產生問題,另外有的方法好比initBugly(); 必須先執行完它以後,主線程才能執行完畢,再跳轉頁面。那麼由於這些問題,若是隻是用線程池來並行,就會致使代碼寫起來過於複雜。性能優化
這也就是爲何要推出啓動器的緣由,固然阿里作的仍是不錯的,可是狗東用阿里作的啓動器感受怪怪的,因此跟着做者一塊兒從零搭建一個啓動器吧。markdown
首先,咱們要定義本身的一些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(); } 複製代碼
好了,這些基本夠用了。異步
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
外部調用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, 若是它不爲空的話
根據上面的角標,逐一介紹
好了如何執行任務尼?
/** * 任務真正執行的地方 */ 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(); 複製代碼
這個啓動器目前已經在某廠的一個比較成熟的項目中使用了,目測仍是蠻好用的,確實比以前啓動速度提高了不少,大約能提高到3秒,還有其他的大概18-19章節,包含不少核心的優化及大型APP的容災方案,前幾天有人聯繫我,想讓我把接下來的內容寫書,猶豫了好久,不知道大家是愛看書,仍是喜歡看博客,歡迎在底下留言