我们经常碰到问题比如状态栏是有的,但是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的界面是黑屏。