Author: The technique of idle fish — Deer edge

The main page of Idle Fish community is implemented using Native, and part of it is carried out using Flutter and Weex. The processing of fixed data structures such as posts and topics, user interaction and status synchronization such as likes and comments are mostly repetitive, and the implementation price is extremely low in multi-technology stacks. Therefore, we wonder whether such a set of tools can be implemented on the end to liberate the labor force and get rid of the dependence on the BFF layer on the server to ensure the research and development efficiency. In the abstract, what we really need is a logical cross-platform tool. The concept of logical cross-platform has been around for a long time, and there are some great solutions to consider:

•C++ has mature interfaces to call Native languages such as Objective-C/Java, which makes C++ a natural cross-platform advantage. At the same time, it is undeniable that the entry threshold of C++ will be relatively high, resulting in high maintenance costs.

•KMM is JetBrains’ SDK for cross-platform mobile development that provides a language-level logical cross-platform solution that compiles direct code into the exact same format as the target platform.

Combining existing technical infrastructure within the team, Dart was used to ensure consistency of data and logic across platforms, saving human resources and ensuring user experience. Name Flutter Worker.

Overall architecture design

Before the overall design, the business scenario of worker should be defined first: the goal is to provide a multi-end reusable logic processing center. Data requests are initiated at the end, and all logical processing is closed at the end of FlutterWorker, which will be returned to the end after execution. In this scenario, the worker only needs to pay attention to the logical processing when writing the Handler. For the data incoming and receiving on the end, the developer only needs to specify the type, and the worker will automatically convert the data into this type.

Therefore, in the overall architecture design, we mainly consider the following aspects:

1.FlutterWorker requires a stable operating environment. As it involves time-consuming operations such as data processing, it is necessary to ensure that the DRAWING of UI interface is not affected. 2. The processed data must be used by multiple ends, and the data structure must be aligned. 3. Provide enough sections to facilitate the expansion of the access party. 4. In order to ensure the stability and reliability of the operation, it is necessary to add monitoring to discover problems in time and test and evaluate the performance before going online.

The overall architecture diagram is as follows:

Operation container layer: Containers are the basis of worker operation. On the premise of ensuring performance, existing technology infrastructure of Idle fish should be utilized as much as possible.

State management: The worker stores data and synchronizes state here. The end stores status data and receives changes in data by subscribing to status messages. Considering that the status management has no strong business demand at present, this part only stays in the Demo stage and has not been implemented, so it will not be explained later.

Data processing layer: it contains all the data processing parts of worker’s middle layer, mainly including Model object synchronization, data conversion and data type synchronization. Basic data types are naturally supported in Dart language, and there are corresponding types on Androiddr end of iOS end. Therefore, workers mainly work on the latter two.

Monitoring layer: Worker runs online, which requires monitoring to ensure timely problem discovery and timely response.

Each module is described in detail below.

Run container layer

The operating environment of FlutterWorker strongly depends on FlutterEngine and Isolate. From this point of view, the following three schemes are sorted out and compared.

It is worth mentioning that with the release of Flutter2.0, we noticed in the update log that there was an optimization for the memory consumption of multiple Flutter Engine instances. According to the official documentation, the static memory usage of the additional Flutter engine is reduced by about 99%, resulting in approximately 180KB per instance. Add FlutterEngineGroup class to create FlutterEngine.

Compared to the original API, FlutterEngine generated by FlutterEngineGroup has the performance advantage of commonly shared resources such as GPU context, font metrics, and snapshots of isolated threads, resulting in faster first rendering, lower latency, and lower memory footprint.

Meanwhile, the EngineGroup itself and its generated FlutterEngine do not need to keep alive continuously. As long as there is only one available FlutterEngine, resources can be shared among all flutterengines at any time. After the EngineGroup is destroyed, the generated FlutterEngine is not affected, but it cannot continue to create new engines based on the existing share.

Although currently FlutterEngineGroup is not a stable API, this opens up another possibility for FlutterWorker. Overall consideration, the current implementation chooses scheme 3 to achieve.

Data processing layer

Starting from user invocation, worker’s data flow diagram is as follows:

The circulation of data in the three terminals is through calling NatiaveWorker, executing to the corresponding WorkerHandler, and then asynchronously calling back to the Native method after processing. The middle part mainly involves the process of Model object synchronization and data conversion, where worker implements processing libraries respectively.

Model object synchronization

