[面试官,你坐好],今天我给你吹下卡顿监控

news2024/12/23 9:55:53

这是一篇面试总结稿,根据之前的面试过程以一种模拟面试的风格进行阐述。

面试官: 自我介绍下

诶?这面试官头发还比较多,应该不牛逼,心里踏实了不少。

我: 面试官你好,我叫**,5年工作经验。曾经跟OPPO产品PK,跟VIVO技术Battle,练就了一身不要脸的乙方心态,平时有写博客的习惯,20来篇博客单篇阅读量3000+,我今天来面试高级Android开发工程师岗位,精通八门兵器,善于救死扶伤…(一顿吹…)

面试官: 嗯,不错啊,我今天主要问下性能方面,你之前有没有做过性能优化?

 上来就直奔主题? 不循循渐进么?我准备的那一堆废话还没说呢。

我: 嗯…我做过功耗优化,比如耗电优化,流量优化。性能方面我做过卡顿优化,主要针对卡顿监控方面。

面试官: 那着重描述下卡顿方面吧,说下你是怎么做的。

废话先给怼上,怎么可能白准备。

我: 嗯… 卡顿其实分为直观的和微观两个方向的,举个例子

  • 比如用户点击了登录,预期是得到登录成功/失败的反馈,可现在没有页面刷新,实际的刷新耗时超出了预期,这就是直观的卡顿。

  • 比如用户在看股票,正常60秒刷新一次,可到了60秒,触发了刷新但是刷新处理逻辑耗时较长,导致新的数据在下一个60秒之前的第59秒才回来,用户基本无感知,主要说的是内部耗时的问题,这种情况可以粗略的理解为微观卡顿。

面试官: 你不用说概念,直接说为什么会产生卡顿就行了

这小子,居然.... 不不不,是这位面试官,居然发现了我在划水...

好的面试官,我整体吹一…,啊不,我整体说一下,说的不对您及时补充。手动呲牙

  • 首先,我认为卡顿产生的原因主要是某些逻辑处理时间超出了我们的预期阈值,这些耗时操作影响了页面的刷新,当刷新的频率和速度(60帧/s)低于我们的预期时,就会产生我们理解的卡顿。

  • 其次,如果主线程耗时过长还可能产生ANR,当然ANR在不同场景下的耗时阈值不同。

  • 最后,当然也有很多现实因素会影响用户体验,比如手机的配置,所处环境的网络质量等…

面试官: 嗯,那你怎么做到让App不卡顿?

我靠,这个问题是个多元化问题啊,我一想,这一时半会也说不完啊。
干脆把问题聚焦到"卡顿监控上",于是整理下发型,说了一句"你刚问的啥? 手动呲牙..."
.
.
.
当然咱也不是那不靠谱的人,怎么会干那不靠谱的事呢?对吧.

我认为做到App不卡顿主要分为以下几种情况

  • 1.如果App整体都是自己开发,那自己写逻辑尽量保持简洁合规,主线程尽可能不做耗时操作,UI绘制层级尽量保持扁平,复杂数据处理尽量前置,或缓存等等.

  • 2.如果项目多人合作开发,这个问题会演进为怎么治理这些卡顿。

  • 3.如果需要治理卡顿,首先需要发现这些卡顿,也就变成了你怎么发现它卡了?

面试官在我之前的项目中我主要负责卡顿监控操作,这为我们多模块的卡顿治理提供了大量的卡顿素材,使得我们能及时发现问题,解决问题。(引导面试官,往自己擅长的领域跟进)

面试官: 行,同学说的不错啊,你主要负责卡顿监控是吧,那说说你怎么监控的吧?

我一猜这小子就会这么问,怎么可能会有面试官不接收挑战的。

他不给你问到你答不上来的那他肯定是第一次面试。

这个时候就要 分阶段,分成果 的去阐述这个卡顿监控的方案选型,实现落地以及优化过程了。

这个过程描述的越透彻,一般面试官对你的能力就越认可。

所以这个时候,我一看面试官来劲了。

反手给他一句,面试官我上个厕所,你坐好,别着急...(说啥到厕所也得把红内裤换上,再看看发型...)

我:

