作者 | 阿里巴巴文娱高级无线开发工程师 孙珑达

责编 | 屠敏

背景

为了适应产品的快速迭代,通常大量的研发资源会投入在新功能的开发上,而针对无用功能的治理却很少被关注。随着时间的推移,线上应用会积累大量的无用代码,再加上人员更迭以及功能交接,治理无用代码的成本越来越高。最终应用安装包过大,导致应用下载转化率降低、应用平台上架受限(例如超过100M的应用不能上架谷歌商店)、研发效率降低等等。

如何治理无用代码?首先是代码静态扫描。对于Android应用,ProGuard工具可以在构建阶段静态分析代码引用关系,自动裁减掉没有被引用到的代码,减少安装包大小。

当然,只有代码静态扫描是不够的,因为它不能代表线上用户实际的使用情况,所以还需要一套线上用户代码覆盖率的统计方案。

下面我将从Android应用的线上代码覆盖率统计切入,分享优酷的无用代码治理的技术思考和落地方案。

传统采集方案

首先,在需要统计的代码处加上统计代码。当代码被执行时,进行统计和上报。应用的代码行数通常都数以万计,手动添加显然是不现实的,所以一般会在构建阶段通过面向切面编程(AOP)来插入统计代码(以下简称为插桩),可以借助一些成熟的AOP中间件完成,例如,Jacoco、ASM。

其次,需要思考是,我们期望采集的粒度是什么?一般来说,粒度从细到粗分为:指令、分支、方法、类级别,粒度越细,代码覆盖率结果越准确,但性能损耗也越大。例如,如果想采集的粒度为指令级别,就需要对每个指令进行插桩,但这种插装会导致指令数也翻倍,安装包增大并且运行时性能下降。

优酷曾尝试过用Jacoco进行分支粒度的插桩,当时希望覆盖尽量多的用户,因为覆盖的用户越多结果越准确。但经测试,此方案使安装包增大10M,运行时性能严重恶化,果断放弃了此方案。

为了权衡性能和采集粒度,目前我们一般都采取类级别粒度的插桩,一方面是因为这样对性能影响较小,另一方面过细的采集粒度反而会加重业务方治理的难度。但此方案还不够完美:

1) 运行时性能:当类首次加载时会执行统计代码,App启动过程会加载成千上万个类 ,会对启动性能造成一定影响;

2) 包大小:有多少个类,就会插入多少行统计代码,对于像优酷这种大型App,也会增加不少的安装包大小;

3) 构建耗时:因为构建过程中需要对每个类进行插桩,增加了构建耗时;

新采集方案—SlimLady

▐  目标

优酷希望有一套方案可以无损地采集线上代码覆盖率,核心目标如下:

  1. 运行时性能:无任何影响;

  2. 包大小:无任何影响;

  3. 构建耗时:无任何影响;

▐  实现

通过研究源码,发现可以通过动态查询DVM虚拟机已加载类的信息来获取类级别的代码覆盖率,下图中“覆盖率采集”部分即为SlimLady采集的原理图,这里我们只关注这部分,其他部分将在后面的整体方案中进行讲解。

ClassTable

Java虚拟机规范规定,类在使用前要先被虚拟机加载。Android中,是通过ClassLoader来完成类加载的,最后保存在Native层的ClassTable中,所以如果我们获取了所有ClassLoader的ClassTable对象,就有可能判断出虚拟机加载了哪些类。

首先,获取所有的ClassLoader对象。对于APK中的类,如果无特殊声明,一般都会被默认的PathClassLoader加载;对于动态加载的类,需要在自定义的ClassLoader中加载,例如Atlas会为每个Bundle创建一个相应的ClassLoader,通过这个ClassLoader来加载Bundle中的类。一旦明确了App中用到了哪些ClassLoder,获取是易如反掌的

其次,通过ClassLoader来获取ClassTable的对象的地址。通过Java层ClassLoader类的源码可知,ClassLoader有一个成员变量classTable(7.0及以上版本),这个变量保存了Native层ClassTable对象的地址,我们可以通过反射获取这个地址:

ClassLoader classLoader = XXX;
Field classTableField = ClassLoader.class.getDeclaredField("classTable");
classTableField.setAccessible(true);
long classTableAddr = classTableField.getLong(classLoader);

但在9.0的系统中成员变量classTable被加入了深灰名单,限制了直接反射,需要通过系统类进行反射绕过此限制:

ClassLoader classLoader = XXX;
Method metaGetDeclaredField = Class.class.getDeclaredMethod("getDeclaredField", String.class);
Field classTableField = (Field) metaGetDeclaredField.invoke(ClassLoader.class, "classTable");
classTableField.setAccessible(true);
long classTableAddr = classTableField.getLong(classLoader);