The first thing the worker needs to implement is OringinDataModel ==> TargetDataModel. The definition of TargetDataModel should be consistent across iOS, Android, and Flutter. Code generation is the best choice. Here reference some hardware protocol, specify IDL format to generate three-terminal Model and import project. The packaged dartGen library can parse these YAML nodes to generate the fat Model we need.

DartGen is packaged as a CLI tool for developers’ convenience. Imitating the GsonFormat plug-in, JSON data can be easily converted to the yamL protocol parsed by the corresponding dartGen in the IDE. For other requirements that generate models, templates can be modified at little cost and high customization.

Data conversion

To transmit data to Native through the MethodChannel of Flutter, a layer of messager encoding is required, and the encoded data is of basic type. Therefore, on the basis of BinaryMessager, WorkerBinaryMessenger is encapsulated to facilitate customization.

There are mature Model transformation libraries (YYModel and FastJson) on iOS and Android. Dart also customizes fish_serializable, providing toJson and fromJson methods in the generated Model to make it easy to convert all types of data, including embedded generic classes such as Maps and lists.

Monitoring scheme design

Each monitoring solution has its limitations in business scenarios, and at this stage, logical consistency is the main consideration. The single logical closed-loop in worker is similar to the process of TCP handshake and wave, and the data flow in the application is similar to message queue, and its transmission process is shown in the figure below:

Thus, we can determine the flow chart of workerMonitor running as follows:

Performance monitoring

In this mode, the mainstream monitoring scheme observes the queue data of the initiator and receiver according to the time slice. There are two ways of time slice. The first way is to set a timer, and the second way is to process data every time there is a call.

Slicing scheme

advantages

disadvantages

Timer slice

Slice implementation is simple and the data in slice is accurate

High performance loss, there will be a lot of invalid statistics

Slice per call

Low performance loss

Data in a slice is the average value. The last slice data in an application cannot be monitored when the first slice data is blocked

In addition, there are two reporting methods: real-time reporting and aggregated reporting. Given the current worker when using the scene the leisure and the obvious distinguish when busy, and end on the long in the frequency of regular reporting tasks can have certain influence on the UI map, using every time call statistic data in the queue, at the same time increase the timeout timer after each call, if has the next call timeout timer is cancelled.

indicators

For online monitoring indicators, see RabbitMQ monitoring to define the indicators to be observed in the initial stage. The initial core focuses on message loss and message latency. Therefore, we determined the following indicators:

The Exception monitoring

Referring to the MONITORING of CRASH by UI engine, exception information and exception stack will be uniformly accepted at the entry function and reported for processing. This is explained in an article about Flutter high availability.

Feasibility verification

Finally, the call code and the corresponding Handler example are as follows:

• the iOS side:

The Android end:

Handler:

In order to test whether our scheme can meet the online standards, we did a violence test in the scene of playing the home page to verify. Compare the processing data of normal Native code and worker, and test the performance of CPU, memory and delay under the condition of multiple concurrent access/ordered access.

Through several comparison tests, the short-term memory and CPU water level increment of FlutterWorker scheme was less than 30M, and the long-term memory and CPU water level increment was less than 5M in common and high-pressure request scenarios, and the delay was less than 5% of the original scheme.

After three-terminal access, data processing can be used by multiple terminals with only one human input, which greatly reduces repetitive work and improves research and development efficiency.

Summary and Outlook

Currently, cross-platform is one of the trends in mobile development, and various companies and teams have their own ideas. The fundamental purpose is to improve the efficiency of research and development and reduce maintenance costs. Each scheme has its own unique advantages, so it is necessary to rationally analyze the current situation of the team and business and choose a suitable scheme.

The Flutter Worker approaches the Flutter Worker from a logical cross-platform perspective and achieves some good results. However, we have only completed a small part of the work at present, and there are still many unfinished areas. In the future, we will continue in-depth research from the following aspects:

1. MethodChannel optimization. The communication of data channel relies heavily on MethodChannel, and its performance can cover the current business scenarios according to the current test results. However, in the long run, the channel will become the bottleneck of worker performance. Taking inspiration from dart_native library, it is feasible to replace native channels with type mapping Pointers. Unit testing. Logical processing functions have clear input and output, writing single test is very efficient. In addition, many products with rapid iteration add new logic on the basis of original logic, so the cost performance of writing single test is higher. 3. Front-end container support. The previous version only supports iOS, Android and Flutter, and does not have good support for Weex and H5 front-end pages. The future is expected to provide an elegant way to access front-end containers.

This is a small attempt by idle fish to explore the path of Flutter. Please keep your eyes on it.