WMS常見問題一(Activity displayed延遲)

我們經常碰到問題比如狀態欄是有的,但是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。

  1. void startSpecificActivityLocked(ActivityRecord r,
  2. boolean andResume, boolean checkConfig) {
  3. // Is this activity's application already running?
  4. ProcessRecord app = mService.getProcessRecordLocked(r.processName,
  5. r.info.applicationInfo.uid, true);
  6. r.task.stack.setLaunchTime(r);//設置Launch的time
  7. if (app != null && app.thread != null) {
  8. try {
  9. if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
  10. || !"android".equals(r.info.packageName)) {
  11. app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
  12. mService.mProcessStats);
  13. }
  14. realStartActivityLocked(r, app, andResume, checkConfig);//會調用ActivityThread的handleLaunchActivity
  15. return;
  16. } catch (RemoteException e) {
  17. Slog.w(TAG, "Exception when starting activity "
  18. + r.intent.getComponent().flattenToShortString(), e);
  19. }
  20. }
  21. mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
  22. "activity", r.intent.getComponent(), false, false, true);//啓動進程
  23. }

在ActivityThread的handleLaunchActivity函數中,會先調用performLaunchActivity函數,這裏函數裏面會創建Activity,然後調用其onCreate函數。然後會調用handleResumeActivity函數,在這個函數中先調用了performResumeActivity函數(會調用Activity的onResume函數),然後handleResumeActivity還會調用WindowManager的addView函數(這個函數會到ViewRootImpl的setView函數,然後再到WMS的addWindow增加窗口,然後ViewRootImpl裏面再佈局、繪製等)

我們再來看看設置Launchtime是保存在mLaunchStartTime中。這個時間就是應用啓動的時間

  1. void setLaunchTime(ActivityRecord r) {
  2. if (r.displayStartTime == 0) {
  3. r.fullyDrawnStartTime = r.displayStartTime = SystemClock.uptimeMillis();
  4. if (mLaunchStartTime == 0) {
  5. startLaunchTraces(r.packageName);
  6. mLaunchStartTime = mFullyDrawnStartTime = r.displayStartTime;
  7. }
  8. } else if (mLaunchStartTime == 0) {
  9. startLaunchTraces(r.packageName);
  10. mLaunchStartTime = mFullyDrawnStartTime = SystemClock.uptimeMillis();
  11. }
  12. }


