阿里云「飞天发布时刻」2024来啦!新产品、新特性、新能力、新方案,等你来探~ 了解详情
写点什么

滴滴 DoKit For Flutter 正式开源,功能及核心实现解读

  • 2021-01-05
  • 本文字数:4569 字

    阅读完需:约 15 分钟

滴滴DoKit For Flutter正式开源,功能及核心实现解读

DoKit For Flutter 是一个 DoKit 针对 Flutter 环境的产研工具包,内部集成了各种丰富的小工具,UI、网络、内存、监控等等。


Github地址


Pub仓库地址


操作文档



Flutter 是 Google 开源的跨端技术框架。凭借其区别于 RN/Weex 的自渲染模式,在社区里引起了广泛关注,不管是终端还是前端的小伙伴都趋之若鹜,大有一统大前端江湖的气势。而国内大厂如闲鱼、字节、美团等,也都在其核心业务上完成了落地。


早在两年前,滴滴就有多个内部团队开始在 Flutter 领域进行尝试。但是在开发过程中,我们遇到了很多调试性问题,如日志、帧率、抓包等。为了解决这些开发测试过程中遇到的各类问题,DoKit 团队联合滴滴代驾和货运团队,把平时工作过程中沉淀下来的效率工具进行业务剥离和脱敏,并最终打造出 DoKit For Flutter,在服务内部业务的同时,也为社区贡献一份力量。


那么接下来就让我来列举一下 DoKit For Flutter 的功能以及核心实现。

工具详解

基本信息


基本信息模块会展示当前 dart 虚拟机进程、CPU、Flutter 版本信息、当前 App 包名和 dart 工程构建版本信息。



VM 信息通过VMService获取。Flutter 版本实际上是通过 Devtools 服务注入的"flutterVersion"方法获取到的,在 flutter attach 后,本地会起一个 websocket 服务,连接 VMService 并注入 flutterVersion 和其余方法(HotReload、HotRestart 等),通过 VMService 调用 flutterVersion 方法,会从本地 flutter sdk 目录下解析 version 文件返回版本号。

路由信息



在 Flutter 中,每个页面对应一个 Route,通过 Navigator 管理 Route。


Navigator 内部会包含一个 Overlay Widget,每个 Route 最终都转化成一个_OverlayEntryWidget 添加到 Overlay 上。这个地方可以把 Overlay 理解为 Android 中的 FrameLayout,内部子 View 上下叠加。每打开一个新的 Route,都相当于往 FrameLayout 添加一个新的子 View。


Navigator 会存在嵌套的情况,即 Route 所创建的页面本身也包含一个 Navigator,比如 App 的根 Widget 是 MaterialApp(自带 Navigator),Route 页面也用 MaterialApp 包裹,就会形成 Navigator 嵌套的情况。还是以 FrameLayout 来理解,这也就相当于嵌套的 FrameLayout。


路由信息功能会打印出当前栈顶页面所处的 Route 信息,如果存在 Navigator 嵌套的情况,也会向上遍历打印出每层 Navigator 的信息。


具体的实现方式是,先获取当前根 app 根 Element,可以使用 WidgetsBinding.instance.renderViewElement 作为根 Element,再通过递归调用 element 的 visitChildElements 方法,向下遍历整棵树找到最后一个 RenderObejctElement,该 RenderObejctElement 即为当前显示的页面上的元素。然后使用 ModalRoute.of(element)方法即可获取到当前页面的路由信息。


至于嵌套的路由信息,则可以通过找到的 RenderObejctElement 的 findAncestorStateOfType 方法,反向向上递归遍历,获得所处的 Navigator 的 NavigatorState,再调用 ModalRoute.of(navigatorState.context),如果返回不为空则表示存在嵌套。

方法通道



Flutter 的 Method Channel 调用最终都会经过 ServiceBinding.instance._defaultBinaryMessenger 这个对象,类型为 BinaryMessenger,由于这个对象是个私有对象,无法动态进行修改。不过查看 ServiceBinding 的源码可以发现这个对象是通过 ServiceBinding.createBinaryMessenger 方法创建的,通过使用 flutter 的 mixins,可以实现对该方法的重写。


