文章基于Android 11
写在前面:
最近的文章都会放流程图,时序图之类的图片,解释下为什么这么做:
图片的好处:
- 流程清晰,一目了然
- 很多代码,如同老太太的裹脚布,又臭又长。影响理解,特别当想和别人解释清除一件事时,大量的代码,会搞得大家没耐心。
所以,如果只是浅尝辄止,只要看图即可得到你想要的结论。如果想深入了解,则跟着图片,对照源码梳理一遍流程。因此这篇文章,如果你有自己阅读源码的方式,理解完第一张图,后续的也就不用看了。后面的文章是给想学源码,却不知道怎么学的人,也是我看源码的个人习惯。
正文开始
本文重点
- 弄清楚Android是如何统计页面启动耗时的
- 这个时间是如何算出来的
查看Android的日志可以发现这样一条日志
2024-09-25 10:50:20.944 1292-1350 ActivityTaskManager system_process I Displayed xxx包名/.MainActivity: +966ms
那么我们要统计计时,其实只要弄清楚这个日志的时间怎么计算出来的。跟踪源码,全局搜索Displayed发现
ActivityMetricsLogger#logAppDisplayed
java
private void logAppDisplayed(TransitionInfoSnapshot info) {
if (info.type != TYPE_TRANSITION_WARM_LAUNCH && info.type != TYPE_TRANSITION_COLD_LAUNCH) {
return;
}
EventLog.writeEvent(WM_ACTIVITY_LAUNCH_TIME,
info.userId, info.activityRecordIdHashCode, info.launchedActivityShortComponentName,
info.windowsDrawnDelayMs);
StringBuilder sb = mStringBuilder;
sb.setLength(0);
sb.append("Displayed ");
sb.append(info.launchedActivityShortComponentName);
sb.append(": ");
TimeUtils.formatDuration(info.windowsDrawnDelayMs, sb);
Log.i(TAG, sb.toString());
}
可以看到重点就是这个时间info.windowsDrawnDelayMs,那么这个数据是怎么来的呢?直接上图(如果看不清楚,下载下来看):
下面是代码分析:整个过程属实无聊。是我自己看源码的一个方法,如果你有自己看源码的方式,后续不看也罢。
as右键find Usages(后续所有的只有一个来源都是这么得到的结论)
TransitionInfoSnapshot#windowsDrawnDelayMs数据的来源只有一个
java
private TransitionInfoSnapshot(TransitionInfo info, ActivityRecord launchedActivity,int windowsFullyDrawnDelayMs) {
//......
windowsDrawnDelayMs = info.mWindowsDrawnDelayMs;
//......
}
ActivityMetricsLogger#mWindowsDrawnDelayMs
java
/** Elapsed time from when we launch an activity to when its windows are drawn. */
//直译:从启动 Activity 到绘制其窗口所经过的时间。
int mWindowsDrawnDelayMs;
查看这个值的写入只有一个地方
ActivityMetricsLogger#notifyWindowsDrawn
java
/**
* Notifies the tracker that all windows of the app have been drawn.
*
* @return Non-null info if the activity was pending to draw, otherwise it might have been set
* to invisible (removed from active transition) or it was already drawn.
*/
TransitionInfoSnapshot notifyWindowsDrawn(@NonNull ActivityRecord r, long timestampNs) {
if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn " + r);
final TransitionInfo info = getActiveTransitionInfo(r);
if (info == null || info.allDrawn()) {
if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn no activity to be drawn");
return null;
}
//这里进行赋值
// Always calculate the delay because the caller may need to know the individual drawn time.
info.mWindowsDrawnDelayMs = info.calculateDelay(timestampNs);
info.removePendingDrawActivity(r);
final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
if (info.mLoggedTransitionStarting && info.allDrawn()) {
done(false /* abort */, info, "notifyWindowsDrawn - all windows drawn", timestampNs);
}
return infoSnapshot;
}
TransitionInfo#calculateDelay
java
int calculateDelay(long timestampNs) {
// Shouldn't take more than 25 days to launch an app, so int is fine here.
//传入的时间 - 开始过渡的时间
return (int) TimeUnit.NANOSECONDS.toMillis(timestampNs - mTransitionStartTimeNs);
}
接下来分两部分
- 开始过渡的时间什么时候赋值
- 传入的时间是什么时间
开始过渡的时间写入只有一个来源
java
private TransitionInfo(ActivityRecord r, LaunchingState launchingState, int transitionType,
boolean processRunning, boolean processSwitch) {
mLaunchingState = launchingState;
//此处就是赋值的地方
mTransitionStartTimeNs = launchingState.mCurrentTransitionStartTimeNs;
//......
}
查看launchingState.mCurrentTransitionStartTimeNs的赋值
TransitionInfoSnapshot#notifyActivityLaunching
java
private LaunchingState notifyActivityLaunching(Intent intent, @Nullable ActivityRecord caller,
int callingUid) {
final long transitionStartTimeNs = SystemClock.elapsedRealtimeNanos();
//......
if (existingInfo == null) {
// Only notify the observer for a new launching event.
launchObserverNotifyIntentStarted(intent, transitionStartTimeNs);
final LaunchingState launchingState = new LaunchingState();
launchingState.mCurrentTransitionStartTimeNs = transitionStartTimeNs;
return launchingState;
}
existingInfo.mLaunchingState.mCurrentTransitionStartTimeNs = transitionStartTimeNs;
return existingInfo.mLaunchingState;
}
可以看到是在notifyActivityLaunching方法被调用时的SystemClock.elapsedRealtimeNanos()。继续跟踪这个方法被调用的时机
ActivityStarter#startResolvedActivity
ActivityStartController#doPendingActivityLaunches
ActivityStart#executeRequest
到这里熟悉Activity启动流程的基本也就知道开始过渡时间的来源了。不熟悉的我放一张图,大家看下Activity的启动流程,应该也能明白这个时间大概是什么时候。(为了方便看清楚,只截图了一部分,完整图很大,截图放不下。可私聊我获取)
接下来查看结束时间,即TransitionInfo#calculateDelay传入的时间
再贴一次代码
java
TransitionInfoSnapshot notifyWindowsDrawn(@NonNull ActivityRecord r, long timestampNs) {
//......
// Always calculate the delay because the caller may need to know the individual drawn time.
info.mWindowsDrawnDelayMs = info.calculateDelay(timestampNs);
//......
}
ActivityRecord#onWindowsDrawn
java
/** Called when the windows associated app window container are drawn. */
void onWindowsDrawn(boolean drawn, long timestampNs) {
//......
final TransitionInfoSnapshot info = mStackSupervisor
.getActivityMetricsLogger().notifyWindowsDrawn(this, timestampNs);
//......
}
ActivityRecord#updateReportedVisibilityLocked
java
void updateReportedVisibilityLocked() {
if (nowDrawn != reportedDrawn) {
onWindowsDrawn(nowDrawn, SystemClock.elapsedRealtimeNanos());
reportedDrawn = nowDrawn;
}
}
至此,结束时间赋值来源分析结束