1. 程式人生 > 其它 >android arraylist排序_android啟動優化---BetterInitiator

android arraylist排序_android啟動優化---BetterInitiator

技術標籤:android arraylist排序

APP的啟動速度對使用者的體驗,和留存有很大的影響,所以android的啟動優化還是比較重要的,也是各廠android程式猿的績效指標。

1.對於啟動優化,常規操作有以下幾點:

1.視覺上的優化:就是將啟動APP後的白屏頁給替換掉,讓使用者在視覺上感到快。

2.使用非同步執行緒:將一些耗時的操作放到工作執行緒中去。

3.延遲載入:將一些不是立即需要的初始化可以延後執行。

2.存在的問題:

1.在進行初始化時,各種第三方庫的初始化之間有先後順序,比如必須先初始化完網路庫,才能初始化埋點上報。你可能會說,只要讓這兩個庫的初始化放在同一個執行緒不就好了嗎?確實可以這樣做,但是不夠優雅。

2.某些初始化必須在特定階段完成,比如網路庫的初始化,必須在Application的onCreate方法中執行完,到了Splash頁以後會使用網路庫進行網路請求。

為了解決以上問題,BetterInitiator就誕生了。BetterInitiator可以用來進行啟動優化,它將最小單元定義為一個Task,具體的拆分粒度要根據你具體的業務進行。比如我可以把網路的初始化放在一個單獨的Task,把Push的初始化也單獨放在一個Task。

3.使用BetterInitiator

github地址:https://github.com/121880399/betterInitiator

第一步:在你專案的根目錄下的build.gradle檔案中新增如下

allprojects { repositories { ... maven { url 'https://jitpack.io' } } }

第二步:新增依賴

dependencies { implementation 'com.github.121880399:betterInitiator:1.0.5' }

第三步:建立Task

繼承Task類,實現run方法,在run方法中執行你要初始化的內容。

/** * ETask依賴於CTask和DTask * 在主執行緒中執行 * ETask中需要傳入BTask中返回的引數 * @作者 Zhouzhengyi * @建立日期 2019/7/31 */public class ETask extends Task {​ String data;​ public ETask(){ EventBus.getDefault().register(this); }​ @Subscribe(sticky = true) public void onInitEvent(InitEvent event){ data = event.getResult(); }​ @Override public void run() { LogUtils.i("ETask is running"); if(!TextUtils.isEmpty(data)) { LogUtils.i("input data:" + data); } SystemClock.sleep(1000); EventBus.getDefault().unregister(this); }​ @Override public List> dependsOn() { List> list = new ArrayList<>(); list.add(CTask.class); list.add(DTask.class); return list; }​ @Override public boolean runOnMainThread() { return false; }​ @Override public int threadPoolType() { return ITask.IO; }​ @Override public boolean needWait() { return true; }​}​

這是demo中其中一個Task,這個Task的執行依賴於前面BTask的返回值,所以這裡用EventBus將引數傳入,當然還有其他的辦法。在dependsOn這個方法中,定義了依賴關係,ETask必須在CTask和DTask執行完畢以後才能執行。runOnMainThread指定當前任務是否在主執行緒中執行,threadPoolType指定當前是在IO型執行緒池中執行,這裡可以選擇是用IO執行緒池還是CPU執行緒池。needWait如果返回true,那麼在完成這個Task之前,是不會往下執行的,所以這裡有可能造成ANR噢,使用的時候需要注意。當然你可以通過setWaitOutTime方法來指定超時時長。

第四步:將Task新增進排程器

TaskDispatcher taskDispatcher = new TaskDispatcher();

taskDispatcher.init(this)

.addTask(new ATask())

.addTask(new BTask())

.addTask(new CTask())

.addTask(new DTask())

.addTask(new ETask())

.addTask(new FTask(FTaskCallback))

.start();

這樣就會根據每個Task的依賴關係進行執行啦。在這裡我給一張Demo中Task的關係圖給大家。

fea0c41ab294b28de3f54eacb3abff11.png

ATask是在主執行緒中執行,它不依賴於任何Task。BTask也是在主執行緒中執行,它必須在ATask之後執行。CTask和DTask是在工作執行緒中執行,他們必須在BTask執行完畢以後再執行。ETask是在工作執行緒中執行,它必須等CTask和DTask同時執行完畢後再執行,並且如果ETask沒有執行完畢不會進入到MainActivity介面。FTask在IO執行緒中執行,它不依賴於任何Task。

在這裡的依賴關係只是為了模擬複雜的情況,在正常情況下不會出現子執行緒中的任務要等待主執行緒的情況,如果是這種情況,也沒必要使用子執行緒了。

4.原理

介紹完使用以後,我們來說說原理。首先所有的Task加入到排程器以後,會區分哪些是有依賴的Task,依賴關係是怎麼樣的,然後會通過圖的拓撲排序來重新定義存在依賴的Task的先後執行順序,接著會區分哪些Task是在主執行緒中執行,哪些Task是在工作執行緒中執行,並交給相應的執行緒進行執行。

這裡講一下拓撲排序的原理,首先拓撲排序會將所有的節點放進一張鄰接表裡面,這個鄰接表有點類似於我們的Hashmap,只是在每個頂點表節點中加入了一個欄位用於記錄當前的入度。

 public Vector topologicalSort(){ //用於記錄每個節點的入度 int indegree[] = new int[mVerticeCount]; //初始化所有節點的入度 for (int i = 0 ; i < mVerticeCount ; i++){ ArrayList temp = (ArrayList) mAdj[i]; for(int node : temp){ indegree[node]++; } }​ //找出所有入度為0的節點,放入佇列中 Queue queue = new LinkedList<>(); for(int i = 0 ; i < mVerticeCount ;i++){ if(indegree[i] == 0){ queue.add(i); } } //記錄節點數 int count = 0; //從佇列中取節點,並且遍歷該節點之後的節點加入到Vector中 //Vector是執行緒安全的,並且容量是動態擴充套件的 Vector order = new Vector<>(); while(!queue.isEmpty()){ //從佇列中獲取入度為0的節點 int u = queue.poll(); order.add(u); //遍歷該節點的相鄰節點 for(int node : mAdj[u]){ //節點的入度減一,如果發現入度為0,加入到佇列中 if(--indegree[node] == 0){ queue.add(node); } } count++; } //如果取出的節點數量不等於所有節點數量,說明存在環 //原因是,有環的情況下,環內各節點的入度不能消減為0,所以count值只能環外的節點個數 if(count != mVerticeCount){ throw new IllegalStateException("Exists a cycle in the graph"); } return order; }

上面的註釋寫的很詳細了,首先會遍歷得到所有節點入度。然後找出入度為0的節點,放入一個佇列中。每次執行完入度為0的節點後,該節點的相鄰節點的入度減1,如果減1後入度為0,則放入到佇列中,然後只要佇列不為空,就從佇列中不斷的獲取入度為0的節點進行執行。如果獲取節點的次數,跟節點的總次數不相等,那麼說明出現了環。

至於如何實現Task之間的等待,我們用到了CountDownLatch這個類,這個類具體的用法大家可以自行百度一下,這裡就不做詳細的介紹了。