起因: 我们做这个卡顿监控最初是因为收到了部分客诉,反馈某几个功能块操作不流畅,掉帧卡顿,尤其体现在中低端机上,客诉70%都是中低端机。

方案:

最开始,并没有特别好的方案,因此我们先后调研了两个方案

方案一:参考了ANR-WatchDog机制

ANR-WatchDog机制原理不复杂,它内部启动了一个子线程,定时通过主线程Handler发送Message,然后定时去检查Message的处理结果。

通俗来说就是利用了Android系统MessageQueue队列的排队处理特性。

通过主线程Handler发送消息到MessageQueue队列,5秒去看下这个Message有没有被消费,如果消费了则代表没有卡顿,如果没有,则代表有卡顿,当然这个5秒是可调节的。

这种监控非常简单,接入成本较低,但是有弊端,比如

  • 1.它会漏报情况,举个例子,比如我们以5秒未响应作为卡顿阈值,如果我们发送监听Message的时间在上一个消息处理的第2-5秒之间,那这种就会产生漏报。

  • 2.监听间隔越小,系统开销越大。

  • 3.即便监听到了,不好区分卡顿链路,无法提供准确的卡顿堆栈。

方案二:参考BlockCanary开源库,利用Looper.loop() Printer打印机制

每次Message处理(也就是dispatchMessage(msg))都会在处理前,和处理后通过Looper.mLogging打印日志。

我们只需知道打印日志的时间差即可知道Message的处理耗时。当耗时超过我们的阈值时我们即可收集调用堆栈,然后根据堆栈进行针对性优化即可。

这种方案也有弊端,比如

  • 1.View的TouchEvent事件是在queue.next()中处理的,只统计dispatchMessage(msg)前后耗时,不会覆盖到View的TouchEvent耗时。

  • 2.IdleHandler.queueIdle()也在queue.next()中,当主线程空闲会调用IdleHandler,此时IdleHandler也是在主线程执行,当过于耗时时也可能出现卡顿,甚至到只ANR,这种场景也无法监控。

  • 3.SyncBarrier(同步屏障)的泄漏同样无法被监控到,这种情况比较少见,参考了微信的监控方案。

比如

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            ...

            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            }

            ...

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
        }
     }

每次Message处理(也就是dispatchMessage(msg))都会在处理前,和处理后通过Looper.mLogging打印日志.

我们只需知道打印日志的时间差即可知道Message的处理耗时。当耗时超过我们的阈值的时候就是发生卡顿的时机,我们触发收集调用堆栈,然后根据堆栈进行针对性优化即可。

替换Printer可以通过Looper提供的方法,然后自定义Printer进行计算统计即可。

	/**
     * Control logging of messages as they are processed by this Looper.  If
     * enabled, a log message will be written to <var>printer</var>
     * at the beginning and ending of each message dispatch, identifying the
     * target Handler and message contents.
     *
     * @param printer A Printer object that will receive log messages, or
     * null to disable message logging.
     */
    public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }

@Override
public void println(String x) {
        if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
            return;
        }
        if (!mPrintingStarted) {
            //1、记录第一次执行时间,mStartTimestamp
            mStartTimestamp = System.currentTimeMillis();
            mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
            mPrintingStarted = true;
            startDump(); //2、开始dump堆栈信息
        } else {
            //3、第二次就进来这里了,调用isBlock 判断是否卡顿
            final long endTime = System.currentTimeMillis();
            mPrintingStarted = false;
            if (isBlock(endTime)) {
                notifyBlockEvent(endTime);
            }
            stopDump(); //4、结束dump堆栈信息
        }
    }

    //判断是否卡顿的代码很简单,跟上次处理消息时间比较,比如大于3秒,就认为卡顿了
     private boolean isBlock(long endTime) {
        return endTime - mStartTimestamp > mBlockThresholdMillis;
    }

//获取堆栈数据

protected void doSample() {
        StringBuilder stringBuilder = new StringBuilder();
        // 获取堆栈信息
        for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
            stringBuilder
                    .append(stackTraceElement.toString())
                    .append(BlockInfo.SEPARATOR);
        }

        synchronized (sStackMap) {
            // LinkedHashMap中数据超过100个就remove掉链表最前面的
            if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
                sStackMap.remove(sStackMap.keySet().iterator().next());
            }
            //放入LinkedHashMap,时间作为key,value是堆栈信息
            sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
        }
    }

