专栏首页京东技术详解Android UI线程卡顿收集

详解Android UI线程卡顿收集

作者:段云飞

京东前台产品研发部-资深Android工程师,主要负责手机京东Android端图片框架,性能优化,性能数据收集,对Android Framework、App性能优化有深入研究。

1、整体概述

1.1背景

  • 我所在的平台化技术组致力于打造阿凡达开放平台,通过全面的技术解决方案及完善的支撑系统,为业务开发提供便捷的一站式服务,并将手机京东多年来积累技术能力输出到各个京东体系的各个应用中。其中性能监控分析技术是为APP质量的护卫舰,本文要讲解的卡顿收集系统就为性能监控的一部分。
  • 经过多年的技术积累,我们发现因为业务场景复杂、版本迭代快、历史代码庞大、包含各种第三方库等客观原因,很多大型的Android项目都面临着同样的问题——卡顿
  • 当App出现卡顿的时候,即使定位到具体界面,动辄数千行的代码夹杂着各种业务逻辑,也很难定位到底是哪里出现了问题,结果就是客户端越来越卡,恶性循环。

大型项目遇到的现状:

1.2卡顿因素

导致卡顿的因素有很多,常见有:

  • UI线程中的耗时操作
  • 复杂、不合理的布局以及过度绘制
  • 内存使用异常导致频繁GC
  • 错误的异步实现

以上四条中,最主要的卡顿原因为UI线程中执行耗时操作。 我们也一直在研究,在不影响京东App性能的前提下,完美的实现一个UI线程卡顿监控系统。该系统能够监控线上用户的卡顿,上报卡顿数据,数据聚合,根据聚合结果自动生成工单,将工单发给对应模块的负责人。

希望实现的效果为:

  • 非侵入式,不需要到处打点,破坏代码的优雅性
  • 精准定位,直接定位到行
  • 不影响App性能

1.3卡顿监控系统整体结构

整个系统分成4部分:

  1. 主线程卡顿采集SDK
  2. 性能数据上报SDK
  3. 服务端收集到数据后,进行数据聚合
  4. 自动产生工单,发送给对应的工程师

卡顿监控系统结构图:

2、主线程卡顿收集SDK实现

2.1 监控原理

1.主线程只有一个looper

Looper.java的源码可以看到,定义了一个静态变量sMainLooper,主线程无论有多少个Handler,但只有这一个looper存在,主线程中执行任何代码都会回到loop()函数中。

Looper.java的loop():

public static void loop() {
    ...    

    for (;;) {
        ...
        Printer logging = me.mLogging;        
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target);
        }
        msg.target.dispatchMessage(msg);        
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target);
        }
    }

    ...
}

就是这个mLogging,它在每个message处理的前后被调用,而如果主线程发生卡顿,是在dispatchMessage里执行了耗时操作。

2.将主线程的Printer替换

可以看到Looper中的Printer是可以替换的,谢谢Google大牛们目前留了接口,不过仔细想想,即使没有留接口也可以使用反射替换掉。

替换接口:Looper.getMainLooper().setMessageLogging(printer);

3.卡顿条件(endTime-startTime > 卡顿阈值)

Printer在每一个message执行前和执行后成对的调用,就可以知道消息开始时间startTime,以及消息的结束时间endTime。如果endTime-startTime > 卡顿阈值,则认为该条消息执行过长,主线程卡顿。

4.采样

在执行主线程的同时,采样线程要对主线程的堆栈、cpu等信息进行周期采样。执行前采样线程要先休眠一段时间,这样做的主要原因就是为了不对主线程的短消息(即执行时间很短的message)进行干扰,避免抢夺cpu资源,降低对性能的影响。

采样示意图:

2.2 卡顿监控核心流程图

  • 采样线程:每隔一段时间就会采样一次,会产生大量的临时对象,所以采样过程中要控制采样对象个数,这里我用链表实现了一个轻量级的对象池,实现原理参考的Android系统控制Message数量的方案。
  • 主线程:发生卡顿后需要从采样线程的对象池中将属于T2-T1时间段的堆栈等采样信息保存起来,将数据传递给缓存池。
  • 缓存池:即内存缓存,实现一个定时器,每隔固定时间就去检查是否符合上报条件,如果符合就进行数据上报。

2.3 数据的处理

1、 数据分成两类:

  • 确认卡顿:两个相邻的采样时间间隔,取出的主线程堆栈是完全一致的。这种情况就划分到确认卡顿范畴,因为可以明确的判断,该函数在两个时间间隔内还没有执行完。
  • 疑似卡顿:没有相邻间采样间隔堆栈相同,这种属于疑似卡顿。

2、 堆栈预处理:

  • 数据初步聚合:在采样时,假如两个相邻间隔的堆栈完全一致,只需定义一个count字段,然后把count+1,服务端会根据这个count字段来确定本次采样该堆栈花费了多少个时间间隔。这样可以减少重复堆栈缓存,降低内存和流量消耗。
  • 关键行:客户端会过滤堆栈内容,找到带有jd或者jingdong包名的代码行,标记为关键代码行,将关键行做为数据聚合的依据,这样做可以大大减轻服务端的数据聚合压力。

