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