亚博APP-亚博APP手机版

0802-198657711

在线客服| 微信关注
当前位置: 首页 > 后期工艺 > 标签单据

UME - 富厚的Flutter调试工具


本文摘要:配景现在西瓜视频作者侧 Flutter 业务场景已经笼罩了 80% (包罗视频播放场景),用户侧焦点场景包罗我的 Tab 也已经是 Flutter,在开发历程中,袒露了一些问题,debug 调试难、脱离了 IDE 后犹如抓瞎、PM 设计 QA 验收历程中拿不到有用的信息,在市面上找了一圈,也没有类似 iOS Flex 这样强大的调试工具,例如视图巨细、层级的展示,实例工具属性的实时修改,网络请求抓取,log 日志打印,文件检察等,因此西瓜视频 Flutter 基础团队决议开发 UME 以解决上述问题。

亚博app

配景现在西瓜视频作者侧 Flutter 业务场景已经笼罩了 80% (包罗视频播放场景),用户侧焦点场景包罗我的 Tab 也已经是 Flutter,在开发历程中,袒露了一些问题,debug 调试难、脱离了 IDE 后犹如抓瞎、PM 设计 QA 验收历程中拿不到有用的信息,在市面上找了一圈,也没有类似 iOS Flex 这样强大的调试工具,例如视图巨细、层级的展示,实例工具属性的实时修改,网络请求抓取,log 日志打印,文件检察等,因此西瓜视频 Flutter 基础团队决议开发 UME 以解决上述问题。先容UME (读音:油米~) 是一个 Flutter 调试工具包,内部集成了富厚的调试小工具,设计 UI、网络、监控、性能、logger 等,无论是研发、PM、还是 QA 均能使用。

现在已实现的功效首页功效支持拖拽排序展示当前使用功效Widget 信息展示widget 名称widget 巨细widget 文件路径widget 代码所在行Widget 层级widget 构建链支持 widget 搜索widget 信息展示renderObject 详细信息展示网络调试支持所有基于 http 包的等网络请求抓取数据支持结构化展示,长按可以复制到剪贴板收藏请求,单独展示;清空非收藏列表请求过滤与搜索(支持部门匹配、正则匹配)请求导出 curl持久化与导出 HARmock 响应内容完整 har 文件映射修改单个字段结构化信息长按复制内存泄露支持自动检测由 route 打开的页面Widget、State、Route 工具的内存泄漏检测内存检察Dart vm 信息展示当前 Dart 内存使用情况详细类信息所占用的空间跟数量类属性和方法展示CPU 模块CPU 详细信息展示CPU 是使用率(iOS)App 内存和磁盘使用情况性能看板GPU 和 UI FPS 信息展示颜色吸管颜色值获取十六进制对齐标尺widget 屏幕坐标展示可自动吸附最近 widget二维码二维码生成Logger展示 debugPrint 函数输出日志支持搜索Device Info手机硬件信息展示HTTP Server现在 8080 端口用于上传文件,用户可上传 HAR 文件,用于 Mock 请求8888 端口作为数据端口,现在用途是校验服务 host、吸收用户上传的文件未来计划开放更多数据 api,允许通过该服务读取 UME 中的数据Channel Monitorchannel 挪用方法名称请求参数返回起始时间返回效果支持搜索接下来会详细先容一些焦点功效的使用效果以及焦点实现模块详解Widget 信息可以检察当前选中 widget 的巨细、名称,文件路径以及代码所在行数,有了这工具,纵然你不卖力这个功效模块的开发,你也能迅速找到当前代码。那如何能获取到选中当前 widget 的信息呢,巨细通过RenderObject 就能拿到,那 widget 的代码位置呢? 通过WidgetInspectorService 中的 getSelectedSummaryWidget 便可以获取到一个 json 字符串,我们来看下它的结构:{ "description":"Text", "type":"_ElementDiagnosticableTreeNode", "style":"dense", "hasChildren":true, "allowWrap":false, "locationId":0, "creationLocation":{ "file":"file:///Users/.../example/lib/home/widgets/category_card.dart", "line":69, "column":15, "parameterLocations":[ { "file":null, "line":70, "column":24, "name":"data" }, ... ] }, "createdByLocalProject":true, "children":[ { "description":"RichText", "type":"_ElementDiagnosticableTreeNode", "style":"dense", "allowWrap":false, "locationId":1, "creationLocation":{ "file":"file://../packages/flutter/lib/src/widgets/text.dart", "line":425, "column":21, "parameterLocations":[ { "file":null, "line":426, "column":7, "name":"textAlign" }, ... ] }, "children":[], "widgetRuntimeType":"RichText", "stateful":false } ], "widgetRuntimeType":"Text", "stateful":false}由于数据太多了,省略了一部门, 然后凭据对应的 key 即可找到需要的部门。Widget 层级可以检察当前选中 widget 的树层级,以及它 renderObject 的详细 build 链。