我们知道,ServiceBinding 实际也是通过 mixins 在 WidgetsFlutterBinding.ensureInitialized 方法中一起被初始化的,所以只要在 WidgetsFlutterBinding 这个类额外 mixin 一个继承于 ServiceBinding 并且重写了 createBinaryMessenger 方法的类,就能实现对 ServiceBinding 中 createBinaryMessenger 的覆盖,代码如下:

class DoKitWidgetsFlutterBinding extends WidgetsFlutterBinding    with DoKitServicesBinding {  static WidgetsBinding ensureInitialized() {    if (WidgetsBinding.instance == null) DoKitWidgetsFlutterBinding();    return WidgetsBinding.instance;  }}
mixin DoKitServicesBinding on BindingBase, ServicesBinding { @override BinaryMessenger createBinaryMessenger() { return DoKitBinaryMessenger(super.createBinaryMessenger()); }}
复制代码


接下去把 runApp 的入口调用改成如下,就能实现 BinaryMessenger 的替换 static void _runWrapperApp(DoKitApp wrapper) { DoKitWidgetsFlutterBinding.ensureInitialized() ..scheduleAttachRootWidget(wrapper) ..scheduleWarmUpFrame(); } 至于 Method Channel 具体信息的捕获,只要 hook 住 BinaryMessenger.handlePlatformMessage 和 BinaryMessenger.send 两个方法就行了,具体可看 DoKitBinaryMessenger 这个类


控件检查



和路由功能类似,通过从根 element 向下遍历,在遍历过程中记录和选中的 View 有交集的所有 RendereObjectElement,并且记录用以标志当前页面的 RendereObjectElement,获取它的 Route 信息。


遍历完成后,遍历记录下来的 RendereObjectElement,过滤掉 Route 信息和当前页面不一致的,这些 Element 属于被遮盖住的页面。然后通过比对 RendereObjectElement 和选中 View 的交叉区域面积占 RendereObjectElement 面积的比例,占比最大的为当前选中的组件。


在 Debug 模式下可以获取选中组件在工程中的代码位置,将 WidgetInspectorService.instance.selection.current 赋值为选中 element 的 renderObject,再调用 WidgetInspectorService.instance.getSelectedSummaryWidget 方法,会返回一个 json 字符串,解析这个字符串就能获取源码文件名、行列信息等。

日志查看



日志查看功能比较简单,只要使用 runZoned 方法替代 runApp,传入 zoneSpecification,就能为日志输出设置一个代理函数,在这个代理函数内进行日志捕获,同时,还可以为 onError 设置一个代理函数,在这里将捕获的异常也会传入到日志当中。

帧率



使用 WidgetsBinding.instance.addTimingsCallback 可以统计帧率信息,在每帧渲染完成时会触发回调,包含该帧渲染的信息。

内存



同 VM 信息,使用 VMService 可以获取到内存详细使用信息。

网络请求



Flutter 自带的网络请求通过 HttpClient 类发送,只要 hook 住 HttpClient 的创建就可以 hook 整个网络请求的过程。查看 HttpClient 的构造函数可以发现,如果存在 HttpOverrides,就会使用 HttpOverrids 来创建 HttpClient