3、数据收集策略和展示

  • 收集策略:策略包含App版本号、Build号、Android系统、开关灰度比例、不同网络环境(2G,3G,4G,WIFI)、数据是否实时上报等。可以自由组合,精确控制覆盖范围。
  • 精准匹配:可为某些特殊用户单独开启,比如该用户一直投诉页面卡顿,可以精确控制只让该用户开启此功能。
  • 数据展示:部分数据聚合结果如下图

2.4 开发过程中遇到的问题

1、 Printer替换:由于任何一个模块都可以设置主线程的Printer,测试时发现,经常有其他未知模块会替换掉主线程的Printer,最典型的就是WebView类,WebView类中有一个setWebContentsDebuggingEnabled()函数,无论设置true还是false,都会将线程的Printer替换掉。解决办法是设置一个暗门,等h5开发同事真正需要调试时,触发暗门,调用setWebContentsDebuggingEnabled,默认不调用。

2、 getMainPrinter():Looper中并没有提供获得主线程Printer的方法。解决办法,通过反射Framework层代码获得,代码如下:

/**
 * 反射获得主线程的Printer对象
 * @return
 */

private static Printer getMainPrinter(){
   try {
       Field privatePrinterField =Looper.class.getDeclaredField("mLogging");
       privatePrinterField.setAccessible(true);
       Looper mainLooper = Looper.getMainLooper();
       Printer oldPrinter = (Printer) privatePrinterField.get(mainLooper);//获得私有字段值
       if (oldPrinter != null){
           return oldPrinter;
       }
    } catch (Exception e) {
        e.printStackTrace();
    }    return null;
}

3、总结

卡顿收集属于我们APM监控系统的重要组成部分,收集到的卡顿数据样本越大越准确,目前手机京东App使用此系统,每天接收到用户卡顿数据上百万条,能够精准定位卡顿用户以及卡顿原因,再通过大数据的聚合,可以基本掌握每个版本的卡顿情况。

当然,卡顿数据收集只是整个工作的第一步,数据收集到服务端后,还需要QA,测试,业务研发等同学一起努力优化,才能真正的降低卡顿率。

本文分享自微信公众号 - 京东技术(jingdongjishu),作者:段云飞

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-03-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【解析】京东线下生态基础设施解决方案

    京东技术
  • 警告:Android P(禁用非官方API)

    警告:Android P(禁用非官方API) ? 1 作者介绍 李俊涛 京东前台产品研发部-资深Android工程师 看雪论坛 Android安全小组成员 前言...

    京东技术
  • Kotlin京东业务实战 | 强大又简洁的JVM语言

    9年以上开发经验,熟悉主流移动开发框架,热衷于探索新技术,曾负责过店铺、JDReact架构等项目的开发工作,目前专注于京东App领券中心频道开发,以及移动端技术...

    京东技术
  • 《广研Android卡顿监控系统》

    实现背景 应用的使用流畅度,是衡量用户体验的重要标准之一。Android 由于机型配置和系统的不同,项目复杂App场景丰富,代码多人参与迭代历史较久,代码可能会...

    腾讯Bugly
  • Android卡顿监控系统

    Android 由于机型配置和系统的不同,项目复杂App场景丰富,代码多人参与迭代历史较久等,实际测试时候也会偶尔发现某些业务场景发生卡顿的现象...

    likunhuang
  • 微信iOS卡顿监控系统

    引子 微信 iOS 团队在值班的时候,时不时会收到这样的卡顿反馈:“用户A 刚才碰到从后台切换前台卡了一下,最近偶尔会遇到几次”、“用户B 反馈点对话框卡了五六...

    微信终端开发团队
  • Hexo+github搭建个人博客环境配置和发布(图文详解)

    上一篇博文 《Hexo+github搭建个人博客-环境搭建篇》 中,我们讲解了利用Hexo搭建个人博客应该要配置哪些环境。 相信大家已经迫不及待的想要知道接下来...

    好好学java
  • 币聪早报:比特币哈希率在单日内突破1000万TH/s,比特大陆S11开启测试?

    尽管最近比特币价格走势疲软,但比特币挖掘的网络哈希率在过去两天突然大幅上涨。这种增加使其达到令人难以置信的每秒6200万terahashes。仅仅两天前,该网络...

    币聪财经
  • GitHub 标星10k+,新型冠状病毒(2019-nCoV)最全的数据集在这里!

    新型冠状病毒(2019-nCoV)突然降临武汉,随着春运大潮,逐渐扩散到全国各省份。这让原本应该是热热闹闹的春节,一下子气氛冷到冰点,感觉空气中都带着恐怖的气息...

    猴哥yuri
  • Python 自由定制表格的实现示例

    很多开发者说自从有了 Python/Pandas,Excel 都不怎么用了,用它来处理与可视化表格非常快速。

    砸漏

扫码关注云+社区

领取腾讯云代金券