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

news2024/10/2 12:28:05

文章基于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;
        }
    }

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2166093.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python的Pandas库学习指南

应用场景 Pandas库因其强大的数据处理和分析能力,在数据科学和数据分析领域有着广泛的应用。 1. 数据清洗 处理缺失数据:通过删除或填充缺失值来清洗数据。数据类型转换:将数据转换为合适的格式,例如将日期字符串转换为日期类型…

k8s_资源管理介绍

资源管理介绍 在k8s中,所有内容都抽象成资源,用户需要通过操作资源来管理k8s k8s本身就是一个集群系统,用户可以在集群中部署服务,在k8s集群中运行一个个的容器,将指定的程序部署到容器中 k8s最小的管理单元是pod&…

AI-Talk开发板之wifi scan

一、说明 AI-Talk开发板使用ESP32-C3扩展WIFI通信功能,与CSK6011A通过SPI接口通信。 与处理器的信号连接: ESP32-C3需要烧录hosted固件,参考:AI-Talk开发板更新ESP32固件_esp32 固件-CSDN博客 二、工程 1、创建项目 进入exampl…

本地编译安装|编译安装最新版postgis3.4.3版本指南

一、本地编译安装步骤介绍 本地编译,指的是在本地环境编译安装某个软件,例如,本文所述的最新版postgis3.4.3,本地是什么cpu架构,编译完成后,编译产出物就可以在其它的同cpu架构的服务器上直接适用了&#…

关于JAVA中Scanner和sout读取超时问题

1.Scanner与System.out为什么慢 Scanner读取速度慢的原因: 在ACM模式下,输入数据和输出数据时,他会将数据放在一个文件里面 在new上一个Scanner时,去调用一个next()方法,他会去访问IO设备&…

【mac开发入坑指南】能让你的终端好用一万倍的神仙组合iTerm2 + oh-my-zsh

介绍 iTerm2 iTerm2是默认终端的替代品,也是目前Mac系统下最好用的终端工具,集颜值和效率于一身。 Oh-My-Zsh Oh My Zsh 是一款社区驱动的命令行工具,正如它的主页上说的,Oh My Zsh 是一种生活方式。 它基于Zsh 命令行&#xff0c…

docker-文件复制(docker ps:用于在Docker主机和容器之间拷贝文件或目录)

文章目录 1、把宿主机的文件复制到容器内部1.1、查询 宿主机 root 下的文件1.2、docker cp /root/anaconda-ks.cfg spzx-redis:/root1.3、查看 spzx-redis 容器 中/root目录下是否有 anaconda-ks.cfg 文件 2、把容器中的文件 复制 到宿主机中2.1、查看 spzx-redis 容器 / 下的文…

怎么批量制作文本或链接静态码?批量静态码在线的生成技巧

怎么将文本或者链接做成静态二维码呢?有很多的场景都会使用静态二维码,在需要制作大量静态二维码时,有什么方法能够快速提高二维码制作效率呢?在网上能够找到在线二维码生成器的功能来快速生成二维码,下面通过这篇文章…

【shell脚本8】Shell脚本学习--其他

目录 ​编辑 Shell输入输出重定向 重定向深入讲解 Here Document Shell输入输出重定向 Unix 命令默认从标准输入设备(stdin)获取输入,将结果输出到标准输出设备(stdout)显示。一般情况下,标准输入设备就是键盘,标准输出设备就是终端&…

JAVA-StringBuilder和StringBuffer

一、认识String类 1.认识 String在Java中是字符串类型,但与其他类型不同。它是一个类,可以创建对象的类。与int、char等自待类型有些许不同。但它仍然是java提供的一种类型。 类中有4个属性,这里主要认识一下value属性。它是实际存放字符串…

AI 赋能大模型:从 ChatGPT 到国产大模型的角逐与发展契机

在当今科技飞速发展的时代,大模型作为人工智能领域的关键技术,正引发着深刻的变革。它们在自然语言处理、计算机视觉、语音识别等众多领域展现出了惊人的潜力,为各行各业带来了前所未有的机遇和挑战。本文将深入剖析大模型的技术原理、市场态…

字符串的join和os.path.join()

>>> key "".join("I love China.")>>> keyI love China.>>> key.join("--xwf")-I love China.-I love China.xI love China.wI love China.f为什么执行key.join("--xwf")的结果不是“I love China.--xwf”…

鄂尔多斯市鄂托克旗巴音乌苏六保煤矿5MW分布式光伏项目案例分析

摘 要:分布式光伏发电利用太阳能光伏板,分散布置在各区域,通过小规模、模块化并网或独立使用。其特点为就近发电、并网、转换和使用。技术进步和政策支持降低了光伏组件成本,推动了分布式光伏监控系统在多个领域的广泛应用。在全球…

【学习笔记】UWB技术定位原理

UWB技术还没有完全普及,却在慢慢兴起,一旦爆发,势不可挡也。 01-什么是UWB? UWB(Ultra Wide Band)是一种无载波通信技术,UWB不使用载波,而是使用短的能量脉冲序列,并通…

TikTok多语言商城系统源码+落地页 附搭建教程

TikTok多语言商城系统源码落地页 附搭建教程 环境 nginx php7.4.33 redis5.0.8 Memcached 1.6.6 mysql5.6 phpMyAdmin 伪静态 location / { try_files $uri $uri/ /index.php?$query_string; } 源码下载:https://download.csdn.net/download/m0_660477…

CVPR2021 安全AI挑战者计划第六期赛道一第二名方案分享 (UM-SIAT队)

关联比赛: CVPR2021 安全AI挑战者计划第六期:防御模型的白盒对抗攻击 CVPR2021 安全AI挑战者计划第六期赛道一第二名方案分享 (UM-SIAT队) 1.赛题简介 1.比赛通过15个防御模型测试攻击算法,其中包括13个在CIFAR-10上训练的模型…

单刀单掷(SPST)及单刀双掷(SPDT)模拟开关

单刀单掷(SPST)及单刀双掷(SPDT)模拟开关是对继电器元件的一种模拟简化, 本质上还是一种用开关控制的开关. 我们先从单刀双掷(SPDT)模拟开关与继电器的一个对比中了解其特性. 单刀双掷(SPDT)模拟开关 通过菜单 绘制--有源集成电路--添加模拟开关(SPDT) 可以添加一个单刀双掷…

我与Linux的爱恋:进程创建|终止

​ ​ 🔥个人主页:guoguoqiang. 🔥专栏:Linux的学习 ​ 文章目录 一、进程创建**fork函数**写时拷贝 二、进程终止进程退出的常见方法 一、进程创建 fork函数 在Linux中fork函数是非常重要的函数,它从已存在进程…

黑马智数Day4-2

渲染基础Table列表 封装获取企业列表接口 export function getEnterpriseListAPI(params) {return request({url: /park/enterprise,params}) } 组件中获取数据 <script> import { getEnterpriseListAPI } from /apis/enterprise export default {name: Building,dat…

C++ | Leetcode C++题解之第435题无重叠区间

题目&#xff1a; 题解&#xff1a; class Solution { public:int eraseOverlapIntervals(vector<vector<int>>& intervals) {if (intervals.empty()) {return 0;}sort(intervals.begin(), intervals.end(), [](const auto& u, const auto& v) {retur…