专栏首页京东技术详解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热修复框架之优逆势分析(Hotfix)

    Android平台出现了一些优秀的热更新方案,主要可以分为4类: 基于Instant Run 热插拔方案:美团的Robust(实时修复)   Robust插件对...

    用户1155943
  • 小程序来了

    2017年1月9日,小程序如约而至。 小程序究竟该如何获取?首先,你需要将微信更新至iOS6.5.3版本或Android6.5.3版本,其次,你得尝试使用一个小...

    前朝楚水
  • realtime 库和框架概览

    Websocket 库 现在大部分新版浏览器、Android、IOS 都已经支持了 Websocket,直接使用 Websocket 问题不大,那么 Webso...

    dys
  • DevOps平台实践落地之构建管理详解

    ? 企业做DevOps平台,本质上是做企业的IT生产线,最终是实现整个企业级的数字化生产线。构建作为落地DevOps平台必不可少的环节之一,是持续集成、交付和...

    yuanyi928
  • 移动开发的跨平台实践及在企业中的应用

    目录: 一、移动跨平台已成为必然 二、驱动原生是移动跨平台的最佳选择 三、以工程化的形式解决移动跨平台问题 四、普元在企业移动跨平台上的优秀实践 五、总结与展望...

    yuanyi928
  • 干货 | 沪江高级安卓工程师徐宜生:移动端APM性能监控

    徐宜生,现任沪江高级Android开发工程师,著有《Android群英传》《Android群英传:神兵利器》两书,CSDN博客专家,慕课网Android讲师,活...

    IT大咖说
  • 实战:详解猫眼电影小程序开发过程

    作者:辉仔,Android开发工程师,目前主要涉及移动端APP的开发,Github地址:https://github.com/hundredays。 责编:陈...

    智能算法
  • Android热修复技术原理详解(最新最全版本)

    本文框架 什么是热修复? 热修复框架分类 技术原理及特点 Tinker框架解析 各框架对比图 总结   通过阅读本文,你会对热修复技术有更深的认知,本文会列出各...

    用户1155943
  • 【小教程】教你拿出app中的图片资源

    有一种方法通过ipa或apk包来获得每个资源的切图,今天就来简单介绍下。因为有的同学还不知道这个方法,尤其是没有技术背景的同学(我司UI妹子得知这个方法如获至宝...

    前朝楚水
  • OpenCV3.2集成Android Studio2.2开发配置

    主要知识点 OpenCV4Android3.2 SDK介绍与下载 AS2.2与OpenCV3.2集成 程序演示与代码 AS集成OpenCV3.2配置常见错误 O...

    OpenCV学堂

扫码关注云+社区

领取腾讯云代金券