×
Community Blog Flutter Analysis and Practice: Design and Practices of the High-Availability Framework

Flutter Analysis and Practice: Design and Practices of the High-Availability Framework

This article talks about the design and practices of Xianyu's high-availability framework.

With the booming development of mobile smart devices, a mobile multi-terminal development framework has become a general trend. Download the Flutter Analysis and Practice: Evolution and Innovation of Xianyu Technologies eBook for step-by-step analyses based on real-life problems, as well as clear explanations of the important concepts of Flutter.

4.3.1 Why Do We Monitor Flutter Performance?

We now have various Application Performance Monitoring (APM) means. During native development, we have developed many SDKs to monitor online performance data. However, Flutter has made many revolutionary changes compared with native. As a result, all native performance monitoring on Flutter pages is invalid. To solve this problem, Xianyu launched a project called "Flutter High-Availability SDK" in December 2018. This project aims to make Flutter pages as measurable as native pages.

4.3.2 Required SDKs

Since performance monitoring is mature, we have sufficient resources for reference. Xianyu references the performance monitoring SDKs of the high EMAS availability of Taobao Mobile, the Matrix of WeChat, and the Hertz of Meituan to determine the performance metrics to be collected and the features required for the SDKs based on the actual Flutter situation.

4.3.2.1 Performance Metrics

1) Page sliding smoothness

Frames Per Second (FPS) is the main metric for page sliding smoothness. However, the FPS cannot be used to distinguish between a large number of slight frame lag events and a small number of serious frame lag events. These two types of frame lag events impact the user experience differently. Therefore, in addition to the FPS, we also introduced the sliding duration and frame dropping duration to measure the page sliding smoothness.

2) Page loading duration

The time consumed by page loading better reflects the interaction duration of users. The interaction duration is the period from the time when the user starts route redirection to the time when the interaction ends during page loading.

3) Page exceptions

Exceptions are basic metrics for evaluating user experience. We need to determine whether a version is qualified by collecting page exceptions and calculating the page exception rate.

4.3.2.2 SDK Features

1) Accuracy

Accuracy is a basic requirement of a performance monitoring SDK because developers may spend a lot of unnecessary time on troubleshooting due to false positives.

2) Online monitoring

Online monitoring states that the cost of collecting data cannot be too high, and the monitoring cannot affect app performance.

3) Easy to develop

The ultimate goal of an open-source project is that everyone gets involved and contributes to the community. Therefore, the SDK must be easy to develop, and a series of specifications are needed for development.

4.3.3 Overall Design in Terms of a Single Index

This section takes the collection of the instantaneous FPS as an example to describe the overall design of the SDK.

First, the FpsRecorder class inherited from BaseRecorder needs to be implemented. This class is used to obtain the pop and push time of pages at the business layer, as well as the time (provided by FlutterBinding) when page rendering starts and ends and tap events occur and calculate the source data according to the time. For the instantaneous FPS, the source data is the duration of each frame.

class FpsRecorder extends BaseRecorder {
    ///...
  @override
  void onReceivedEvent(BaseEvent event) {
    if (event is RouterEvent) {
      ///...
    } else if (event is RenderEvent) {
      switch (event.eventType) {
        case RenderEventType.beginFrame:
          _frameStopwatch.reset();
                _frameStopwatch.start();
          break;
        case RenderEventType.endFrame:
          _frameStopwatch.stop();
          PerformanceDataCenter().push(FrameData (_frameStopwatch.elapsedMicroseconds));
          break;
      }
    } else if (event is UserInputEvent) {
      ///...
    }
  }

  @override
  List<Type> subscribedEventList() {
    return <Type>[RenderEvent, RouterEvent, UserInputEvent];
  }
}

We start from beginFrame and end at endFrame to get the duration of each frame. The collected duration of each frame is encapsulated as FrameData and pushed to the PerformanceDataCenter. The PerformanceDataCenter distributes the data to the Processor that has subscribed to the FrameData. Therefore, we need to create an FpsProcessor to subscribe to and process the source data.

class FpsProcessor extends BaseProcessor {
    ///...
  @override
  void process(BaseData data) {
    if (data is FrameData) {
      ///...
      if (isFinishedWithSample(currentTime)) {
        ///当时间间隔大于1s,则计算一次FPS
        _startSampleTime = currentTime;
        collectSample(currentTime);
      }
    }
  }

  @override
  List<Type> subscribedDataList() {
    return [FrameData];
  }
  
  void collectSample(int finishSampleTime) {
    ///...
    PerformanceDataCenter().push(FpsUploadData(avgFps: fps));
  }
  ///...
}

The FpsProcessor collects the duration of each frame and calculates the instantaneous FPS of 1s. Similarly, the calculated FPS is encapsulated as an FpsUploadData and then pushed to the PerformanceDataCenter. The PerformanceDataCenter sends the FpsUploadData to the Uploader that has subscribed to it for processing. Therefore, we need to create a MyUploader to subscribe to and process the data.

class MyUploader extends BaseUploader {
  @override
  List<Type> subscribedDataList() {
    return <Type>[
      FpsUploadData, //TimeUploadData, ScrollUploadData, ExceptionUploadData,
    ];
  }

  @override
  void upload(BaseUploadData data) {
    if (data is FpsUploadData) {
      _sendFPS(data.pageInfoData.pageName, data.avgFps);
    }
    ///...
  }
}

