background

At present, the business scene of the author side of the watermelon video has covered more than 40 pages (including the video playing scene). The core scene of the user side, including my Tab, has also been a Flutter. During the development process, some problems have been exposed. Debugging is difficult, leaving the IDE just like blind, PM design QA acceptance process can not get useful information, in the market to find a round, there is no such a powerful debugging tool like iOS Flex, such as view size, hierarchy display, real-time modification of instance object properties, network request fetching, The Watermelon Video Flutter foundation team decided to develop a UME to solve the above problems.

introduce

UME is a Flutter debug kit that integrates a wealth of debug tools, including UI, networking, monitoring, performance, Logger, etc., that can be used by R&D, PM, and QA.

Currently implemented functions

The following sections will detail the effects and implementation of some core functions:

Module,

The Widget information

You can view the size, name, file path, and number of lines of code of the currently selected widget. With this tool, you can quickly find the current code even if you are not responsible for the development of the feature module.

How do I get information about the selected widget? The size is available through the RenderObject, and the location of the widget’s code? To get a JSON string, use the getSelectedSummaryWidget in WidgetInspectorService. Let’s look at its structure:

{ "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} replicate codeCopy the code

Because there is too much data, part of the data is omitted, and then the required part can be found according to the corresponding key.

The Widget hierarchy

You can view the tree hierarchy of the currently selected widget and the detailed build chain for its renderObject.

Retrieving the current currentElement through InspectorSelection The entire build chain can then be retrieved using the debugGetDiagnosticChain method. RenderObject information is also readily available by fetching the current RenderObject through currentElement and then using the toString method.

ShowCode

You can view the page code of the current page.

The main implementation involves the following key points:

  1. Gets the file name of the widget that the current page belongs to
  2. Locate and read the Dart script based on its file name

The main use of WidgetInspectorService to obtain the file name. The reading script is mainly implemented using VMService.

Gets the name of the current page widget
  • We get the current page’s values by traversingrenderObjectList of desired target widgets by size.
  • As explained in the Widget message, we can passWidgetInspectorServiceIn thegetSelectedSummaryWidgetMethod to get a JSON string.
  • Extract the value of “creationLocation” which is the file address of the current widget during development.
  • The last part of the address string we extract is the file name of the current page code.
Find and read the script
  • VMServiceIn thegetScriptsMethod to get the ids and filenames of all library files under the current thread.
  • We can get the target library file ID by comparing the file name.
  • throughVMServicethegetObjectMethod can get the object corresponding to the current ID, we just get the library file ID to get the library object, read the objectsourceProperty, which is our source code.

Memory leaks

LeakDetector is used to detect flutter memory leaks. The overall implementation idea is similar to that of the Android LeakCannary tool. Expando is used to weakly reference the object to be detected, and VMService is used to get the reference chain of the leaked object, and finally the leaked information is stored locally and displayed.

Dart VM Service A set of Web services provided by Dart. The data transmission protocol is JSON-RPC 2.0. The interface provides important information about the Dart virtual machine. Here is the process:

  1. Obtain VMService
  • Get ObservatoryUri
    • throughService.``getInfo``()To obtainServiceProtocolInfo, from whichserverUri
    • throughvm_serviceUtil tool method inconvertToWebSocketUrl()Convert the HTTP format URI format above to ws:// format
    • Obtain the VmService service object.vm_service_ioThere’s one in the filevmServiceConnectUri()Method, passing in aobservatoryUriYou can get a VmService object
  1. Get isolateId
  • The VmService getVM method gets the VM object, which stores all the IsolateRef
    • throughService.getIsolateID(Isolate.current)Debug only returns null for release
  1. Get libraryId
  • After getting the isolateId through step 2, call VmServicegetIsolateGet the corresponding Isolate.
  • The LibraryRef ID can be obtained by iterating through the libraries field of the Isolate, which is the List of LibraryRef, and then matching the URI of the current Library to the List of LibraryRef uri.
  • With the Id of isolateId and LibraryRef, call the VmService’s getObject method to get the Library, The id field is the libraryId we are looking for (in fact, the ID of the LibraryRef should be, can actually test).
  1. To obtain the objectId

Since getInstance(isolateId, classId, Limit) has performance and limit limitations, Instead, we use the Invoke (isolateId, targetId, Selector, argumentIds, disableBreakpoints) method, To retrieve the libraryId, which is the targetId of invoke, we need to store the target object temporarily and invoke it to retrieve the InstanceRef of the object. And then we get the ID field, which is our objectId.

  1. Leak to judge
  • Get the Obj Instance of an Expando object with the getObject(isolateId, objectId) method. Its true type is actually an Instance.
  • Select _data from Instance fields where the _data field is of type ObjRef. You can go through BoundField’s FieldRef field and then match FieldRef’s name with ‘_data’) inexpando_path.dartAs an Expando, the _data field is a List.
  • Iterating over the _data field, if both are null, it indicates that all the observed key objects have been freed; If the element is not null, then the element is converted to an Instance object (which is a WeakProperty), and the propertyKey field is our actual unreclaimed object.
  1. Get the reference path
  • There’s one VmServicegetRetainingPathMethod takes an object’s reference chain directly, but only one.
  • Note that after using Expando to detect memory leaks, the Expando reference to the original object is released.
  • Instance’s ID will expire, and VmService has a maximum cache of 8192 for it, so don’t save the ID, save the object instead.
  1. Trigger GC
  • There’s one VmServicegetAllocationProfile(isolateId, gc=true)Method to trigger the DART VM for GC, which is what the Dev Tools tool ultimately calls to trigger the GC button. According to the tests, the FULL GC is triggered.
  1. trigger
  • The Route to detect

    • Provided by the FrameworkNavigatorObserverMechanism that can easily listen to pages in and out of the stack and trigger leak detection for routes in didPop, didRemove, and didReplace methods.
  • Widget/State detection

    • The general in-page Widget/State is not detected, but only the Widget and State corresponding to the real page. The framework does not provide a global mechanism to listen for page destruction. Here we usehook_annotationHook two points: RouteRootState’s initState method, which records the page object to be detected; Dispose method of State, if it is the page we have recorded, triggers the detection process.

