当前位置: 首页 > news >正文

【Android】页面启动耗时统计流程梳理

文章基于Android 11

写在前面:
最近的文章都会放流程图,时序图之类的图片,解释下为什么这么做:
图片的好处:

  • 流程清晰,一目了然
  • 很多代码,如同老太太的裹脚布,又臭又长。影响理解,特别当想和别人解释清除一件事时,大量的代码,会搞得大家没耐心。

所以,如果只是浅尝辄止,只要看图即可得到你想要的结论。如果想深入了解,则跟着图片,对照源码梳理一遍流程。因此这篇文章,如果你有自己阅读源码的方式,理解完第一张图,后续的也就不用看了。后面的文章是给想学源码,却不知道怎么学的人,也是我看源码的个人习惯。

正文开始

本文重点

  • 弄清楚Android是如何统计页面启动耗时的
  • 这个时间是如何算出来的

查看Android的日志可以发现这样一条日志

2024-09-25 10:50:20.944 1292-1350 ActivityTaskManager system_process I Displayed xxx包名/.MainActivity: +966ms

那么我们要统计计时,其实只要弄清楚这个日志的时间怎么计算出来的。跟踪源码,全局搜索Displayed发现

ActivityMetricsLogger#logAppDisplayed

    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数据的来源只有一个

		private TransitionInfoSnapshot(TransitionInfo info, ActivityRecord launchedActivity,int windowsFullyDrawnDelayMs) {//......windowsDrawnDelayMs = info.mWindowsDrawnDelayMs;//......}

ActivityMetricsLogger#mWindowsDrawnDelayMs

		/** Elapsed time from when we launch an activity to when its windows are drawn. *///直译:从启动 Activity 到绘制其窗口所经过的时间。int mWindowsDrawnDelayMs;

查看这个值的写入只有一个地方

ActivityMetricsLogger#notifyWindowsDrawn

    /*** 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

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);
}

接下来分两部分

  • 开始过渡的时间什么时候赋值
  • 传入的时间是什么时间

开始过渡的时间写入只有一个来源

        private TransitionInfo(ActivityRecord r, LaunchingState launchingState, int transitionType,boolean processRunning, boolean processSwitch) {mLaunchingState = launchingState;//此处就是赋值的地方mTransitionStartTimeNs = launchingState.mCurrentTransitionStartTimeNs;//......}

查看launchingState.mCurrentTransitionStartTimeNs的赋值

TransitionInfoSnapshot#notifyActivityLaunching

    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传入的时间

再贴一次代码

    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

    /** 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

    void updateReportedVisibilityLocked() {if (nowDrawn != reportedDrawn) {onWindowsDrawn(nowDrawn, SystemClock.elapsedRealtimeNanos());reportedDrawn = nowDrawn;}}

至此,结束时间赋值来源分析结束

相关文章:

  • Git从了解到操作
  • JavaEE: 深入探索TCP网络编程的奇妙世界(二)
  • 建立队列,插入队列,删除队列
  • scrapy之setting文件详解
  • 0基础学习PyTorch——时尚分类(Fashion MNIST)训练和推理
  • 阿里云函数计算 x NVIDIA 加速企业 AI 应用落地
  • 10.Lab Nine —— file system-上
  • 丹摩智算(damodel)部署stable diffusion实验
  • 三子棋小游戏
  • 【React】组件通信
  • Android 已经过时的方法用什么新方法替代?
  • 使用Python解决数据分析中的相关性分析
  • macOS 15 Blank OVF - macOS Sequoia 虚拟化解决方案
  • 分享个锂电池升压芯片,3.7V升5V大电流输出的芯片。AD2403 PWM升压芯片
  • 如何创建一个包含多个列的表?
  • @jsonView过滤属性
  • [iOS]Core Data浅析一 -- 启用Core Data
  • 《Java编程思想》读书笔记-对象导论
  • 【Linux系统编程】快速查找errno错误码信息
  • 002-读书笔记-JavaScript高级程序设计 在HTML中使用JavaScript
  • learning koa2.x
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • spring boot下thymeleaf全局静态变量配置
  • 使用common-codec进行md5加密
  • 说说动画卡顿的解决方案
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 原生 js 实现移动端 Touch 滑动反弹
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • ​比特币大跌的 2 个原因
  • # 利刃出鞘_Tomcat 核心原理解析(八)-- Tomcat 集群
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • #### go map 底层结构 ####
  • #nginx配置案例
  • #systemverilog# 之 event region 和 timeslot 仿真调度(十)高层次视角看仿真调度事件的发生
  • (04)odoo视图操作
  • (6) 深入探索Python-Pandas库的核心数据结构:DataFrame全面解析
  • (day 12)JavaScript学习笔记(数组3)
  • (python)数据结构---字典
  • (Repost) Getting Genode with TrustZone on the i.MX
  • (黑马出品_高级篇_01)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式
  • (十六)视图变换 正交投影 透视投影
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • (一)Neo4j下载安装以及初次使用
  • (转)ObjectiveC 深浅拷贝学习
  • (转)大型网站架构演变和知识体系
  • .NET Micro Framework 4.2 beta 源码探析
  • .Net 高效开发之不可错过的实用工具
  • .NET 直连SAP HANA数据库
  • .pop ----remove 删除
  • @AliasFor 使用
  • @Builder用法
  • [ Socket学习 ] 第一章:网络基础知识
  • [ 代码审计篇 ] 代码审计案例详解(一) SQL注入代码审计案例
  • [ 云计算 | AWS 实践 ] 基于 Amazon S3 协议搭建个人云存储服务