写点什么

Flutter 线上代码覆盖率解决方案——FlutterCodeX

  • 2020-05-14
  • 本文字数:2596 字

    阅读完需:约 9 分钟

Flutter线上代码覆盖率解决方案——FlutterCodeX

背景

近年来,闲鱼旧业务在 Flutter 架构升级下,大量页面通过 Flutter 开发实现。业务不断迭代,包体积也随之增大,闲鱼 Android、iOS 安装包大小较去年有较大增加,其中,Flutter 在闲鱼包体积中占比 20%,闲鱼开发逐步需要考虑进行 Flutter 侧工程治理。Flutter 官方也在为包大小不断努力,致力于降低打包产物的大小,但仍未有成熟方案。因此现阶段,我们可以考虑如何将无效代码下线。


通过人工梳理的方式,依赖于开发人员的业务熟悉程度,难免疏漏。我们需要有准确的的线上代码覆盖率,作为数据依据,推动业务进行行之有效的代码下线。


本文为您介绍,Flutter 的线上代码覆盖率解决方案——FlutterCodeX。针对类级别编译时代码插粧,运行时后台数据聚合,进行数据采集上报,获得最终代码覆盖率数据,推动废弃业务下线,达到包体瘦身,对工程健康做长效监控与改善。


插桩方案探索

在线上代码覆盖率的统计中,问题的难点主要在于,如何准确判断类,是否被调用过?一般人会马上可以想到,只需要在每个类初始化时,加入一段代码,标记该类已经被调用,最快的就是构建函数中添加,但成本极高,有没有自动化、无侵入的插桩方案呢?以下从 iOS、Android、Flutter 不同的插桩方案进行简单的对比。


iOS


iOS 中,ObjC 首次调用类初始化时,+initialize 被执行,系统会自动标记已被调用,在 metaClass 的 data 的 flags 字段中的 1<<29 位的这个 bit RW_INITIALIZED,就记录着类是否 initialize。可以通过判断类是否被初始化,因此在运行时,找到合适的时机,遍历所有类,进行数据的聚合上传。


static BOOL MOCClassIsInitilatized(Class cls) {void*metaClass = (__bridge void*)object_getClass(cls);class_rw_t*rw = *(class_rw_t**)((uintptr_t)metaClass + 4* sizeof(uintptr_t));if(((class_rw_t*)((uintptr_t)rw & FAST_DATA_MASK))->flags & RW_INITIALIZED) {return YES;}return NO;}
复制代码


Android


Android 中,Java 语言可以不需要侵入原有代码,以添加静态代码块的形式添加插桩代码,buildscript 增加编译插件,在编译时遍历所有类文件进行代码插入即可。