这个获取到选中 widget 的一个 build 链还是比力简朴的,通过 InspectorSelection 获取到当前 currentElement ,然后 使用 debugGetDiagnosticChain 方法就可以获取到整个 build 链了。RenderObject 的信息也很好获得,通过currentElement 拿到 当前的RenderObject,然后使用 toString方法就可以拿到了。ShowCode可以检察到当前页面的页面代码。

主要实现涉及到以下几个关键点:获取到当前页面 widget 所属的文件名凭据 dart 剧本的文件名来找到并读取剧本获取文件名主要使用WidgetInspectorService实现。而读取剧本主要使用VMService实现。获取当前页面 widget 文件名我们通过遍历获恰当前页面的renderObject列表,根据巨细筛选出我们想要的目的 widget。

Widget 信息中解说到过,我们可以通过WidgetInspectorService 中getSelectedSummaryWidget 方法获取到 json 字符串。提取"creationLocation"的值即是当前 widget 的在开发历程中的文件地址。我们截取出来地址字符串的最后一部门就是当前页面代码所在的文件名了。

找到并读取剧本VMService中的getScripts方法可以获取当前线程下的所有库文件的 ID 和文件名。我们通过比对文件名可以获得目的库文件 id。通过VMService的getObject方法可以获取到当前 id 对应的工具,我们传入刚刚获取的库文件 id 即可获得这个库工具,读取工具的source属性,内里就是我们的源码了。

内存泄露LeakDetector 用于检测 flutter 内存泄漏,总体的实现思想和 Android 平台的LeakCannary工具类似。使用Expando来弱引用持有待检测工具,而且使用 VMService 拿到泄漏工具的引用链,最终将泄漏信息当地存储而且展示出来。Dart VM Service Dart 提供的一套 web 服务,数据传输协议是 JSON-RPC 2.0。

亚博app手机版

通过它提供的接口我们能获取到 Dart 虚拟机内部的一些重要信息。下面先容下整个历程:获取 VMService 服务获取 ObservatoryUri通过Service.``getInfo``()获取ServiceProtocolInfo,从中取出serverUri通过vm_service中的 util 工具方法convertToWebSocketUrl()将上面的 http 花样的 uri 花样转为 ws://花样获取 VmService 服务工具, vm_service_io文件中有个vmServiceConnectUri()方法,传入一个observatoryUri就可以获取一个 VmService 工具获取 isolateId通过 VmService 的 getVM 方法拿到 VM 工具,VM 工具中存储着所有的 IsolateRef通过Service.getIsolateID(Isolate.current)拿到,只有 debug 下有效,release 下会返回 null获取 libraryId通过第 2 步拿到 isolateId 之后,然后挪用 VmService 的getIsolate拿到对应的 Isolate 工具。

