我們經常碰到問題比如狀態欄是有的,但是Activity的界面是黑屏。而logcat中也有如下log:
02-27 16:07:47.816929 2667 2733 I ActivityManager: Displayed com.android.settings/.SubSettings: +30s71ms
這樣的問題我們如何分析,這裏我們從代碼角度分析下。當然我們追查log的時候是查log的源碼,再通過哪裏調用一步步反推的,這裏我們直接從一開始的順序分析。這裏邏輯比較清楚。
我們以前在分析AMS啓動Activity的時候,先會調用ActivityStackSupervisor的startSpecificActivityLocked方法,這個方法裏面會先啓動進程還是去ActivityThrread調用handleLaunchActivity等。在這裏我們要注意一個地方是設置Activity的Launch time。
- void startSpecificActivityLocked(ActivityRecord r,
- boolean andResume, boolean checkConfig) {
- // Is this activity's application already running?
- ProcessRecord app = mService.getProcessRecordLocked(r.processName,
- r.info.applicationInfo.uid, true);
-
- r.task.stack.setLaunchTime(r);//設置Launch的time
-
- if (app != null && app.thread != null) {
- try {
- if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
- || !"android".equals(r.info.packageName)) {
- app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
- mService.mProcessStats);
- }
- realStartActivityLocked(r, app, andResume, checkConfig);//會調用ActivityThread的handleLaunchActivity
- return;
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception when starting activity "
- + r.intent.getComponent().flattenToShortString(), e);
- }
-
- }
-
- mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
- "activity", r.intent.getComponent(), false, false, true);//啓動進程
- }
在ActivityThread的handleLaunchActivity函數中,會先調用performLaunchActivity函數,這裏函數裏面會創建Activity,然後調用其onCreate函數。然後會調用handleResumeActivity函數,在這個函數中先調用了performResumeActivity函數(會調用Activity的onResume函數),然後handleResumeActivity還會調用WindowManager的addView函數(這個函數會到ViewRootImpl的setView函數,然後再到WMS的addWindow增加窗口,然後ViewRootImpl裏面再佈局、繪製等)
我們再來看看設置Launchtime是保存在mLaunchStartTime中。這個時間就是應用啓動的時間
- void setLaunchTime(ActivityRecord r) {
- if (r.displayStartTime == 0) {
- r.fullyDrawnStartTime = r.displayStartTime = SystemClock.uptimeMillis();
- if (mLaunchStartTime == 0) {
- startLaunchTraces(r.packageName);
- mLaunchStartTime = mFullyDrawnStartTime = r.displayStartTime;
- }
- } else if (mLaunchStartTime == 0) {
- startLaunchTraces(r.packageName);
- mLaunchStartTime = mFullyDrawnStartTime = SystemClock.uptimeMillis();
- }
- }
接着我們再來看AppWindowToken的updateReportedVisibilityLocked函數,這個函數會在WMS的多個地方調用,但凡有窗口變化必然會調用這個函數,我們來看下它先會遍歷該AppWindowToken下所有的窗口,然後計算器繪製的窗口數量,當所有的窗口已經繪製過了,就會將nowDrawn的狀態置爲true,如果這個這個這個狀態發生了改變就會發送REPORT_APPLICATION_TOKEN_DRAWN消息。
- void updateReportedVisibilityLocked() {
- .....
- final int N = allAppWindows.size();
- for (int i=0; i<N; i++) {//遍歷這個AppWindowToken下所有的窗口
- WindowState win = allAppWindows.get(i);
- if (win == startingWindow || win.mAppFreezing
- || win.mViewVisibility != View.VISIBLE
- || win.mAttrs.type == TYPE_APPLICATION_STARTING
- || win.mDestroying) {
- continue;
- }
-
- numInteresting++;
- if (win.isDrawnLw()) {
- numDrawn++;//繪製的窗口數量
- if (!win.mWinAnimator.isAnimating()) {
- numVisible++;
- }
- nowGone = false;
- } else if (win.mWinAnimator.isAnimating()) {
- nowGone = false;
- }
- }
-
- boolean nowDrawn = numInteresting > 0 && numDrawn >= numInteresting;//所有窗口繪製了
- boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting;
- if (!nowGone) {
- // If the app is not yet gone, then it can only become visible/drawn.
- if (!nowDrawn) {
- nowDrawn = reportedDrawn;
- }
- if (!nowVisible) {
- nowVisible = reportedVisible;
- }
- }
-
- if (nowDrawn != reportedDrawn) {//狀態改變就會發送消息
- if (nowDrawn) {
- Message m = service.mH.obtainMessage(
- H.REPORT_APPLICATION_TOKEN_DRAWN, this);
- service.mH.sendMessage(m);
- }
- reportedDrawn = nowDrawn;
- }
我們看看判斷窗口是否已經繪製的函數,READY_TO_SHOW和HAS_DRAWN狀態都可以。
- public boolean isDrawnLw() {
- return mHasSurface && !mDestroying &&
- (mWinAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW
- || mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN);
- }
我們再來看看WMS中對這個REPORT_APPLICATION_TOKEN_DRAWN消息的處理主要就是調用了AppWindowToken中的AppToken的windowsDrawn方法
- case REPORT_APPLICATION_TOKEN_DRAWN: {
- final AppWindowToken wtoken = (AppWindowToken)msg.obj;
-
- try {
- if (DEBUG_VISIBILITY) Slog.v(
- TAG, "Reporting drawn in " + wtoken);
- wtoken.appToken.windowsDrawn();
- } catch (RemoteException ex) {
- }
- } break;
這個函數在ActivityRecord的Token類中實現,其又是調用了ActivityRecord 的windowsDrawnLocked函數
- @Override
- public void windowsDrawn() {
- synchronized (mService) {
- ActivityRecord r = tokenToActivityRecordLocked(this);
- if (r != null) {
- r.windowsDrawnLocked();
- }
- }
- }
ActivityRecord 的windowsDrawnLocked函數主要是調用了reportLaunchTimeLocked函數,注意這個時候我們把當前時間傳入了reportLaunchTimeLocked函數
- void windowsDrawnLocked() {
- if (displayStartTime != 0) {
- reportLaunchTimeLocked(SystemClock.uptimeMillis());
-
- //add by LC, startActivity over, disable boost
- if(mStackSupervisor.mIsBoostEnable == true)
- {
- mStackSupervisor.mPerf.setBoostEnable_native(0);
- mStackSupervisor.mIsBoostEnable = false;
- }
- }
- mStackSupervisor.sendWaitingVisibleReportLocked(this);
- startTime = 0;
- finishLaunchTickingLocked();
- if (task != null) {
- task.hasBeenVisible = true;
- }
- }
reportLaunchTimeLocked函數會把當前的時間減去 Launchtime的時間(Activity啓動到顯示的時間差),然後打印displayed的延遲時間的log。這個時間差就是Activity的Launchtime時間到這個AppWindowToken下的所有窗口都到一個準備顯示狀態的時間差。所有窗口到準備顯示狀態還需要VSync信號過來再進行顯示的。
- private void reportLaunchTimeLocked(final long curTime) {
- final ActivityStack stack = task.stack;
- if (stack == null) {
- return;
- }
- final long thisTime = curTime - displayStartTime;
- final long totalTime = stack.mLaunchStartTime != 0//當前時間減去Launchtime
- ? (curTime - stack.mLaunchStartTime) : thisTime;
- if (SHOW_ACTIVITY_START_TIME) {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching: " + packageName, 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(TAG, sb.toString());
- }
- mStackSupervisor.reportActivityLaunchedLocked(false, this, thisTime, totalTime);
- if (totalTime > 0) {
- //service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime);
- }
- displayStartTime = 0;
- stack.mLaunchStartTime = 0;
- }
這樣我們再來分析下如下的log,這個Activity的displayed延遲了30多秒。
02-27 16:07:47.816929 2667 2733 I ActivityManager: Displayed com.android.settings/.SubSettings: +30s71ms
基於這樣的問題,我們回顧上面的代碼,在Activity的handleLaunchActivity中先後會調用Activity的onCreate和onResume函數,然後纔是到WMS的addWindow創建窗口(窗口創建了之後纔會把所有和這個AppWindowToken的窗口置爲一個準備顯示的狀態,這個時候就會去打印這個log,也會計算這個延時)並且最後的顯示界面還是靠VSync信號驅動的。所以這中間應用的onCreate和onResume耗時的可能性比較大才會最終導致打印這個log,現象也是狀態欄能顯示而Activity的界面是黑屏。