【Android 性能优化】应用启动优化 ( 启动白屏问题 | 应用启动时间测量 | 冷启动 | 热启动 | 应用启动时间计算源码分析 )
文章目錄
- 一、 APP 啟動(dòng)白屏 / 黑屏
- 二、 APP 啟動(dòng)速度測(cè)量
- 1. 通過(guò) Logcat 日志查看應(yīng)用啟動(dòng)時(shí)間
- 2. 通過(guò) adb 命令查看界面啟動(dòng)時(shí)間
- 三、 APP 冷啟動(dòng)與熱啟動(dòng)
- 四、 APP 啟動(dòng)時(shí)間計(jì)算
一、 APP 啟動(dòng)白屏 / 黑屏
1 . 問(wèn)題描述 : Android 應(yīng)用啟動(dòng)時(shí) , 尤其是大型復(fù)雜的應(yīng)用 , 經(jīng)常出現(xiàn)幾秒鐘的白屏或黑屏 , 黑屏或白屏取決于主界面 Activity 的主題風(fēng)格 ;
2 . 解決方案 : Android 應(yīng)用啟動(dòng)時(shí)很多大型應(yīng)用都會(huì)有一個(gè)幾秒的倒計(jì)時(shí) , 并配上動(dòng)態(tài)廣告 , 這并不是開發(fā)者想要放上去的 , 而是為了避免上述啟動(dòng)白屏導(dǎo)致用戶體很差 ;
3 . 啟動(dòng)優(yōu)化 : 這里的應(yīng)用啟動(dòng)白屏 / 黑屏的時(shí)間 , 是可以通過(guò)優(yōu)化解決的 , 如將 333 秒鐘的啟動(dòng)白屏卡頓 , 優(yōu)化為毫秒級(jí) ;
二、 APP 啟動(dòng)速度測(cè)量
1. 通過(guò) Logcat 日志查看應(yīng)用啟動(dòng)時(shí)間
通過(guò) Logcat 日志查看應(yīng)用啟動(dòng)時(shí)間 : 該方法只適用于 Android 4.4 版本之后的手機(jī) ;
① 使用工具 : 使用 Android Studio 中的 Logcat 日志查看工具 ;
② 過(guò)濾選項(xiàng) : 設(shè)置過(guò)濾選項(xiàng)為 No Filters , 這是系統(tǒng) ActivityTaskManager 打印的 , 不屬于應(yīng)用日志信息 ;
③ 關(guān)鍵字 : 使用 Display 關(guān)鍵字過(guò)濾 ;
④ 截取到的日志信息如下 :
2020-06-20 08:44:09.821 1266-1305/? I/ActivityTaskManager: Displayed kim.hsl.recyclerview/.MainActivity: +334ms 2020-06-20 08:44:18.457 1266-1305/? I/ActivityTaskManager: Displayed com.google.android.permissioncontroller/com.android.packageinstaller.permission.ui.GrantPermissionsActivity: +315ms 2020-06-20 08:44:22.308 1266-1305/? I/ActivityTaskManager: Displayed kim.hsl.cckfa/.MainActivity: +311ms⑤ 具體操作選項(xiàng)參考下面的截圖 :
2. 通過(guò) adb 命令查看界面啟動(dòng)時(shí)間
通過(guò) adb 命令查看界面啟動(dòng)時(shí)間 :
① 命令格式 : adb shell am start -W 包名/完整 Activity 類名 ;
② 本次命令 : adb shell am start -W kim.hsl.rtmp/kim.hsl.rtmp.MainActivity , 其中 kim.hsl.rtmp 是包名 , kim.hsl.rtmp.MainActivity 是完整的類名 ;
Microsoft Windows [版本 10.0.18362.900] (c) 2019 Microsoft Corporation。保留所有權(quán)利。C:\Users\octop>adb shell am start -W kim.hsl.rtmp/kim.hsl.rtmp.MainActivity Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=kim.hsl.rtmp/.MainActivity } Status: ok LaunchState: COLD Activity: kim.hsl.rtmp/.MainActivity TotalTime: 374 WaitTime: 376 CompleteC:\Users\octop>③ 命名截圖 : 下面的截圖作為參考 ;
④ Activity 界面源碼 : 上述啟動(dòng)類類對(duì)應(yīng) RTMP_Pusher 應(yīng)用中的主界面 ;
package kim.hsl.rtmp; public class MainActivity extends AppCompatActivity {//... }三、 APP 冷啟動(dòng)與熱啟動(dòng)
熱啟動(dòng)的速度要遠(yuǎn)高于冷啟動(dòng) ;
1 . 冷啟動(dòng) : 應(yīng)用后臺(tái)沒有該應(yīng)用 , 該應(yīng)用所有資源都要重新加載 , 分配新的進(jìn)程 , 初始化 Application , 初始化 Activity 界面 ; 下圖中的 LanuchState 中顯示了啟動(dòng)的模式 , 下圖中顯示的是冷啟動(dòng) ;
2 . 熱啟動(dòng) : 按下 Home 鍵 , 應(yīng)用進(jìn)入后臺(tái) , 再次啟動(dòng) , 應(yīng)用由后臺(tái)轉(zhuǎn)到前臺(tái) , 這種啟動(dòng)稱為熱啟動(dòng) ; 下圖中的 LanuchState 中顯示了啟動(dòng)的模式 , 下圖中顯示的是熱啟動(dòng) ;
四、 APP 啟動(dòng)時(shí)間計(jì)算
1 . 啟動(dòng) Activity 命令 : adb shell am start -W 包名/完整 Activity 類名 ;
2 . 日志打印內(nèi)容 :
C:\Users\octop>adb shell am start -W kim.hsl.rtmp/kim.hsl.rtmp.MainActivity Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=kim.hsl.rtmp/.MainActivity } Status: ok LaunchState: COLD Activity: kim.hsl.rtmp/.MainActivity TotalTime: 431 WaitTime: 433 CompleteC:\Users\octop>3 . am 命令及源碼 : 在上述命令中可以啟動(dòng) Android 應(yīng)用 Activity 界面 , 其中 am 命令是由 Am.java 程序編譯出來(lái)的 , 其路徑是 \frameworks\base\cmds\am\src\com\android\commands\am\Am.java ;
4 . 分析 Am.java 可執(zhí)行文件 :
① 執(zhí)行啟動(dòng) Activity 相關(guān)邏輯 : 啟動(dòng)前后都記錄了時(shí)間 , 整個(gè)啟動(dòng)過(guò)程就是通過(guò)這兩個(gè)時(shí)間計(jì)算出來(lái)的 ;
IActivityManager.WaitResult result = null; int res;// 記錄開始時(shí)間 final long startTime = SystemClock.uptimeMillis(); if (mWaitOption) {// 啟動(dòng) Activity result = mAm.startActivityAndWait(null, null, intent, mimeType,null, null, 0, mStartFlags, profilerInfo, null, mUserId);res = result.result; } else {res = mAm.startActivityAsUser(null, null, intent, mimeType,null, null, 0, mStartFlags, profilerInfo, null, mUserId); } // 記錄結(jié)束時(shí)間 final long endTime = SystemClock.uptimeMillis();② 打印啟動(dòng)時(shí)間相關(guān)源碼 : IActivityManager.WaitResult 對(duì)象中封裝了 thisTime , totalTime 等時(shí)間信息 ; 這些時(shí)間都是在啟動(dòng)過(guò)程中計(jì)算的 ;
if (mWaitOption && launched) {if (result == null) {result = new IActivityManager.WaitResult();result.who = intent.getComponent();}System.out.println("Status: " + (result.timeout ? "timeout" : "ok"));if (result.who != null) {System.out.println("Activity: " + result.who.flattenToShortString());}if (result.thisTime >= 0) {System.out.println("ThisTime: " + result.thisTime);}if (result.totalTime >= 0) {System.out.println("TotalTime: " + result.totalTime);}System.out.println("WaitTime: " + (endTime-startTime));System.out.println("Complete"); }5 . 分析啟動(dòng)時(shí)間計(jì)算源碼 :
① 計(jì)算啟動(dòng)時(shí)間源碼 : APP 啟動(dòng)時(shí)間計(jì)算在 \frameworks\base\services\core\java\com\android\server\am\ActivityRecord.java 類中計(jì)算 ;
② 在 windowsDrawn 方法中調(diào)用 reportLaunchTimeLocked 計(jì)算時(shí)間 :
public void windowsDrawn() {synchronized(service) {if (displayStartTime != 0) {// 該方法是入口方法reportLaunchTimeLocked(SystemClock.uptimeMillis());}mStackSupervisor.sendWaitingVisibleReportLocked(this);startTime = 0;finishLaunchTickingLocked();if (task != null) {task.hasBeenVisible = true;}} }③ 啟動(dòng)時(shí)間計(jì)算 : 可以看到在 reportLaunchTimeLocked 方法中 , 計(jì)算了 thisTime 和 totalTime 的時(shí)間值 ;
private void reportLaunchTimeLocked(final long curTime) {final ActivityStack stack = task.stack;// 這里計(jì)算了 APP 啟動(dòng)時(shí)間final long thisTime = curTime - displayStartTime;final long totalTime = stack.mLaunchStartTime != 0? (curTime - stack.mLaunchStartTime) : thisTime;if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) {Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching", 0);EventLog.writeEvent(EventLogTags.AM_ACTIVITY_LAUNCH_TIME,userId, System.identityHashCode(this), shortComponentName,thisTime, totalTime);StringBuilder sb = service.mStringBuilder;sb.setLength(0);sb.append("Displayed ");sb.append(shortComponentName);sb.append(": ");TimeUtils.formatDuration(thisTime, sb);if (thisTime != totalTime) {sb.append(" (total ");TimeUtils.formatDuration(totalTime, sb);sb.append(")");}Log.i(ActivityManagerService.TAG, sb.toString());}mStackSupervisor.reportActivityLaunchedLocked(false, this, thisTime, totalTime);if (totalTime > 0) {//service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime);}displayStartTime = 0;stack.mLaunchStartTime = 0;}6 . APP 啟動(dòng)時(shí)間總結(jié) :
① 開始時(shí)間 : 點(diǎn)擊 APP 圖標(biāo) , 該時(shí)間就是 Am.java 中記錄的 startTime 開始時(shí)間 ;
// 記錄開始時(shí)間 final long startTime = SystemClock.uptimeMillis();② 系統(tǒng)分配內(nèi)存時(shí)間 : 之后 Android 系統(tǒng)會(huì)給 APP 分配內(nèi)存 , 這段時(shí)間是無(wú)法進(jìn)行優(yōu)化的 , 用戶無(wú)法控制 , 占用時(shí)間由系統(tǒng)控制 ;
③ 畫面繪制完畢調(diào)用方法 : 當(dāng)調(diào)用 ActivityRecord.java 中的 windowsDrawn 方法時(shí) , 畫面繪制完畢 , 此時(shí)會(huì)調(diào)用 reportLaunchTimeLocked 方法 , 傳入當(dāng)前時(shí)間 SystemClock.uptimeMillis() 作為參數(shù) ;
if (displayStartTime != 0) {// 該方法是入口方法reportLaunchTimeLocked(SystemClock.uptimeMillis());}④ 畫面繪制時(shí)間 : 在 reportLaunchTimeLocked 方法中 , 計(jì)算 thisTime 時(shí)間 , curTime 是傳入的 SystemClock.uptimeMillis() 參數(shù) , 即當(dāng)前時(shí)間 , displayStartTime 是畫面開始繪制的時(shí)間 , thisTime 就是畫面繪制時(shí)間 ;
final ActivityStack stack = task.stack; final long thisTime = curTime - displayStartTime;⑤ 總時(shí)間計(jì)算 : 這里計(jì)算總時(shí)間時(shí) , 需要根據(jù)當(dāng)前是冷啟動(dòng)還是熱啟動(dòng)進(jìn)行不同的計(jì)算 , 如果是冷啟動(dòng) , (curTime - stack.mLaunchStartTime) 時(shí)間是總時(shí)間 , thisTime 是熱啟動(dòng)的啟動(dòng)總時(shí)間 ;
final long totalTime = stack.mLaunchStartTime != 0? (curTime - stack.mLaunchStartTime) : thisTime; 《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的【Android 性能优化】应用启动优化 ( 启动白屏问题 | 应用启动时间测量 | 冷启动 | 热启动 | 应用启动时间计算源码分析 )的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【Android RTMP】安卓直播推流
- 下一篇: 【Android 性能优化】应用启动优化