The Uploader can select the UploadData to be subscribed to by using subscribedDataList(), and receive and report notifications by using upload(). Theoretically, one Uploader corresponds to one upload channel. Users can report data to different places as needed by using the LocalLogUploader and NetworkUploader.

4.3.4 Overall Structural Design

An SDK can be divided into four layers and uses the publish/subscribe model extensively. The four layers communicate with each other through PerformanceDataCenter and PerformanceEventCenter, as shown in Figure 4-12. This mode can completely decouple layers and process data more flexibly.

1
Figure 4-12

4.3.4.1 API

The API layer contains some externally exposed APIs. For example, init() needs to be called before runApp(), and the business layer needs to call the pushEvent() method to provide some calling opportunities for the SDK.

4.3.4.2 Recorder

The Recorder layer uses the opportunities provided by events to collect source data and submits the data to the Processor that has subscribed to the data for processing. For example, the duration of each frame in the FPS collection is a piece of source data. This layer is designed to use the source data in different places. For example, the duration of each frame can be used to calculate the FPS and the number of lagging seconds.

Before the source data is used, the BaseRecoder must be inherited to select the event for subscription through subscribedEventList(). Then, it processes received event through onReceivedEvent().

abstract class BaseRecorder with TimingObserver {
  BaseRecorder() {
    PerformanceEventCenter().subscribe(this, subscribedEventList());
  }
}
mixin TimingObserver {
  void onReceivedEvent(BaseEvent event);

  List<Type> subscribedEventList();
}

4.3.4.3 Processor

At this layer, the source data is processed into the final data that can be reported and is submitted to the Uploader that has subscribed to the data for reporting. For example, in the FPS collection, the FPS over a period of time can be obtained based on the collected duration of each frame.

The BaseProcessor must be inherited to select the subscribed data type through subscribedDataList(). Then, it processes the received data through process().

abstract class BaseProcessor{
  void process(BaseData data);

  List<Type> subscribedDataList();

  BaseProcessor(){
    PerformanceDataCenter().registerProcessor(this, subscribedDataList());
  }
}

4.3.4.4 Uploader

This layer is implemented by users because they want to report data to different places. Therefore, the SDK provides a base class. You only need to follow the specifications of the base class to obtain the subscribed data.

The BaseUploader must be inherited to select the subscribed data type through subscribedDataList(). Then, it processes the received UploadData through upload().

abstract class BaseUploader{

  void upload(BaseUploadData data);

  List<Type> subscribedDataList();

  BaseUploader(){
    PerformanceDataCenter().registerUploader(this, subscribedDataList());
  }
}

4.3.4.5 PerformanceDataCenter

This layer receives BaseData (source data) and UploadData (processed data), and distributes the call opportunities to the Processor and Uploader that have subscribed to them for processing.

In the BaseProcessor and BaseUploader constructors, the register method of the PerformanceDataCenter is called for registration. This operation stores Processor and Uploader instances in two corresponding maps of the PerformanceDataCenter. This data structure allows one DataType to map multiple subscribers.

final Map<Type, Set<BaseProcessor>> _processorMap = <Type, Set<BaseProcessor>>{};

final Map<Type, Set<BaseUploader>> _uploaderMap = <Type, Set<BaseUploader>>{};

As shown in Figure 4-13, when the PerformanceDataCenter.push() method is called to push data, data is distributed based on the data type, and is sent to all the Processors or Uploaders that have subscribed to the data type.

2
Figure 4-13

4.3.4.6 PerformanceEventCenter

The design idea of this layer is similar to the PerformanceDataCenter layer. However, this layer receives events (the opportunities) provided by the business layer and distributes these opportunities to their Recorders that have subscribed to them for processing. The main types of events are:

  • App Status: An app is switched between the frontend and backend.
  • Page Status: The frame rendering starts or ends.
  • Business Status: Pop or push events occur on pages, pages slide, or exceptions occur in the business.

4.3.5 Use of the SDK

We need to know that the Flutter High-Availability SDK only collects data and that subsequent data reporting and data presentation must be customized based on actual conditions. Based on this, the following briefly introduces how to use the high-availability SDK.

To use the SDK, you only need to focus on the API and Uploader layers and perform the following operations:

  • Reference the high-availability SDK in Pubspec
  • Call the init() method to initialize the SDK before calling the runApp() method
  • In the business code, use the pushEvent() method to provide some necessary opportunities for the SDK, such as the pop and push events of the route
  • Customize an Uploader class to report the data to the data collection platform in your desired format

4.3.6 Implementation of the SDK

Xianyu has optimized the data accuracy of the Flutter High-Availability SDK many times, solved many problems in exception scenarios, and implemented a refactor. So far, the SDK has been running stably on Xianyu. No stability issues caused by the high-availability SDK have occurred, and the data collection accuracy has stabilized after several optimizations.

Flutter high availability focuses on the collection of performance data. Therefore, Xianyu needs to use the existing capabilities of the Alibaba Group for data reporting and data presentation. Xianyu uses the EMAS backend data processing and frontend data presentation capabilities of Taobao Mobile to report and display the data collected online by the high-availability SDK. This allows Flutter pages to compete with native pages.

0 0 0
Share on

XianYu Tech

56 posts | 4 followers

You may also like

Comments