至此,我们就获得了所有的ClassTable对象的地址,里面保存了全部的类加载信息。

类名列表 

通过阅读源码发现ClassTable有个方法可以通过类名查询类是否被加载过(下节将详细介绍),这样我们只需要获得所有类名的列表,再调用那个方法,即可以判断类是否被加载过。

APK中的类名列表可以通过DexFile进行获取,如下:

List<String> classes = new ArrayList<>();
DexFile df = new DexFile(context.getPackageCodePath());
for (Enumeration<String> iter = df.entries(); iter.hasMoreElements(); ) {classes.add(iter.nextElement());
}

同理,动态加载的类也可以通过DexFile获取;

类是否被加载

通过阅读源码发现class_table.cc中,ClassTable有个Lookup方法,传入类名和类名的hash值,返回类对象的地址,如下:

mirror::Class* ClassTable::Lookup(const char* descriptor, size_t hash)

如果返回值为nullptr,说明没有加载过此类,否则,说明加载过。

mirror::Class* ClassTable::Lookup(const char* descriptor, size_t hash)

获取此方法地址的方法:

  1. 加载so:class_table.cc在libart.so中,所有我们需要用dlopen加载libart.so获得此so的handler。其实在加载前,libart.so在当前进程一定已经被加载过了,此次加载只是为了获得handler,并不耗时;

  2. 符号表:通过readelf查询Lookup的符号:_ZN3art10ClassTable6LookupEPKcj;

  3. 方法指针:调用dlsym,传入handler和符号表,即可以找到Lookup方法的地址;

注:从7.0系统开始,Google禁止了调用系统Native的API,这里我们通过/proc/self/maps找到libart.so的地址,将里面的符号表进行拷贝,进而绕过此限制;

至此,我们就可以通过调用ClassTable的Lookup方法,传入类名和hash值,判断类是否被加载过了。

总结

这样,我们就能知道某一时刻有哪些类被加载过,对其上传,进行聚合和处理,再通过对比所有类名列表,就能得到代码覆盖率数据了。此方案不需要插桩,所以可以无损地采集覆盖率。

新方案整体设计

上面提到的采集方案是整个方案的核心,除此之外还有上下游的配套流程,整体方案的设计如下图:

1) APK分发:通过构建中心构建出最新的APK,分发给用户;

2) 触发采集:用户安装应用,在使用过程中,将APP退后台10s后,通过采样率计算是否命中,若命中,则触发代码覆盖率采集

3) 配置下发:在需要时,可以通过配置中心下发配置来动态调整功能开关、采样率等配置;

4) 数据采集:代码覆盖率采集中间件(SlimLady)统计出被加载的类,将已加载的类名保存在文件中,进行压缩,将压缩后的数据传给上传中间件;

5) 数据上传:上传中间件将数据上传到云端;

6) 数据下载:服务器定期对云端数据进行下载;

7) 类信息提供:服务器从构建中心获取类信息,包括所有类名列表和混淆文件;

8) 数据解析:服务器按版本对代码覆盖率数据进行解压、反混淆、聚合统计,聚合统计后的结果包含了被加载过的类及次数,与所有类名列表进行对比,即可以知道哪些类没有被加载过,将结果保存至数据库;

9) 结果聚合:网页端从数据库读取聚合结果,按模块展示代码覆盖率、模块热度、模块大小等信息。

总结

本方案突破了传统的插桩埋点统计,动态获取虚拟机信息,无损地采集代码覆盖率。有了代码覆盖率数据,能做的治理有很多,例如:下线无用代码、模块;瘦身或下线调用低频、体积大的模块;在集成阶段添加代码覆盖率卡口等等。

【End】

推荐阅读 

☞深度学习“三巨头”、图灵奖得主 Yann LeCun:我没有天赋,所以才追随聪明人

☞64% 的企业未实现智能化,5 成公司算法工程师团队规模小于 10人,AI 工程师的机遇在哪里?

☞2020年,5种将死的编程语言

☞抗住百万人直播、被联合国推荐,起底飞书技术演进之路!

☞还不知道 AWS 是什么?这 11 个重点带你认识 AWS !

☞智能合约编写之 Solidity 的设计模式

你点的每一个在看,我认真当成了喜欢