遍历 Isolate 的 libraries 字段,这是一个 LibraryRef 的 List,然后拿当前 Library 的 uri 去 List 中匹配 LibraryRef 的 uri,就可以获取 LibraryRef 的 id。拿着 isolateId 和 LibraryRef 的 Id,挪用 VmService 的 getObject 方法就可以获取 Library,取其 id 字段就是我们要找的 libraryId(其实 LibraryRef 的 id 应该就是了,实际可以测试)。获取 objectId由于getInstance(isolateId, classId, limit)方法存在性能和 limit 限制的问题,我们转而使用invoke(isolateId, targetId, selector, argumentIds, disableBreakpoints)方法,借助 Library 顶层函数就可以获取 libraryId 也就是 invoke 方法中的 targetId,最后我们只需要将目的工具暂存一下再通过 invoke 方法取出来就可以拿到该工具的 InstanceRef 了,进而拿到其 id 字段就是我们要找的 objectId 了。

泄漏判断通过 getObject(isolateId, objectId)方法拿到 Expando 的工具的 Obj 实例,它的真实类型其实是一个 Instance。遍历 Instance 的 fields 字段找到_data(_data 的类型是 ObjRef,可以拿到它对应的 Instance 实例)字段(怎么找_data?可以通过 BoundField 的 FieldRef 字段,然后匹配 FieldRef 的 name 为‘_data’),在expando_path.dart中我们可以看到 Expando 的详细实现,_data 字段是一个 List。遍历_data 字段,如果都为 null,讲明我们视察的 key 工具都释放了;如果元素不为 null,则将该该元素转为 Instance 工具(其实就是一个 WeakProperty),取其 propertyKey 字段就是我们实际的没被接纳的工具了。

获取引用路径VmService 有一个getRetainingPath方法可以直接拿到一个工具的引用链,可是只会拿一条。需要注意在前面使用 Expando 检测完内存泄漏之后,就释放 Expando 对原始工具的引用。Instance 的 id 会逾期,VmService 对它的缓存最大是 8192,所以不要生存 id 而要生存工具。触发 GCVmService 有一个getAllocationProfile(isolateId, gc=true)方法,通过它来触发 dart vm 举行 gc,这个也是 Dev Tools 工具上触发 gc 按钮最终挪用的方法。

据测试触发的都是 FULL GC。触发时机Route 检测借助 framework 提供的NavigatorObserver机制,可以很轻松的监听到页面的收支栈,在 didPop、didRemove、didReplace 方法中触发对 route 的泄漏检测。Widget/State 检测一般的页内 Widget/State 不检测,而只检测真正页面临应的 Widget 和 State,framework 并没有提供一个全局监听页面销毁的机制。

这里我们借助hook_annotation(这个后面会解释)来 hook 两个点:RouteRootState 的 initState 方法,记载要检测的页面临象;State 的 dispose 方法,如果是我们已记载的页面,则触发检测流程。内存检察Memory 可用于检察当前 Dart VM 工具所占用情况。

需要拿到 vm 内存的话就必须得依赖 Dart VM,上文说到,通过 vm_service 就可通过它提供的接口拿到。通过 Future<MemoryUsage> getMemoryUsage 就能获取到当前 isolate 所占用的信息,来看下 MemoryUsage 的结构, 每个属性都有详细的解释,这里就不再赘述了。/// The amount of non-Dart memory that is retained by Dart objects. For/// example, memory associated with Dart objects through APIs such as/// Dart_NewWeakPersistentHandle and Dart_NewExternalTypedData. This usage is/// only as accurate as the values supplied to these APIs from the VM embedder/// or native extensions. This external memory applies GC pressure, but is/// separate from heapUsage and heapCapacity.int externalUsage;/// The total capacity of the heap in bytes. This is the amount of memory used/// by the Dart heap from the perspective of the operating system.int heapCapacity;/// The current heap memory usage in bytes. Heap usage is always less than or/// equal to the heap capacity.int heapUsage;那如何获取到每个类工具的内存信息呢?通过 getAllocationProfile 获取分配工具的信息,通过members属性来获取到每个 class 所占用的堆信息。

亚博app

