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

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

面试官: 自我介绍下

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

我: 面试官你好,我叫**,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


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

甄云质量管理解决方案——插上数字“羽翼”,实现PPAP落地

导语 质量管理是供应链管理中非常重要的一环&#xff0c;通过制定和开展质量相关的活动&#xff0c;确保产品符合质量标准和一致性要求。质量管理水平的高低直接影响供应链的整体效率和最终效益&#xff0c;关乎企业形象和品牌声誉&#xff0c;已然是企业的生命线。 作为采购…

ES6:Object.assign方法详解

ES6&#xff1a;Object.assign方法详解 1、前言2、语法3、基本用法3.1 目标对象和源对象无重名属性3.2 目标对象和源对象有重名属性3.3 有多个源对象3.4 其他情况3.4.1 只有一个参数时&#xff0c;Object.assign会直接返回该参数3.4.2 如果该参数不是对象&#xff0c;则会先转成…

el-table 表头设置渐变色

<el-table :data"tableData" stripe><el-table-column prop"name" label"测试" align"left"></el-table-column><el-table-column prop"code" label"测试1" align"left"></…

科技云报道:边缘云赛道开启,谁能成为首个“出线”厂商?

科技云报道原创。 每一轮底层技术变革&#xff0c;都会带来全新的商业机遇。随着万物智联时代到来&#xff0c;大量数据产生的源头由传统的中心化向分散数据源变革&#xff0c;越来越多云边协同场景的出现&#xff0c;使得边缘云成为计算领域数据处理的新范式之一。 自2020年…

OR-Tools工具安装(Python-Vs code)-自用

安装&#xff08;已安装python以及Vs code&#xff09; pip安装 python -m pip install --user ortools安装完成示意如下&#xff1a; 验证安装 python -c "import ortools; print(ortools.__version__)"输出结果为版本号

网络:TCP/IP协议

1. OSI七层参考模型 应用层 表示层 会话层 传输层 网络层 数据链路层 物理层 2. TCP/IP模型 应用层 传输层 网络层 数据链路层 物理层 3. 各链路层对应的名称 应用层对应的是协议数据单元 传输层对应的是数据段 网络层对应的是数据包 链路层对应的是数据帧 物理层对应的是比特…

Mysql 简介

Mysql 简介 学习目的 MySQL作为目前最流行的关系型数据库管理系统之一&#xff0c;因其开源免费的特性&#xff0c;成为小型Web应用的重点关注对象。几乎所有的动态Web应用基本都在使用MySQL作为数据管理系统。学习MySQL的目的也是为了更好地理解数据库相关的SQL注入漏洞&…

uniapp实战

上面是tab栏&#xff0c;下面是swiper&#xff0c;&#xff0c;tab和swiper和 红色滑块 动态变化&#xff0c;&#xff0c; 遇到的问题&#xff1a; 往下滚动 tab栏 吸顶&#xff1a; position:sticky; z-index:99; top:0;swiper切换触发 change 事件&#xff0c; :current …

【Unity2D】相机移动以及设置相机边界

添加相机 添加相机时&#xff0c;首先需要在unity中添加 Cinemachine 包 第一次使用这个包时&#xff0c;需要在Package Manager中搜索并安装 安装Camera Mechine包后&#xff0c;添加2D Camera 设置跟随对象为Ruby &#xff08;从Hierarchy中将Ruby拖动到Follow中&#xff0…

UE5 关于MRQ渲染器参数

最佳参数&#xff1a; Spatial Sample Count&#xff1a;使用奇数样本时临时抗锯齿会收敛 Temporal Sample Count&#xff1a;超过2之后&#xff0c;采样过大会造成TAA效果不佳 TSR&#xff1a;UE5最好的抗锯齿方案

AcWing241. 楼兰图腾(树状数组)

输入样例&#xff1a; 5 1 5 3 2 4输出样例&#xff1a; 3 4解析&#xff1a; 以某个点 i 为最低点的 V 的数量&#xff0c;为 i 左侧和右侧比 a[ i ] 大的数量 a&#xff0c;b 的乘积。 但是&#xff0c;直接求这两个数的复杂度为O(n)&#xff0c;则整个复杂度为O( n^2 )&am…

js基础-练习三

九九乘法表&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthsc, initial-scale1.0"><title>九九乘法表</title><style&g…

可解释的 AI:在transformer中可视化注意力

Visualizing Attention in Transformers | Generative AI (medium.com) 一、说明 在本文中&#xff0c;我们将探讨可视化变压器架构核心区别特征的最流行的工具之一&#xff1a;注意力机制。继续阅读以了解有关BertViz的更多信息&#xff0c;以及如何将此注意力可视化工具整合到…

论文笔记--Distilling the Knowledge in a Neural Network

论文笔记--Distilling the Knowledge in a Neural Network 1. 文章简介2. 文章概括3 文章重点技术3.1 Soft Target3.2 蒸馏Distillation 4. 文章亮点5. 原文传送门 1. 文章简介 标题&#xff1a;Distilling the Knowledge in a Neural Network作者&#xff1a;Hinton, Geoffre…

iOS 单元测试之常用框架 OCMock 详解

目录 前言&#xff1a; 一、单元测试 1.1 单元测试的必要性 1.2 单元测试的目的 - 约束条件是否通过形式参数来传送。 1.3 单元测试依赖的两个主要框架 二、OCMock 的集成与使用 2.1 OCMock 的集成方式 2.2 OCMock 的使用方法 2.3 mock 使用限制 前言&#xff1a; OC…

IDEA+SpringBoot + Mybatis + Shiro+Bootstrap+Mysql资产设备管理系统

IDEASpringBoot Mybatis ShiroBootstrapMysql资产设备管理系统 一、系统介绍1.环境配置 二、系统展示1. 管理员登录2.用户新增3.用户设置4.岗位管理5. 审批节点6. 人员查询7. 组织设置8. 人员调整9.角色设置10.角色模块映射11.模块设置12.应用模块13.光纤交换机14.服务器15.网…

从实践彻底掌握MySQL的主从复制

目录 一、本次所用结构如图---一主多从级联&#xff1a; 二、IP。 三、配置M1&#xff1a; 四、从库M1S1&#xff1a; 五、从库M2配置&#xff1a; 六、 从库M2S1&#xff1a; 一、本次所用结构如图--- 一主多从级联&#xff1a; 二、IP。这里M1S1和M1S2一样的&#xff0…

hack the box—Lame

扫描 还是老方法nmapfscan得到开放的端口和服务 nmap -sV -sC -sT -v -T4 10.10.10.3 看到开了445&#xff0c;先来波ms17-010&#xff0c;发现失败。 这里还开个21&#xff0c;并且可以知道版本号&#xff0c;直接搜索ftp漏洞 msf正好有对应的模块 设置好参数后进行攻击&…

Hadoop 集群如何升级?

前言 本文隶属于专栏《大数据技术体系》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见大数据技术体系 正文 升级 Hadoop 集群需要细致的规划&#xff0c;特…

【设计模式】单例设计模式详解(包含并发、JVM)

文章目录 1、背景2、单例模式3、代码实现1、第一种实现&#xff08;饿汉式&#xff09;为什么属性都是static的&#xff1f;2、第二种实现&#xff08;懒汉式&#xff0c;线程不安全&#xff09;3、第三种实现&#xff08;懒汉式&#xff0c;线程安全&#xff09;4、第四种实现…