和无用代码说再见!阿里文娱无损代码覆盖率统计方案相关推荐

  1. 阿里文娱技术专家战獒: 领域驱动设计详解之What, Why, How?

    战獒 阿里文娱技术专家 读完需要 8 分钟 速读仅需 3 分钟 阿里妹导读:什么是领域驱动设计?传统分层架构在实际开发中存在哪些问题?业务开发人员如何设计并搭建自己的领域模型?阿里文娱技术专家战獒将为 ...

  2. 阿里文娱实战 | 小而美的 egg-react-ssr 开源实现方案

    作者|  阿里文娱前端工程师 十忆 责编 | 夕颜 封面图 | CSDN付费下载自视觉中国 出品 | CSDN(ID:CSDNnews) 本文基于优酷 PC/H5 业务改造的经验心得,提炼出了一套基础 ...

  3. 从 Ops 到 NoOps,阿里文娱智能运维的关键:自动化应用容量管理

    作者|  阿里文娱高级开发工程师 金呈 责编 | 夕颜 出品 | CSDN(ID:CSDNnews) 概述 1. 背景 随着业务形态发展,更多的生产力集中到业务创新,这背后要求研发能力的不断升级.阿里 ...

  4. 面对万人世界军人运动会票务,阿里文娱 Dpath 如何保障稳定性?

    作者|  阿里文娱技术专家 司楚 责编 | 夕颜 出品 | CSDN(ID:CSDNnews) 背景 第七届世界军人运动会是中国第一次承办综合性国际军事赛事,有 100 多个国家.一万多现役军人参与竞 ...

  5. 阿里文娱测试实战:机器学习+基于热度链路推荐的引流,让对比测试更精准

    作者 | 阿里文娱测试开发专家 正辰 出品 | CSDN(ID:CSDNnews) 对比测试的原理和现状 引流对比测试是目前阿里内部常用的一种回归测试手段,它基于线上真实流量做采集.回放.对比,通过对 ...

  6. 看看阿里文娱怎么建设开放平台,这就是专业~

    大家好,我叫大鸡腿,大家可以关注下我,会持续更新技术文章还有人生感悟,感谢~ 前言 哥们哥们,开放平台了解过吗? 没有搭建过开放平台,也接过第三方api,比如说阿里ocr.支付宝支付接口,那个平台就叫 ...

  7. 从Ops到NoOps,阿里文娱智能运维的关键:自动化应用容量管理

    作者|  阿里文娱高级开发工程师 金呈 编辑 | 夕颜 来源 | CSDN(ID:CSDNnews) 概述 1. 背景 随着业务形态发展,更多的生产力集中到业务创新,这背后要求研发能力的不断升级.阿里 ...

  8. 阿里文娱搜索算法实践与思考

    作者:若仁,来自:DataFunTalk 导读:视频搜索是涉及信息检索,自然语言处理 ( NLP ),机器学习以及计算机视觉 ( CV ) 等多领域的综合应用场景,随着深度学习在这些领域的长足进展以及 ...

  9. 深度学习核心技术精讲100篇(五十四)-阿里文娱多模态视频分类算法中的特征改进

    前言 类目体系是视频网站运营中的重要工具,也是推荐算法中提升冷启效果的重要手段.因此一套设计合理.准确率.覆盖率高的基础类目必不可少.阿里文娱类目体系建设团队与运营.审核一起建立的一二级类目体系,目前 ...

最新文章

  1. 《css世界》的那些实用技巧
  2. 制药行业SAP项目里的那些MES系统
  3. Selenium3自动化测试——16. 处理HTML5视频播放
  4. formal method online checking tool
  5. Android 第三章 SQLite 数据库
  6. hyperion卫星重访时间_观摩卫星发射|2020第四届全球物联网大会上让我们一起去“放星”...
  7. bp matlab预测结果差,BP神经网络 预测 精度差
  8. matlab中制作软件,如何用matlab制作一个小软件
  9. 遗传算法优化SVM支持向量机分类预测的参数代码模型
  10. 局域网电脑互访的设置大全
  11. 用python批量处理图片_Python学习九十三天:Python批量处理图片
  12. bzoj1805: [Ioi2007]Sail 船帆
  13. panda经典四道题期末考核分享
  14. 白天不去搜寻痛苦,但晚上却不能抑制
  15. 【解决】无法为JSP编译类: Enumeration cannot be resolved to a variable
  16. API汇总;API大全(转载)
  17. 无需MS Office创建Excel!再C ++中以编程方式在Excel电子表格中创建图表
  18. 中文编程书籍资料一览表
  19. JSON对象数组去重
  20. Linux发行商,能否齐步走?

热门文章

  1. 跨域-(jsonp cors)
  2. 第一百七十三节,jQuery,Ajax
  3. Firefox-常用扩展
  4. linux 下Eclipse for C/C++的不常见设置
  5. TortoiseSVN更改账号的方法
  6. [引]Visual C# 开发中心 - C# 编程工具
  7. 网格系统常用的三种比例
  8. python怎么查看安装了哪些库_如何查看Python 安装位置以及已经安装的库
  9. 《统计学习方法》—— k近邻方法、kd树以及python3实现
  10. Flutter实战一Flutter聊天应用(二)