对齐标尺对齐标尺用来丈量当前 widget 所在屏幕的一个坐标位置,开启吸附开关后可以自动吸附最近 widget。标尺显示当前坐标还是很是简朴的,通过手势移动的坐标,来改变Positioned的位置即可,并通过屏幕的巨细来盘算出当前的距离,下面会着重讲一下自动吸附的实现。

要吸附最近的 widget,就必须找到当前位置的所在的 widget,然后并画出当前 widget 的一个巨细规模,最后设置标尺的位置即可,那么如何找到当前坐标的 widget 呢?通过 globalKey 我们可以获取到当前页面的一个RenderObject,然后通过它的debugDescribeChildren 获取到它的所有子节点,然后通过describeApproximatePaintClip获取到当前工具坐标系中的Rect,之后在凭据一些坐标转换,判断是不是在当前坐标规模,最后凭据RenderObject 的巨细做一个排序,这样我们就能知道最小的谁人一定是当前坐标位置中最近的 widget 了,获得最近的 widget 之后,我们只需要将标尺的中心位置设置成离 widget 最近的四个角即可。颜色吸管可以检察到当前页面任何像素的颜色,利便调试 UI。这个功效首先分为两步,1、配景放大 2、获取当前像素的颜色值如何放大图片在 Flutter 中,要想给图片加一些效果,我们可以用到 BackdropFilter, 其实就是加上一层滤镜效果,发现参数其实并不多,通过 ImageFilter就能添加详细的滤镜,想要做一个放大的效果,我们可以使用 ImageFilter.matrix ,它能够放大配景图片, filterQuality 参数可以用来设置放大效果的质量,那如何放大对应的位置以及放大的倍数呢?通过Matrix4便可以设置,通过我们手势移动的位置,加上 scale 就能盘算出它的矩阵参数,并赋值给ImageFilter.matrix就能获得放大效果。

