詳解Android性能優(yōu)化之啟動(dòng)優(yōu)化
1、為什么要進(jìn)行啟動(dòng)優(yōu)化
網(wǎng)上流行一種說法,就是8秒定律,意思是說,如果用戶在打開一個(gè)頁面,在8秒的時(shí)間內(nèi)還沒有打開,那么用戶大概的會(huì)放棄掉,意味著一個(gè)用戶的流失。從這里就可以看出,啟動(dòng)優(yōu)化的重要性了。
2、啟動(dòng)的分類
2.1 冷啟動(dòng)
先來看看冷啟動(dòng)的流程圖
從圖中可以看出,APP啟動(dòng)的過程是:ActivityManagerProxy 通過IPC來調(diào)用AMS(ActivityManagerService),AMS通過IPC啟動(dòng)一個(gè)APP進(jìn)程,ApplicationThread通過反射來創(chuàng)建Application并且綁定,最后通過ActivityThread來控制activity的生命周期,在相關(guān)頁面的生命周期中通過ViewRootImpl來對(duì)view的實(shí)現(xiàn)。從而完成應(yīng)用的啟動(dòng)。
2.2 熱啟動(dòng)
熱啟動(dòng)的速度是最快的,它就是進(jìn)程從后臺(tái)切換到前臺(tái)的一個(gè)過程。
2.3 溫啟動(dòng)
溫啟動(dòng)只會(huì)重新走一遍頁面的生命周期,但是對(duì)于進(jìn)程,application不會(huì)重新在創(chuàng)建。
3、優(yōu)化方向
上面介紹了啟動(dòng)的幾種方式可以看出,我們針對(duì)啟動(dòng)優(yōu)化,基本只是優(yōu)化冷啟動(dòng)就可以了。但是從冷啟動(dòng)的啟動(dòng)流程中很多都是系統(tǒng)做的,我們沒有辦法操控。我們能做的,就是application的生命周期和activity的生命周期這部分,啟動(dòng)優(yōu)化往往就是從這兩塊入手。
4、啟動(dòng)時(shí)間的測(cè)量方式
4.1 使用adb 命令方式(線下使用方便)
adb shell am start -W 包名/包名+類名
ThisTime:最后一個(gè)activity的啟動(dòng)耗時(shí)
TotalTime:所有activity的啟動(dòng)耗時(shí)
WaitTime:AMS啟動(dòng)activity的總耗時(shí)
這里由于我直接進(jìn)入到主界面,中間并沒有SplashActivity,所有ThisTime 和 TotalTime的時(shí)間是一樣的
優(yōu)勢(shì):在線下使用方便,適合于跑線下的產(chǎn)品,和獲取競(jìng)品的時(shí)間,然后比對(duì)
缺點(diǎn):不能帶到線上,獲取的時(shí)間,只能說是一個(gè)大概時(shí)間,不是很嚴(yán)謹(jǐn)。
4.2 手動(dòng)打點(diǎn)方式
通過System.currentTimeMillis()來打時(shí)間戳
缺點(diǎn):很明顯,對(duì)代碼侵入性非常的大,如果說我想要打出每一個(gè)任務(wù)花費(fèi)的時(shí)間,那么代碼看起來就很惡心了
5、優(yōu)雅獲取方法耗時(shí)
5.1 AOP Aspect Oriented Programming 面向切面編程
AOP:通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。它的核心思想就是將應(yīng)用程序中的業(yè)務(wù)邏輯處理部分同對(duì)其提供通用服務(wù)部分即“橫切關(guān)注點(diǎn)”進(jìn)行分離。
OOP:引入封裝,繼承,多態(tài)等概念來建立一種對(duì)象層次結(jié)構(gòu),它允許開發(fā)者定義縱向的關(guān)系,但并不適合橫向的關(guān)系。
可以說AOP是OOP的一種補(bǔ)充和完善。
5.2 aspectj的使用
AspectJ是一個(gè)面向切面編程的框架,是對(duì)java的擴(kuò)展且兼容java,AspectJ定義了AOP語法,它有一個(gè)專門的編譯器來生成遵守java字節(jié)編碼規(guī)范的Class文件。
在項(xiàng)目的根目錄的build.gradle添加依賴:
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
在app下的build.gradle添加依賴
apply plugin: 'android-aspectjx'
在dependencies中添加
implementation 'org.aspectj:aspectjrt:1.9.4'
然后創(chuàng)建一個(gè)類
package com.noahedu.myapplication.aspectj; import android.util.Log; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @Aspect public class MyApplicationAspectj { @Around("call(* com.noahedu.myapplication.MyApplication.**(..))") public void getTime(ProceedingJoinPoint joinPoint){ Signature signature = joinPoint.getSignature(); String name = signature.getName(); long time = System.currentTimeMillis(); try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } Log.e("MyApplicationAspectj " ,(name + " cost " + (System.currentTimeMillis() - time))); } }
這樣我們運(yùn)行的時(shí)候,就會(huì)直接在logcat中打印出application中的onCreate方法中所有調(diào)用方法的耗時(shí)情況了
2020-07-10 14:22:27.151 1619-1619/? E/MyApplicationAspectj: taskOne cost 150
2020-07-10 14:22:29.203 1619-1619/com.noahedu.myapplication E/MyApplicationAspectj: taskTwo cost 2052
2020-07-10 14:22:29.554 1619-1619/com.noahedu.myapplication E/MyApplicationAspectj: taskThrid cost 351
2020-07-10 14:22:30.556 1619-1619/com.noahedu.myapplication E/MyApplicationAspectj: taskFour cost 1001
這樣我們幾乎沒有碰Application中的任何代碼,也就夠得出各個(gè)方法的耗時(shí),幾乎對(duì)代碼無侵入。
6、啟動(dòng)優(yōu)化的工具選擇
6.1 traceview
TraceView是Android SDK中內(nèi)置的一個(gè)工具,他可以加載trace文件,以圖形化的形式展示相應(yīng)代碼的執(zhí)行時(shí)間,次數(shù)及調(diào)用棧,便于我們分析。
Debug.startMethodTracing("MyApplication"); //TODO Debug.stopMethodTracing();
運(yùn)行項(xiàng)目就可以我們的SD卡中找到對(duì)應(yīng)的trace文件了,如果是Android studio可以直接在右下角找到
DeviceFileExporer -->sdcard --> Android -- > data -->files --->自己項(xiàng)目的包名
然后雙擊即可查看文件了
優(yōu)點(diǎn):使用簡單,圖形形式展示所執(zhí)行的時(shí)間,調(diào)用棧等。
缺點(diǎn):會(huì)影響到我們優(yōu)化的方向,由于是圖形化展示,也是比較占用CPU資源的,所以得到的時(shí)間往往是比實(shí)際的要大。
7、啟動(dòng)器
上面介紹了多了幾個(gè)獲取任務(wù)執(zhí)行時(shí)間的方式和工具,那么當(dāng)我們知道某個(gè)方法耗時(shí)了,我們?cè)撛趺刺幚砟兀?/p>
package com.noahedu.myapplication; import android.app.Application; import android.os.Debug; import android.util.Log; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Debug.startMethodTracing("MyApplication"); taskOne(); taskTwo(); taskThrid(); taskFour(); Debug.stopMethodTracing(); } public void taskOne(){ try { Thread.sleep(150); } catch (InterruptedException e) { e.printStackTrace(); } } public void taskTwo(){ try { Thread.sleep(2050); } catch (InterruptedException e) { e.printStackTrace(); } } public void taskThrid(){ try { Thread.sleep(350); } catch (InterruptedException e) { e.printStackTrace(); } } public void taskFour(){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
現(xiàn)在application的onCreate方法中有幾個(gè)任務(wù),各個(gè)耗時(shí)是不一樣的。可能很多同學(xué)就會(huì)說了,異步處理啊,開線程,放到線程池中,或者創(chuàng)建一個(gè)IntentService來執(zhí)行。那么我們就要考慮幾個(gè)問題了
第一:異步處理,如果在某個(gè)頁面需要用到某個(gè)SDK,但是又沒有初始化完成呢?
第二:假如說taskTwo需要taskOne的某個(gè)返回值呢?怎么保證taskOne在taskTwo之前執(zhí)行完畢呢?
第三:開線程,開多少個(gè)線程呢?多了會(huì)造成資源浪費(fèi),少了資源又沒有合理的利用。
我個(gè)人覺得針對(duì)于啟動(dòng)優(yōu)化,在Application中的onCreate()進(jìn)行初始化任務(wù)操作,我們首先需要對(duì)這些任務(wù)進(jìn)行一個(gè)優(yōu)先級(jí)劃分,針對(duì)于那些優(yōu)先級(jí)高的任務(wù),我們可以優(yōu)先進(jìn)行處理,對(duì)于那些優(yōu)先級(jí)較低的,我們可以適當(dāng)?shù)难舆t進(jìn)行加載。
其次有很多同學(xué)喜歡把那些優(yōu)先級(jí)較低的任務(wù)進(jìn)行延遲加載,比如new Handler().postDelayed(),這種我覺得是非常不可取的,假如說放在postDelayed中的任務(wù)耗時(shí)2s,延遲1s進(jìn)行處理,那么在執(zhí)行2s任務(wù)的過程中,有用戶進(jìn)行操作,那豈不是很卡嗎,很明顯,這是指標(biāo)不治本的。
7.1 啟動(dòng)器的思想
針對(duì)上面說的幾個(gè)痛點(diǎn),怎么在處理上面的幾個(gè)痛點(diǎn),又能保證代碼的可維護(hù)性呢?換句話說就是一個(gè)新人不需要理解整個(gè)過程,直接就可以開干呢?那么,啟動(dòng)器來了。
啟動(dòng)器核心思想:充分利用CPU多核,自動(dòng)梳理任務(wù)順序
7.2 啟動(dòng)器的原理
1、任務(wù)全部封裝成Task對(duì)象,傳入到集合中。
2、根據(jù)所有的任務(wù)依賴關(guān)系,形成一個(gè)有向無環(huán)圖,然后通過拓?fù)渑判蚺帕谐鋈蝿?wù)的執(zhí)行流程
3、通過CountDownLatch來控制某一個(gè)任務(wù)是否執(zhí)行完畢才進(jìn)行下一步。
4、線程池創(chuàng)建核心線程的數(shù)量,由手機(jī)的核數(shù)量決定的。
7.3啟動(dòng)器使用方式
7.4啟動(dòng)器核心代碼
進(jìn)行任務(wù)排序
package com.noahedu.launchertool.launchstarter.sort; import com.noahedu.launchertool.launchstarter.task.Task; import com.noahedu.launchertool.launchstarter.utils.DispatcherLog; import java.util.ArrayList; import java.util.List; import java.util.Set; import androidx.annotation.NonNull; import androidx.collection.ArraySet; public class TaskSortUtil { private static List<Task> sNewTasksHigh = new ArrayList<>();// 高優(yōu)先級(jí)的Task /** * 任務(wù)的有向無環(huán)圖的拓?fù)渑判? * * @return */ public static synchronized List<Task> getSortResult(List<Task> originTasks, List<Class<? extends Task>> clsLaunchTasks) { long makeTime = System.currentTimeMillis(); Set<Integer> dependSet = new ArraySet<>(); Graph graph = new Graph(originTasks.size()); for (int i = 0; i < originTasks.size(); i++) { Task task = originTasks.get(i); if (task.isSend() || task.dependsOn() == null || task.dependsOn().size() == 0) { continue; } for (Class cls : task.dependsOn()) { int indexOfDepend = getIndexOfTask(originTasks, clsLaunchTasks, cls); if (indexOfDepend < 0) { throw new IllegalStateException(task.getClass().getSimpleName() + " depends on " + cls.getSimpleName() + " can not be found in task list "); } dependSet.add(indexOfDepend); graph.addEdge(indexOfDepend, i); } } List<Integer> indexList = graph.topologicalSort(); List<Task> newTasksAll = getResultTasks(originTasks, dependSet, indexList); DispatcherLog.i("task analyse cost makeTime " + (System.currentTimeMillis() - makeTime)); printAllTaskName(newTasksAll); return newTasksAll; } @NonNull private static List<Task> getResultTasks(List<Task> originTasks, Set<Integer> dependSet, List<Integer> indexList) { List<Task> newTasksAll = new ArrayList<>(originTasks.size()); List<Task> newTasksDepended = new ArrayList<>();// 被別人依賴的 List<Task> newTasksWithOutDepend = new ArrayList<>();// 沒有依賴的 List<Task> newTasksRunAsSoon = new ArrayList<>();// 需要提升自己優(yōu)先級(jí)的,先執(zhí)行(這個(gè)先是相對(duì)于沒有依賴的先) for (int index : indexList) { if (dependSet.contains(index)) { newTasksDepended.add(originTasks.get(index)); } else { Task task = originTasks.get(index); if (task.needRunAsSoon()) { newTasksRunAsSoon.add(task); } else { newTasksWithOutDepend.add(task); } } } // 順序:被別人依賴的————》需要提升自己優(yōu)先級(jí)的————》需要被等待的————》沒有依賴的 sNewTasksHigh.addAll(newTasksDepended); sNewTasksHigh.addAll(newTasksRunAsSoon); newTasksAll.addAll(sNewTasksHigh); newTasksAll.addAll(newTasksWithOutDepend); return newTasksAll; } private static void printAllTaskName(List<Task> newTasksAll) { if (true) { return; } for (Task task : newTasksAll) { DispatcherLog.i(task.getClass().getSimpleName()); } } public static List<Task> getTasksHigh() { return sNewTasksHigh; } /** * 獲取任務(wù)在任務(wù)列表中的index * * @param originTasks * @return */ private static int getIndexOfTask(List<Task> originTasks, List<Class<? extends Task>> clsLaunchTasks, Class cls) { int index = clsLaunchTasks.indexOf(cls); if (index >= 0) { return index; } // 僅僅是保護(hù)性代碼 final int size = originTasks.size(); for (int i = 0; i < size; i++) { if (cls.getSimpleName().equals(originTasks.get(i).getClass().getSimpleName())) { return i; } } return index; } }
執(zhí)行任務(wù)代碼
package com.noahedu.launchertool.launchstarter.task; import android.os.Looper; import android.os.Process; import com.noahedu.launchertool.launchstarter.TaskDispatcher; import com.noahedu.launchertool.launchstarter.stat.TaskStat; import com.noahedu.launchertool.launchstarter.utils.DispatcherLog; /** * 任務(wù)真正執(zhí)行的地方 */ 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() { DispatcherLog.i(mTask.getClass().getSimpleName() + " begin run" + " Situation " + TaskStat.getCurrentSituation()); Process.setThreadPriority(mTask.priority()); long startTime = System.currentTimeMillis(); mTask.setWaiting(true); mTask.waitToSatisfy(); long waitTime = System.currentTimeMillis() - startTime; startTime = System.currentTimeMillis(); // 執(zhí)行Task mTask.setRunning(true); mTask.run(); // 執(zhí)行Task的尾部任務(wù) 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); mTaskDispatcher.markTaskDone(mTask); } DispatcherLog.i(mTask.getClass().getSimpleName() + " finish"); } } /** * 打印出來Task執(zhí)行的日志 * * @param startTime * @param waitTime */ private void printTaskLog(long startTime, long waitTime) { long runTime = System.currentTimeMillis() - startTime; if (DispatcherLog.isDebug()) { DispatcherLog.i(mTask.getClass().getSimpleName() + " wait " + waitTime + " run " + runTime + " isMain " + (Looper.getMainLooper() == Looper.myLooper()) + " needWait " + (mTask.needWait() || (Looper.getMainLooper() == Looper.myLooper())) + " ThreadId " + Thread.currentThread().getId() + " ThreadName " + Thread.currentThread().getName() + " Situation " + TaskStat.getCurrentSituation() ); } } }
基本核心代碼就是上面這幾個(gè),完整的代碼會(huì)在后面的demo給出
8、其他優(yōu)化方案
8.1 對(duì)延遲任務(wù)進(jìn)行分批初始化
使用IdleHandler特性,進(jìn)行空閑執(zhí)行 (適合優(yōu)先級(jí)不是很高,不急于初始化的第三方SDK)
IdleHandler:IdleHandler 可以用來提升性能,主要用在我們希望能夠在當(dāng)前線程消息隊(duì)列空閑時(shí)做些事情(譬如 UI 線程在顯示完成后,如果線程空閑我們就可以提前準(zhǔn)備其他內(nèi)容)的情況下,不過最好不要做耗時(shí)操作。簡單來說就是,looper對(duì)象有空的時(shí)候就會(huì)執(zhí)行IdleHandler中的任務(wù)。
前面說過,在application的任務(wù)進(jìn)行優(yōu)先級(jí)劃分,那么如果優(yōu)先級(jí)低的任務(wù),我們是不是可以不再application的onCreate中進(jìn)行初始化呢?是否可以放到activity中進(jìn)行呢?如果可以在activity中,那么在activity那個(gè)階段適合呢?其實(shí)在onCreate(),onResume()都是可以的。當(dāng)然我們也可以使用view中的getViewTreeObserver().addOnPreDrawListener進(jìn)行監(jiān)聽,這個(gè)事件是視圖將要繪制的時(shí)候會(huì)回調(diào)該方法。我們可以在回調(diào)的方法中將要初始化的SDK放大IdleHandler中。
8.2 提前加載SP 可以放到multidex之前加載,利用此階段的CPU
SharedPreferences,以鍵值對(duì)的形式進(jìn)行數(shù)據(jù)的保存的,會(huì)一次性加載到內(nèi)存中,所以我們可以考慮那個(gè)階段的CPU相對(duì)來說比較空閑。如可以放到multidex之前加載,充分利用此階段的CPU進(jìn)行
demo地址:[https://github.com/343661629/startOptimization]
以上就是詳解Android性能優(yōu)化之啟動(dòng)優(yōu)化的詳細(xì)內(nèi)容,更多關(guān)于Android性能優(yōu)化之啟動(dòng)優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
android 調(diào)用JNI SO動(dòng)態(tài)庫的方法
android 調(diào)用JNI 分為靜態(tài)調(diào)用與動(dòng)態(tài)調(diào)用,接下來通過本文給大家介紹android 調(diào)用JNI SO動(dòng)態(tài)庫的方法,感興趣的朋友一起看看吧2021-11-11Android+SQLite數(shù)據(jù)庫實(shí)現(xiàn)的生詞記事本功能實(shí)例
這篇文章主要介紹了Android+SQLite數(shù)據(jù)庫實(shí)現(xiàn)的生詞記事本功能,結(jié)合具體實(shí)例形式分析了Android操作SQLite數(shù)據(jù)庫實(shí)現(xiàn)生詞記錄功能的操作步驟與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-09-09Android編程實(shí)現(xiàn)在Activity中操作刷新另外一個(gè)Activity數(shù)據(jù)列表的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)在Activity中操作刷新另外一個(gè)Activity數(shù)據(jù)列表的方法,結(jié)合具體實(shí)例形式分析了2種常用的Activity交互實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-06-06