以上两种方案,已经能解决大部分问题,面试官,我吹完了

面试官: 嗯,看来真做过卡顿监控,那你说的这两种方案都有弊端,你怎么监控TouchEvent和IdelHander的卡顿呢?

我: 啊… 天不早了,我得回家吃饭了,咱们下次再细聊…


下面针对Android 开发不同的技术专题领域,做了一些相对于的面试题整理,也查阅了不少参考学习文档,才得出的相应的参考答案,这里所展示出来的是一小部分的内容,如想参考全集可→:https://qr18.cn/CgxrRy


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

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

相关文章

Alchemy Catalyst 2023 Crack

Alchemy Catalyst 2023 Crack Alchemy CATALYST是一个可视化的本地化环境&#xff0c;支持本地化工作流程的各个方面。它帮助组织加快本地化进程&#xff0c;比竞争对手更快地进入新市场&#xff0c;并为他们创造新的收入机会。 创建全球影响力 高质量的产品和服务翻译对跨国组…

人才缺口将达 6.7 万人?半导体行业“后继无人”,美国危?

根据美国半导体行业协会&#xff08;SIA&#xff09;和牛津经济研究所&#xff08;Oxford Economics&#xff09;联合编制的一项研究报告指出&#xff0c;到2030年&#xff0c;美国半导体行业预计面临约6.7万名人才缺口。 根据预测&#xff0c;到2029年底&#xff0c;美国芯片行…

1.Flink概述

1.1 技术架构 应用框架层: 在API层之上构建的满足特定应用场景的计算框架&#xff0c;总体上分为流计算和批处理两类应用框架。API 层&#xff1a; Flink对外提供能力的接口 &#xff0c;实现了面向流计算的DataStream API和面向批处理的DataSet API。运行时层&#xff1a;Flin…

基于Java+SpringBoot+vue前后端分离小徐影城管理系统设计实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

标准IO函数练习

一、实现登录功能。 自定义一个usr.txt&#xff0c;手动输入账户密码&#xff0c;格式如下&#xff1a;账户 密码 例如&#xff1a; zhangsan 12345 lisi abcde wangwu abc123 需求如下&#xff1a; 从终端获取账户密码&#xff0c;与文件中的账户密码比较若终端输入的账户…

好看的思维导图模板有哪些?分享这几款

好看的思维导图模板有哪些&#xff1f;思维导图是一种非常实用的工具&#xff0c;可以帮助我们更好地组织和理解信息。然而&#xff0c;如果你想让你的思维导图看起来更好看&#xff0c;吸引人眼球&#xff0c;那么选择一个好的思维导图模板是非常重要的。 在众多的思维导图制作…

【STM32】 强大的 STM32Cube 生态 STM32CubeIDE 无伤速通

本文介绍的软件&#xff0c;均可以在ST官网st.com免费下载&#xff08;你需要注册登录&#xff09;&#xff0c;首选官网下载最新版本&#xff0c;如果有问题&#xff0c;可以在我的公众号回复&#xff1a;Cube&#xff0c;获取截止今日的最新版本软件安装包。 目录 一、STM32C…

传统域名与区块链域名的区别是什么?

区块链技术的发展&#xff0c;不仅带来了数字货币的兴起&#xff0c;还催生了一种新型的互联网域名——Web3 域名。Web3 域名作为一种新兴的域名体系&#xff0c;与传统的域名有着很大的区别。今天&#xff0c;我们就来探讨一下传统域名与区块链域名的区别。 首先&#xff0c;让…

查看docker容器启动参数

查看docker启动参数 1、查看docker容器的自启动策略2、查看docker容器的日志滚动清理策略 以下配置命令以redis容器为例 1、查看docker容器的自启动策略 docker inspect --format{{json .HostConfig.RestartPolicy}} redis输出的name是always 表示此容器是开机自启动的&#x…

蓝桥杯上岸必背!!!(第六期树与图的遍历)

