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