The memory view

Memory can be used to view the current usage of Dart VM objects.

To get vm memory, you have to rely on the Dart VM, which is available through the vm_service interface. Using the Future

getMemoryUsage command, you can obtain information about the current status of the ISOLATE. Look at the structure of MemoryUsage. Each attribute is explained in detail.

/// 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; Copy the codeCopy the code

So how do we get the memory information for each class object? The getAllocationProfile is used to get information about allocated objects, and the Members attribute is used to get information about the heap occupied by each class.

Align scale

The alignment ruler is used to measure the coordinate position of the current widget on the screen. When the attach switch is turned on, the nearest widget will be automatically attached.

The ruler shows the position of c-21 very simply, by moving the position of the hand, and calculates the distance by the size of the screen. We will focus on the implementation of the automatic adsorption. To attach the nearest widget, you must find the widget with the current location, draw a size range for the current widget, and set the ruler position. GlobalKey allows us to retrieve a RenderObject from the current page and all its children from debugDescribeChildren. Then through describeApproximatePaintClip access to the current object in the coordinate system the Rect, according to some after coordinate transformation, judge whether the current coordinate range, and finally according to the size of the RenderObject do a sorting, This way we know that the smallest widget must be the nearest widget in the current coordinate position. Once we have the nearest widget, we simply set the center position of the ruler to the nearest four corners of the widget.

Straw color

You can see the color of any pixel on the current page for easy UI debugging.

This function is first divided into two steps: 1, background enlargement, 2, get the current pixel color value

How to enlarge an image

To add some effect to a Flutter image, we can use the BackdropFilter, which is to add a layer of filter effect. There are not many parameters. We can use the ImageFilter to add a specific filter. We can use ImageFilter. Matrix, which can enlarge the background image. The filterQuality parameter can be used to set the quality of the effect. This can be set by using Matrix4, the position of our gesture movement, plus the scale to calculate its matrix parameters, and assign to ImageFilter. Matrix will be magnified.

How to get image pixel and color value

To capture a screenshot of a Flutter, you must use the RepaintBoundary with globalKey to capture the current screenshot of the screen.

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); Copy the codeCopy the code