factory HttpClient({SecurityContext? context}) {  HttpOverrides? overrides = HttpOverrides.current;  if (overrides == null) {    return new _HttpClient(context);  }  return overrides.createHttpClient(context);}// 所以这里重写了一个HttpOverridsclass DoKitHttpOverrides extends HttpOverrides {  final HttpOverrides origin;
DoKitHttpOverrides(this.origin);
@override HttpClient createHttpClient(SecurityContext context) { if (origin != null) { return DoKitHttpClient(origin.createHttpClient(context)); } // 置空,防止递归调用,使得_HttpClient可以被初始化 HttpOverrides.global = null; HttpClient client = DoKitHttpClient(new HttpClient(context: context)); // 创建完成后继续置回DoKitHttpOverrides HttpOverrides.global = this; return client; }}
复制代码


替换 HttpOverrides


HttpOverrides origin = HttpOverrides.current;HttpOverrides.global = new DoKitHttpOverrides(origin);
复制代码


hook 住 HttpClient 方法后,对于请求和返回结果的 hook 过程就和 Android 中的 HttpUrlConnection 类似了,具体可以看 DoKitHttpClient、DoKitHttpClientRequest、DoKitHttpClientResponse 三个类。

版本 API 兼容


Flutter 版本更新还是比较快的,每一个大版本更新都会带来一些 API 的变更,目前 DoKit 的方案需要重写一些 framework 层的类,在兼容多版本时就会有一些问题。以上面的 BinaryMessager 为例,1.17 版本只有四个方法,用来 hook 的 DoKitBinaryMessager 是这么写的


class DoKitBinaryMessenger extends BinaryMessenger {  final MethodCodec codec = const StandardMethodCodec();  final BinaryMessenger origin;
DoKitBinaryMessenger(this.origin);
@override Future<void> handlePlatformMessage(String channel, ByteData data, callback) { ChannelInfo info = saveMessage(channel, data, false); PlatformMessageResponseCallback wrapper = (ByteData data) { resolveResult(info, data); callback(data); }; return origin.handlePlatformMessage(channel, data, wrapper); }
@override Future<ByteData> send(String channel, ByteData message) async { ChannelInfo info = saveMessage(channel, message, true); ByteData result = await origin.send(channel, message); resolveResult(info, result); return result; }
@override void setMessageHandler( String channel, Future<ByteData> Function(ByteData message) handler) { origin.setMessageHandler(channel, handler); }
@override void setMockMessageHandler( String channel, Future<ByteData> Function(ByteData message) handler) { origin.setMockMessageHandler(channel, handler); }}
复制代码


用来 hook 的 wrapper 类需要调用 oring 对象的同名方法。但在 1.20 版本 BinaryMessager 增加了两个新方法 checkMessageHandler 和 checkMockMessageHandler,如果使用 1.17.5 版本的 flutter sdk 去编译,就无法调用 origin.checkMessageHandler 方法,因为不存在;如果使用 1.20.4 版本的 flutter sdk 去编译,编译和发布没问题,但编出来的 sdk 在 1.17.5 的工程被引用后,也会因为 checkMessageHandler 方法不存在导致编译失败。


针对这种多个 Flutter 版本 API 不同导致的兼容性问题,可以使用扩展方法 extension 关键字来解决。 建立一个_BinaryMessengerExt 类如下:

extension _BinaryMessengerExt on BinaryMessenger {  bool checkMessageHandler(String channel, MessageHandler handler) {    return this.checkMessageHandler(channel, handler);  }
bool checkMockMessageHandler(String channel, MessageHandler handler) { return this.checkMockMessageHandler(channel, handler); }}
复制代码


在 1.17.5 版本,调用 origin.checkMessageHandler 会走到扩展方法的 checkMessageHandler 中,编译能通过,由于这个方法在 1.17.5 中是绝对不会被调用到的,虽然会形成递归调用,但没影响。而在 1.20 版本,BinaryMessenger 本身实现了 checkMessageHandler 方法,所以调用 checkMessageHandler 方法会走到 BinaryMessenger 的 checkMessageHandler 方法中,也能正常使用。 通过 extentsion,只要以最低兼容版本的类作为基础,在扩展类中定义新版本中新增的 API,就能解决多版本 API 兼容的问题。

总结


以上就是 DoKit For Flutter 的现有功能以及工具的基本原理介绍。 我们知道当前它的功能还不是完善,后续我们会继续不断深入的挖掘业务中的痛点并持续输出各种提高用户效率的工具,努力让 DoKit For Flutter 变得更加优秀,符合大家的期望。


DoKit 一直追求给开发者提供最便捷和最直观的开发体验,同时我们也十分欢迎社区中能有更多的人参与到 DoKit 的建设中来并给我们提出宝贵的意见或 PR。 DoKit 的未来需要大家共同的努力。

2021-01-05 14:354836

评论 1 条评论

发布
用户头像
这个点了我还是不困
2023-11-24 15:17 · 北京
回复
没有更多了
发现更多内容

docker通过dockerfile安装sftp教程。

百度搜索:蓝易云

Linux 运维 Dockerfile 云服务器 sftp

Fusion Studio 18 v18.6.4完美兼容破解版 附Fusion Studio激活补丁

Rose

mac视频后期特效处理 Fusion Studio 18下载 Fusion Studio激活秘钥 Blackmagic Fusion Studio

SVN管理工具Cornerstone for Mac入门教程 Cornerstone永久破解资源

Rose

MongoDB和阿里云携手驱动WeLab 引领超千万用户迈向智能金融未来

Geek_2d6073

软件测试/人工智能丨成员运算符

测试人

人工智能 软件测试

Sermant:无代理服务网格架构解析及无门槛玩转插件开发

华为云开发者联盟

云原生 后端 华为云 华为云开发者联盟 DTSE Tech Talk

SmartGit for Mac(老牌Git客户端)v23.1.1中文注册版 支持M/intel

Rose

SmartGit破解版 SmartGit许可证 Git 客户端 SmartGit for Mac SmartGit 中文

开源MES/免费MES/开源MES生产流程管理

万界星空科技

开源 开源代码 开源软件 免费开源 开源mes

Atlassian发布四个CVSS风险评分9.0或更高漏洞,影响多个产品

龙智—DevSecOps解决方案

Atlassian

Nacos 配置中心源码 | 京东物流技术团队

京东科技开发者

源码 nacos 源码剖析 配置中心

oracle dblink mysql查询text无法显示问题

百度搜索:蓝易云

MySQL oracle Linux text DBLINK

Topaz Video AI for mac v4.0.7注册激活版 人工智能视频增强 支持M/Intel

Rose

mac软件下载 人工智能视频增强 Video Enhance AI 下载 Video Enhance AI 注册

大模型发展的前景与挑战 主赛道:技术人的 2023 总结

不叫猫先生

大模型 ChatGPT #技术人的2023总结

万界星空科技低代码平台:搭建MES系统的优势

万界星空科技

低代码 数字化 MES系统 低代码开发 mes

基于FFmpeg实现一个数据流风格的视频处理工具 | 社区征文

为自己带盐

ffmpeg #技术人的2023总结

Amazon CodeWhisperer 免费的 AI 代码生成助手!最新体验反馈~

亚马逊云科技 (Amazon Web Services)

人工智能 亚马逊云科技 云上探索实验室 Amazon CodeWhisperer

CloudQuery x 达梦,国产数据库正当时

BinTools图尔兹

数据库 数据库管理 数据库安全 达梦 兼容适配

Wireshark中的http协议包分析

小齐写代码

如何将在线教育小程序一键生成App

Geek_2305a8

SVN优缺点详解及版本控制系统选型建议

龙智—DevSecOps解决方案

svn 版本控制

【数据安全】金融行业数据安全保障措施汇总

行云管家

金融 数据安全 运维安全 数据安全运维

mac强大的音频处理工具Ableton Live 12 中文版最新

胖墩儿不胖y

Mac软件 mac音频编辑器

视频后期特效合成软件:Blackmagic Fusion Studio18 激活最新

mac大玩家j

Mac软件 特效合成工具 Mac软件特效

Q-learning 入门:以 Frozen Lake 游戏环境为例

Baihai IDP

人工智能 程序员 AI 强化学习 白海科技

Web网页端IM产品RainbowChat-Web的v6.0版已发布

JackJiang

网络编程 即时通讯 IM

NFTScan | 12.04~12.10 NFT 市场热点汇总

NFT Research

NFT NFTScan nft工具

零基础也能搞定文案生成应用,半小时包教包会!「大模型摇摇乐」硬核教程来啦!

飞桨PaddlePaddle

人工智能 代码 零基础 开发教程 文案生成

VisualDiffer for Mac(文件夹和文件比较工具) 1.8.9中文激活密钥版

mac

苹果mac Windows软件 VisualDiffer 文件快速比较工具

010 Editor 十六进制编辑器 注册激活版 mac/win

Rose

010 Editor下载 010 Editor破解版 010 Editor注册码 16进制编辑器

INTO领航:2023社交变革峰会揭示数字社交的未来格局

Geek_2d6073

LED透明屏市场前景展望

Dylan

全球经济下行 中美贸易 LED LED显示屏 led显示屏厂家

滴滴DoKit For Flutter正式开源,功能及核心实现解读_开源_林基宗_InfoQ精选文章