publicclass A {static{// todo report class A initialize}}
复制代码


Flutter


Flutter 与 Android、IOS 的方案均有一定差异,Dart 没有 Java 的静态代码块,也没有类似 ObjC 的系统标记。在什么地方插桩,可以不侵入原有代码呢?


理论上,Dart Class 初始化执行顺序为:


  1. class variables initialize on declaration (no static)

  2. initializer list

  3. superclass’s constructor

  4. main class’s constructor


改写构造函数会直接侵入原有代码,Dart 构造函数的多样写法也增加了自动化插件的难度。因此改写构造器不是第一选择。根据初始化执行顺序,很快可以想到,是否可以增加新的类成员,初始化时调用插桩代码,以达到类初始化插桩的效果。例如


class A {bool isCodeX = ReportUtil.addCallTime('A');// ...biz}
复制代码


但在 Dart 中,针对拥有常量构建器的类,要求所有的成员均为 final,成员初始化必须在第 1 第 2 阶段,或构造函数入参进行初始化,即使是 extends、with 也强制要求子类及 Mixin 所有的变量均为 final。而 Flutter 中,Widget 等常用组件,均使用常量构建函数,无法通过这种形式插桩。


class A {final num x, y;const A(this.x, this.y);}
复制代码


注入代码的形式不可用!


还有其他办法吗?可不可以通过 AOP 的方式,hook 住所有的类构建器呢?而闲鱼技术团队刚刚开源的 AspectD,恰好可以解决这个问题。


AspectD 是针对 Dart 的 AOP 编程框架,通过 Transform 实现 dill 变换以实现 AOP,可以便捷地实现无侵入代码自由注入。


在 Flutter v1.12.13 下验证,针对常量构建器、无构建函数、命名为 ClassName.identifier 形式构建函数,均测试通过!AspectD 代码如下:


@Aspect()@pragma("vm:entry-point")classCodeXExecute{@pragma("vm:entry-point")CodeXExecute();
@Call("package:flutter_codex_demo/test.dart", "A", "+A")@pragma("vm:entry-point")void _incrementA(PointCut pointcut) { pointcut.proceed();// todo report class A initialize}}
复制代码


AspectD 原理不在此详细说明,有兴趣请移步https://github.com/alibaba-flutter/aspectd

整体方案设计

FlutterCodeX 线上代码覆盖率 SDK,由编译时代码插桩插件、运行时数据采集模块组成。



  • 代码插桩插件


编译时,通过 build_runner,CodeXGenerator 与 CodeAstVisitor 进行工程内所有类 ast 解析,遍历所有类构造函数,自动生成 AspectD 的 PointCut Execute 类文件,hook 类构建函数,在构造函数执行完毕后,插桩标记类调用信息,同时还生成项目的完整类列表至构建产物。关键代码如下:


CodeAstVisitor:
// visit all classvoid visitClassDeclaration(ClassDeclaration node) {SourceNode sourceNode = SourceNode(source_path, node.name?.name); node.members.forEach((ClassMember member) {// find all constructorif(member isConstructorDeclaration) {String constructorName = member.name?.name;if(constructorName == null|| constructorName.isEmpty) {// ClassName Constructor constructorName = sourceNode.name;} else{// ClassName.identifier Constructor constructorName = (sourceNode.name ?? '') + "\\."+ constructorName;} sourceNode.constructor.add(constructorName);return;}});
CodeXGenerator.collector.codeList[sourceNode.key()] = sourceNode;}
复制代码


AspectD Execute 如下图所示,类 A 拥有两个构造函数,生成两个 AspectD AOP 函数。



  • 运行时数据采集模块


运行时,工程中每个类初始化后将会自动调用 addCallTime 方法,将类调用信息缓存,选择用户退出后台的时机,进行数据文件进行压缩上传,目前我们采用阿里云 OSS 文件上传。根据应用活跃用户数,设置采样率,命中至少 5 万用户 UV。


  • 数据汇总与产出


最后,线上运行一段时间后,我们将数据汇总,与打包构建产物中的完整类列表进行比对,即可获得线上代码覆盖率数据,推动业务进行行之有效的瘦身。


以简单 Demo 工程为例:


说在最后

目前,FlutterCodeX 在闲鱼 App 即将上线,结合客户端 Android、iOS 代码覆盖率数据,有效地推动废弃业务下线,助力包体瘦身,对工程健康做长效监控与改善。


本文转载自公众号闲鱼技术(ID:XYtech_Alibaba)。


原文链接


https://mp.weixin.qq.com/s/VkjhTSKPpEZVYYsvcLsxCA


2020-05-14 14:062673

评论

发布
暂无评论
发现更多内容

鸿蒙生态逐光者图谱

最新动态

适用于FUJIFILM X和GFX富士相机的LUTS调色预设包 Earth LUT Pack

Rose

营销一体化如何精准构建智能营销生态,驱动生意持续增长

赛博威科技

人工智能 营销费用管理 赛博威

“所得”如何超越“所见”?合合信息扫描全能王发布扫描“黑科技”

合合技术团队

图像识别 OCR 合合信息 扫描全能王

软件测试学习笔记丨TestNG测试框架

测试人

软件测试

案例分享丨全球金融科技独角兽利用悦数图数据库保障金融交易安全

悦数图数据库

金融 图数据库

亚马逊商品详情的 API 接口获取与分析:挖掘商业价值的关键技术

Noah

FCPX插件:视频去闪烁消除频闪工具Remove Flicker 中文英文版

Rose

星途重启:244亿公里外的「旅行者1号」,修好了

DevOps和数字孪生

航天器 旅行者1号

Word文档内容自动生成PPT!这5款堪称办公神器的AI工具不要错过!

彭宏豪95

markdown PPT 在线白板 办公软件 AI生成PPT

打破生态「孤岛」,Catizen将开启Telegram小游戏2.0时代?

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

10个明亮通透粉色调摄影滤镜Lightroom预设

Rose

PS滤镜插件套装Nik Collection 6 for mac中文破解版

Rose

mac菜单栏应用管理软件:Bartender 4 for Mac 中文激活版

你的猪会飞吗

mac软件下载 mac破解软件下载

【干货】Vue3 组件通信方式详解

不在线第一只蜗牛

JavaScript vue.js 前端

FCPX插件:直线图形矩形线条路径动画预设

Rose

全球首部 Sora 生成宣传片《玩具反斗城的起源》亮相;钉钉全新 AI 小时搜索对所有大模型开放丨 RTE 开发者日报

声网

”翼”起向未来!天翼云亮相2024世界智能产业博览会!

天翼云开发者社区

人工智能 云计算 智算中心

公平发售平台开发:重塑代币发行的未来

开发丨飞机丨 @aivenli

Flutter线上代码覆盖率解决方案——FlutterCodeX_开源_君爱_InfoQ精选文章