接着我們再來看AppWindowToken的updateReportedVisibilityLocked函數,這個函數會在WMS的多個地方調用,但凡有窗口變化必然會調用這個函數,我們來看下它先會遍歷該AppWindowToken下所有的窗口,然後計算器繪製的窗口數量,當所有的窗口已經繪製過了,就會將nowDrawn的狀態置爲true,如果這個這個這個狀態發生了改變就會發送REPORT_APPLICATION_TOKEN_DRAWN消息。

  1. void updateReportedVisibilityLocked() {
  2. .....
  3. final int N = allAppWindows.size();
  4. for (int i=0; i<N; i++) {//遍歷這個AppWindowToken下所有的窗口
  5. WindowState win = allAppWindows.get(i);
  6. if (win == startingWindow || win.mAppFreezing
  7. || win.mViewVisibility != View.VISIBLE
  8. || win.mAttrs.type == TYPE_APPLICATION_STARTING
  9. || win.mDestroying) {
  10. continue;
  11. }
  12. numInteresting++;
  13. if (win.isDrawnLw()) {
  14. numDrawn++;//繪製的窗口數量
  15. if (!win.mWinAnimator.isAnimating()) {
  16. numVisible++;
  17. }
  18. nowGone = false;
  19. } else if (win.mWinAnimator.isAnimating()) {
  20. nowGone = false;
  21. }
  22. }
  23. boolean nowDrawn = numInteresting > 0 && numDrawn >= numInteresting;//所有窗口繪製了
  24. boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting;
  25. if (!nowGone) {
  26. // If the app is not yet gone, then it can only become visible/drawn.
  27. if (!nowDrawn) {
  28. nowDrawn = reportedDrawn;
  29. }
  30. if (!nowVisible) {
  31. nowVisible = reportedVisible;
  32. }
  33. }
  34. if (nowDrawn != reportedDrawn) {//狀態改變就會發送消息
  35. if (nowDrawn) {
  36. Message m = service.mH.obtainMessage(
  37. H.REPORT_APPLICATION_TOKEN_DRAWN, this);
  38. service.mH.sendMessage(m);
  39. }
  40. reportedDrawn = nowDrawn;
  41. }

我們看看判斷窗口是否已經繪製的函數,READY_TO_SHOW和HAS_DRAWN狀態都可以。

  1. public boolean isDrawnLw() {
  2. return mHasSurface && !mDestroying &&
  3. (mWinAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW
  4. || mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN);
  5. }

我們再來看看WMS中對這個REPORT_APPLICATION_TOKEN_DRAWN消息的處理主要就是調用了AppWindowToken中的AppToken的windowsDrawn方法

  1. case REPORT_APPLICATION_TOKEN_DRAWN: {
  2. final AppWindowToken wtoken = (AppWindowToken)msg.obj;
  3. try {
  4. if (DEBUG_VISIBILITY) Slog.v(
  5. TAG, "Reporting drawn in " + wtoken);
  6. wtoken.appToken.windowsDrawn();
  7. } catch (RemoteException ex) {
  8. }
  9. } break;

這個函數在ActivityRecord的Token類中實現,其又是調用了ActivityRecord 的windowsDrawnLocked函數

  1. @Override
  2. public void windowsDrawn() {
  3. synchronized (mService) {
  4. ActivityRecord r = tokenToActivityRecordLocked(this);
  5. if (r != null) {
  6. r.windowsDrawnLocked();
  7. }
  8. }
  9. }

ActivityRecord 的windowsDrawnLocked函數主要是調用了reportLaunchTimeLocked函數,注意這個時候我們把當前時間傳入了reportLaunchTimeLocked函數

  1. void windowsDrawnLocked() {
  2. if (displayStartTime != 0) {
  3. reportLaunchTimeLocked(SystemClock.uptimeMillis());
  4. //add by LC, startActivity over, disable boost
  5. if(mStackSupervisor.mIsBoostEnable == true)
  6. {
  7. mStackSupervisor.mPerf.setBoostEnable_native(0);
  8. mStackSupervisor.mIsBoostEnable = false;
  9. }
  10. }
  11. mStackSupervisor.sendWaitingVisibleReportLocked(this);
  12. startTime = 0;
  13. finishLaunchTickingLocked();
  14. if (task != null) {
  15. task.hasBeenVisible = true;
  16. }
  17. }

reportLaunchTimeLocked函數會把當前的時間減去 Launchtime的時間(Activity啓動到顯示的時間差),然後打印displayed的延遲時間的log。這個時間差就是Activity的Launchtime時間到這個AppWindowToken下的所有窗口都到一個準備顯示狀態的時間差。所有窗口到準備顯示狀態還需要VSync信號過來再進行顯示的。

  1. private void reportLaunchTimeLocked(final long curTime) {
  2. final ActivityStack stack = task.stack;
  3. if (stack == null) {
  4. return;
  5. }
  6. final long thisTime = curTime - displayStartTime;
  7. final long totalTime = stack.mLaunchStartTime != 0//當前時間減去Launchtime
  8. ? (curTime - stack.mLaunchStartTime) : thisTime;
  9. if (SHOW_ACTIVITY_START_TIME) {
  10. Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching: " + packageName, 0);
  11. EventLog.writeEvent(EventLogTags.AM_ACTIVITY_LAUNCH_TIME,
  12. userId, System.identityHashCode(this), shortComponentName,
  13. thisTime, totalTime);
  14. StringBuilder sb = service.mStringBuilder;
  15. sb.setLength(0);
  16. sb.append("Displayed ");
  17. sb.append(shortComponentName);
  18. sb.append(": ");
  19. TimeUtils.formatDuration(thisTime, sb);
  20. if (thisTime != totalTime) {
  21. sb.append(" (total ");
  22. TimeUtils.formatDuration(totalTime, sb);
  23. sb.append(")");
  24. }
  25. Log.i(TAG, sb.toString());
  26. }
  27. mStackSupervisor.reportActivityLaunchedLocked(false, this, thisTime, totalTime);
  28. if (totalTime > 0) {
  29. //service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime);
  30. }
  31. displayStartTime = 0;
  32. stack.mLaunchStartTime = 0;
  33. }


這樣我們再來分析下如下的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的界面是黑屏。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章