第六期&#xff1a;树与图的遍历 &#x1f525; &#x1f525; &#x1f525; 蓝桥杯热门考点模板总结来啦✨ ~ 你绝绝绝绝绝绝对不能错过的常考树与图的遍历模板 &#x1f4a5; ❗️ ❗️ ❗️ 大家好 我是寸铁 &#x1f4aa; 祝大家4月8号蓝桥杯上岸 ☀️ 还没背熟模板…

2.1数据结构——线性表

一、定义 线性表是具有相同数据类型的n&#xff08;n>0&#xff09;个数据元素的有限序列&#xff0c;&#xff08;n表示表长&#xff0c;n0为空表&#xff09; 用L表示&#xff1a; 位序&#xff1a;线性表中的“第i个” a1是表头元素&#xff1b;an是表尾元素 除第一个…

2023 年牛客多校第三场题解

A World Fragments I 题意&#xff1a;给定两个二进制数 x , y x,y x,y&#xff0c;每次可以选择 x x x 二进制表达中的其中一位 b b b&#xff0c;然后执行 x ← x − b x \leftarrow x-b x←x−b 或 x ← x b x \leftarrow xb x←xb。问 x x x 最少经过多少次操作变成…

高速数据采集专家-FMC140【产品手册】

FMC140是一款具有缓冲模拟输入的低功耗、12位、双通道&#xff08;5.2GSPS/通道&#xff09;、单通道10.4GSPS、射频采样ADC模块&#xff0c;该板卡为FMC标准&#xff0c;符合VITA57.1规范&#xff0c;该模块可以作为一个理想的IO单元耦合至FPGA前端&#xff0c;8通道的JESD204…

第一启富金:现货黄金市场等待央行决议 非美商品‘弱不禁风’

第一启富金基本面分析&#xff1a; 中国纸黄金交易通显示&#xff0c;全球最大黄金上市交易基金(ETF)截至07月22日持仓量为919.00吨&#xff0c;较上日增持5.20吨&#xff0c;本月止净减持2.90吨。 在俄罗斯上周退出黑海谷物协议&#xff0c;摧毁了乌克兰通往基辅的一条出口路线…

PKCS#1: RSA加密时摘要值规范

RSA的加密和签名标准是类似的&#xff0c;加密时的摘要值结构如下 即非普通的hash摘要值&#xff0c;而是der编码结构的hash摘要值&#xff0c;用ASN1工具打开 可以快速的组装成一个der编码结构的摘要值&#xff0c;代码如下 ByteArrayOutputStream digestInfoValueBuf new By…

《向量数据库指南》:向量数据库Pinecone如何集成OpenAI

目录 嵌入式介绍 环境设置 创建嵌入 初始化Pinecone索引 填充索引 ⚠️警告 查询 本指南介绍了将OpenAI的大型语言模型(LLMs)与Pinecone(称为 OP stack )集成的方法,增强LLMs的语义搜索或“长期记忆”。此组合利用LLMs的嵌入和完成(或生成)端点,以及Pinecone的向…

MySQL 数据库约束

目录 一、数据库约束 1、约束类型 二、NULL 约束 三、unique 约束 四、default 约束 五、primary key 约束 自增主键 六、foreign key 外键约束 七、check 约束 一、数据库约束 我们使用数据库来存储数据&#xff0c;一般是希望这里存储的数据是靠谱的&#xff0c;…

追觅科技,拿什么撑起「百亿」野心?

作者 | 刘然 来源 | 洞见新研社 极度内卷的扫地机人赛道迎来了分叉路口。 从头部企业的最新动向&#xff0c;我们可以一览行业变化。 科沃斯选择多元化发展&#xff0c;大力拓展家电品类&#xff0c;推出了智能料理机、空气净化仪等产品&#xff1b;石头科技坚守爆品战略的同…

超详细,自动化测试allure测试报告实战(总结)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 allure可以输出非…

【Kubernetes运维篇】ingress-nginx实现业务灰度发布详解

文章目录 一、理论&#xff1a;实现灰度发布的几种场景1、场景一&#xff1a;将新版本灰度给部分用户2、场景二&#xff1a;按照比例流程给新版本3、实现灰度发布字段解释 二、实践&#xff1a;1、实验前提环境2、基于Request Header(请求头)进行流量分割3、基于Cookie进行流量…