After obtaining the screenshot, we need to obtain the current pixel value of the Image by moving the position. We can use the Image getPixelSafe to obtain the pixel color with the Uint32 encoding (#AABBGGRR). Finally, we need to convert ABGR to ARGB.

int abgrToArgb(int argbColor) { int r = (argbColor >> 16) & 0xFF; int b = argbColor & 0xFF; return (argbColor & 0xFF00FF00) | (b << 16) | r; } Duplicate codeCopy the code

Network debugging

When debugging the Flutter network, it is very troublesome to mock data or view requests. You need to connect the proxy and use the packet capture tool to perform these operations. You want to be able to perform these operations simply on your mobile phone.

  • Supports fetching all network requests

  • Data support structured display, long press can be copied to the clipboard

  • Collection request, separate display; Clear the unfavorites list

  • Request filtering and search (support partial matching, regular matching)

  • Request to export curl

  • Persist and export HAR

  • Mock response content

    • Full HAR file mapping
    • Modifying a single field
  • Long press to copy structured information

Looking at this, you might ask how does this intercept all the network requests? Dart is used to hook specific apis at compile time (essentially replacing a method’s implementation to add your own implementation). Due to lack of space, we will not go into the process of hook for the moment. All web requests in a Flutter go on _sendunstreaming in the BaseClient class in Package: HTTP/SRC /base_client.dart, We only need hook_sendunstreaming method to intercept all requests on the web.

Logger

It shows logs printed using the debugprint function, especially some of the player logs. It is very convenient to view logs without an IDE. ⁣

There are two ways to intercept print:

  • Dart has onerunZonedThe Zone represents the scope of a code execution environment. The Zone is like a code execution sandbox. Different sandboxes are isolated from each other and can capture, intercept, or modify some code behavior. For example, the Zone can capture log output, Timer creation, microtask scheduling, and all unhandled exceptions. runZoned(…) Method definition:
R runZoned<R>(R body(), { Map zoneValues, ZoneSpecification zoneSpecification, Function onError }) zoneValues: Zone private data, copy code can be obtained by instance Zone [key]Copy the code

ZoneSpecification: Indicates the Zone configuration. You can customize code behaviors, such as blocking log output. All calls to the print method to output the log will be intercepted.

runZoned(() => runApp(MyApp()), zoneSpecification: new ZoneSpecification( print: (Zone self, ZoneDelegate parent, Zone zone, String line) { print(line); })); Copy the codeCopy the code
  • Hook way

Since print may be called in the hook to print logs in an infinite loop, we only use hookdebugPrint. To package: flutter/SRC/foundation/print debugPrintThrottled in the dart hook.

Channel Monitor

You can view all channel calls, including method names, times, parameters, and return results. ⁣

Hookpackage: flutter/SRC/services/platform_channel dart MethodChannel invokeMethod method of a class.

Current problems

At present, only the preliminary version has been completed, many functions still need to be improved and more new functions; I’m going to go into some details; At present, network debugging, channel monitoring, Logger and other functions rely on Hook scheme, and the subsequent Hook scheme will also consider open source.

conclusion

The above introduces some of the core functions and implementation of UME, and many rich functions will not be continued here due to space problems. There will be more interesting things in the future, and some core functions will be considered open source in the future.

Join us

We are the r&d team responsible for the basic technology of Watermelon video client Flutter. We are deeply engaged in Flutter engineering, r&d tools and other aspects to support rapid business iteration while improving the efficiency of Flutter development mode packaging. If you are passionate about technology, welcome to join the Watermelon Video Flutter Foundation Technology team or watermelon Foundation Business Team. At present, we have recruitment needs in Shanghai, Beijing and Hangzhou. For internal promotion, please contact [email protected]. Email subject: Name – years of work – watermelon – iOS/Android.

More share

An example of Go compiler code optimized for bug location and fix parsing

Bytedance breaks federal Learning: Open source Fedlearner framework increases advertising efficiency by 209%

Douyin Quality Construction – iOS Startup optimization principles

IOS performance optimization practice: How does Toutiao Douyin reduce OOM crash rate by 50%+


Welcome to Bytedance Technical Team

Resume mailing address: [email protected]