如何获取图片像素及颜色值在 Flutter 中想要截图的话就必须借助RepaintBoundary了,配合globalKey我们就能获取当屏幕的当前截图了。RenderRepaintBoundary boundary = rootKey.currentContext.findRenderObject();Image image = await boundary.toImage();ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);Uint8List pngBytes = byteData.buffer.asUint8List();snapshot = img.decodeImage(pngBytes);获取到截图后,我们就需要通过移动的位置来获取到图片的当前像素值了,可以通过Image的 getPixelSafe 来获取到 用 Uint32 编码过的像素颜色值了(#AABBGGRR),最后我们只需要把abgr转换成 argb 就好了。int abgrToArgb(int argbColor) { int r = (argbColor >> 16) & 0xFF; int b = argbColor & 0xFF; return (argbColor & 0xFF00FF00) | (b << 16) | r;}网络调试在调试 Flutter 网络的时候,要 mock 数据或者检察请求很是贫苦,需要连署理,使用抓包工具才可以举行这些操作,想要简朴的在手机上就能完成这些操作,所以网络调试模块现在支持的功效:支持所有网络请求抓取数据支持结构化展示,长按可以复制到剪贴板收藏请求,单独展示;清空非收藏列表请求过滤与搜索(支持部门匹配、正则匹配)请求导出 curl持久化与导出 HARmock 响应内容完整 har 文件映射修改单个字段结构化信息长按复制⁣⁣看到这,你可能会问这是怎么拦截到所有的网络请求的呢?这里通过 Dart 在编译时的插桩从而到达对特定 API 的 hook 效果(其实就是替换掉某个方法的实现从而添加自己的实现),由于篇幅问题,这里暂时不展开讲 Hook 的详细流程~ 之后也会有另外的文章来详细说这个。

Flutter 中的所有网络请求走的都是 package:http/src/base_client.dart 中 BaseClient 类中的_sendUnstreamed, 因此,我们只需要 hook _sendUnstreamed 方法便可以拦截到所有的网络请求。Logger会展示使用 debugprint 函数打印的日志,特别是播放器的一些日志,在没有 IDE 的情况下,检察日志还是很利便的。⁣拦截 print 有两种方式:Dart 中有一个runZoned方法,可以给执行工具指定一个 Zone,Zone 表现一个代码执行的情况规模,Zone 类似一个代码执行沙箱,差别沙箱的之间是隔离的,沙箱可以捕捉、拦截或修改一些代码行为,如 Zone 中可以捕捉日志输出、Timer 建立、微任务调理的行为,同时 Zone 也可以捕捉所有未处置惩罚的异常。

runZoned(...)方法界说:R runZoned<R>(R body(), { Map zoneValues, ZoneSpecification zoneSpecification, Function onError})zoneValues: Zone 的私有数据,可以通过实例zone[key]获取zoneSpecification:Zone 的一些设置,可以自界说一些代码行为,好比拦截日志输出行为等。这样所有挪用 print 方法输出日志的行为都市被拦截。

runZoned(() => runApp(MyApp()), zoneSpecification: new ZoneSpecification( print: (Zone self, ZoneDelegate parent, Zone zone, String line) { print(line);}));通 hook 的方式由于在 hook 的 print 方法里可能会挪用 print 来打印日志造成死循环,这里我们只 hook debugPrint 方法,对 package:flutter/src/foundation/print.dart 中 debugPrintThrottled 举行 hook 即可。Channel Monitor可以检察到所有的 channel 挪用,包罗方法名,时间,参数,返回效果。⁣⁣hook package:flutter/src/services/platform_channel.dart 中 MethodChannel 类的invokeMethod方法即可。现在存在的问题现在只是完成了开端的版本,许多功效还需要继续完善以及更多的新功效;接下来会从一些细节上继续深入;现在网络调试、channel 监控、Logger 这些功效依赖于 Hook 方案,后续 hook 方案也会思量开源。

总结以上先容了一些 UME 的焦点功效以及实现,另有许多富厚的功效由于篇幅问题在这里就不继续展开了,之后还会有更多有趣的工具泛起,未来会思量开源一些焦点功效。加入我们我们是卖力西瓜视频客户端 Flutter 基础技术研发团队。我们在 Flutter 工程,研发工具等偏向深耕,支撑业务快速迭代的同时,提高 Flutter 开发调式打包效率。

如果你对技术充满热情,接待加入西瓜视频 Flutter 基础技术团队或者西瓜基础业务团队。现在我们在上海、北京、杭州、均有招聘需求,内推可以联系邮箱:tech@bytedance.com ;邮件标题:姓名 - 事情年限 - 西瓜 - iOS/Android。更多分享一例 Go 编译器代码优化 bug 定位和修复剖析字节跳动破局联邦学习:开源Fedlearner框架,广告投放增效209%抖音品质建设 - iOS启动优化《原理篇》iOS性能优化实践:头条抖音如何实现OOM瓦解率下降50%+接待关注「 字节跳动技术团队 」简历投递联系邮箱「 tech@bytedance.com 」。


本文关键词:亚博app,亚博app手机版

本文来源:亚博app-www.crmzw.com

客户案例Customer case
  • 网约车平台公司不应主动公开发表定价机制和动态调高机制-亚博app手机版
  • 人民币会“破7”吗?:亚博app手机版
  • 【亚博app手机版】下周坑口价格走势分化陕西煤价以稳为主内蒙古价格偏弱
  • 国网江苏电力:推出全国首个电力大数据公共查询平台:亚博app手机版
  • 亚博app|高温致台湾即时用电量达3692.6多万千瓦创史上第二高
  • 中国最低规格能源协商机构国家能源委员会迎接来自2010年正式:亚博app手机版
  • 焦炭第二轮提涨呼声高,焦煤也受益!-亚博app
  • 三峡大坝变形溃堤在即?回应:工程运行安全可靠
  • 亚博app手机版:陕煤集团2018年入渝煤炭总量同比增长逾六成
  • 2019年资本市场值得期待