×
Community Blog Exploring Flutter-Native Hybrid Engineering Decoupling in Xianyu

Exploring Flutter-Native Hybrid Engineering Decoupling in Xianyu

This article explores problems with Flutter-Native engineering decoupling in Xianyu and offers solutions.

By Qiqing

1. Flutter Status Quo in Xianyu

Xianyu is the first large-scale application that uses Flutter hybrid development. The pain point of developing a Xianyu client is the terrible development experience caused by compilation duration. In the Flutter-Native development mode, the Native compilation speed is slow with unbreakable challenges in module development. Since Xianyu is integrated with many middlewares of Alibaba Group, many functions cannot be called directly by Flutter. Therefore, users have to call the corresponding functions through various channels to get to Native. Xianyu currently faces the following pain points in Flutter development:

  • The slow hybrid compilation on the Flutter side: The first compilation on Android and iOS requires over 10 minutes and 20 minutes, respectively.
  • The burden of history code in hybrid stack programming may cause that the data returned by iOS or Android to the Flutter side is inconsistent.
  • The efficiency of the Integration module development is lower than the module development, and the test performance data of the single-module page cannot be expanded.

2. Solution One

2.1 Overview

It has been a long time since this project was established. Due to the fast iteration of business and massive Native plug-ins, it is very difficult to achieve modular splitting of engineering. The following figure shows the project was initiated as modular splitting. On the business side, developers need to split and decouple all business modules, including the middleware of Alibaba Group, business encapsulation components, Native business code, Flutter bridge code, Flutter component library, and Flutter-side business code. The original intention of the project is to organize the code and provide a clean environment to run Flutter. Besides, it also helps Flutter obtain almost all the capabilities of Native. Unpacking will improve the speed and efficiency of compilation, development, and debugging. Specifically, the unpacking is to build a minimal shell project from 0 to 1. Then, it will split the basic middleware of Alibaba Group and encapsulate the business components and Flutter plug-ins. The following figure shows the overall project architecture:

1

The minimum shell project daily needs to be used on the single-module page. With the channel declaration and implementation embedded in it, the results will come after running the minimal shell project. On the Flutter side, the module development obtains the returned results through calling the channel of the minimum shell project in IOC. Finally, developers integrate the module development into the main project of Xianyu FWN in a pub or Git dependency mode.

2.2 Phased Output

Business module splitting has never been easy. It is beneficial, but the input-output ratio is unbalanced. The burden of history code is getting heavier, so the next recipient does not dare to modify the code. During the modular splitting, a new, clean engineering method was proposed at the beginning of the project, and then the middleware of Alibaba Group was split step by step. At last, Mtop, Login, FlutterBoost, and UI Plugin were split up, which took two developers three weeks to finish. The part of the result forms new business. The new interface can support the relatively rapid iterative development. The disadvantages are listed below:

  • It was complicated to split and sort Native middleware. It generated a heavy workload that took two developers three weeks to finish the minimal shell project.
  • It is more difficult to push the business side to split the basic component library, and the current project is in a bad way.
  • The maintenance cost is high. The operation results between shell project splitting and the main project may be inconsistent.
  • The business is urgent for its results, but the input-output ratio is unbalanced on the Flutter single page performance test, Flutter-side modular splitting, and Fass project unified cornerstone.

3. Solution Two

3.1 New Thoughts

  1. For the business side, it is costly to split the package on the Flutter side and build a minimal shell project.
  2. The unified Fass project relies on a Native runtime environment of minimal a shell project to run Flutter-side code. How can you develop a unified Fass project and module development in another environment of the Alibaba Group, since not all business parties provide a minimal shell project to run Fass?
  3. The running environment of a minimal shell project cannot follow the various versions on the Native side, which causes worries in using inconsistent running results.

How can you solve this problem? I proposed the cross-process method. On the Android side, the cross-process call has always been very a common method. The client accesses the server to obtain the results. The Flutter side and Native side operate as a client and a server. As shown in the following figure, Flutter obtains data through MethodChannel or EventChannel. So, there is a different way to think about it.

2

3.2 IPC Cross-Process Communication: Android Binder

On the Android side, I used Android Binder to implement it and started a new app as a shell project. I implemented various plug-ins to access the main project services. Therefore, Flutter in the shell project can call the returned results, but the maintenance cost is still needed. Also, there is no corresponding implementation mechanism on the iOS side, so this method is discarded.

3.3 Specific Solution: Hook Proxy and Socket Service

Android developers should be familiar with hook and plug-in technology. A new idea should be considered at the previous channel architecture from Flutter to Native. Since Native problems cannot be solved, we can solve the channel problems. Instead of implementing IPC on the Native side, we can implement IPC on the channel communication side of the Flutter side and the Native side. Based on the business understanding of the plug-in hook mechanism and IPC mechanism and my understanding of the Flutter channels, a new implementation can be achieved. The implementation method uses the socket service to implement the hook method channel and event channel for the client's method channel and event channel. Through the socket, the processed results are sent to the server to process and obtain real data from the method channel and the event channel. This is how I want it to work. The architecture is on the chart below:

3

The client and server operate through two mobile phones, respectively. The server runs the main project of Xianyu FWN, and the client runs a clean Flutter project. First, the client uses the Flutter-side code to find its corresponding channel. Return results if the channel is available. Otherwise, the request for the channel will be sent to the main project on the server through the socket, and the main project parses the protocol fields defined by the socket. Next, the main project initiates a channel to obtain the result and return it to the client through the socket. After the client obtains the result data from the socket, it performs the corresponding rendering.

You may wonder why it uses two phones instead of one:

  1. When two apps run on one cell phone, the process resources may be recycled if the server is running in the background, interrupting socket communication.
  2. While running the Flutter-side client code on Android, developers need to verify the double-device server code data on the Native side. If there are two mobile phones for the client side and server side, developers only need to change the IP address of the client to request the server on the Android mobile phone or the server on the iOS mobile phone to verify the result.

3.4 Verification

The method channel code is listed below:

Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) async {
    assert(method != null);
    final ByteData result = await binaryMessenger.send(
      name,
      codec.encodeMethodCall(MethodCall(method, arguments)),
    );
    if (result == null) {
      throw MissingPluginException('No implementation found for method $method on channel $name');
    }
    final T typedResult = codec.decodeEnvelope(result);
    return typedResult;
  }

The result = = null scenario is fixed. If it is the specified client, the server data is obtained through the socket. The major part lies in the understanding from Fish MOD:START to Fish MOD:END Code.

Future<T> invokeMethod<T>(String method, [dynamic arguments]) async {
    assert(method != null);
    final ByteData result = await binaryMessenger.send(
      name,
      codec.encodeMethodCall(MethodCall(method, arguments)),
    );
    if (result == null) {
      //Fish MOD:START
      //throw MissingPluginException(
      //      'No implementation found for method $method on channel $name');
      //socket obtains the value from the server-side mobile phone.
      final dynamic serverData =
            await SocketClient.methodDataForClient(clientParams);
      //Fish MOD:END
    }
    final T typedResult = codec.decodeEnvelope(result);
    return typedResult;
  }

Finally, the feasibility of normal data sending and receiving through MethodChannel and EventChannel is verified through this method, and more practices need to be carried out in specific business scenarios in the future.

4. Results Comparison and Prospects

Results Comparison:

Method Selection Splitting Cost Maintenance Cost Runtime
Solution One High High Runtime Runlevel
Solution Two Zero-Cost Zero-Cost Runtime Runlevel (Lower)

Solution one and solution two can eventually solve the runtime problem during compilation. However, solution one incurs a high cost for splitting and maintaining modules. Even though the runtime is reduced, the modular workload has increased a lot. Solution two can solve the splitting cost and maintenance cost perfectly, but it must be operated in a perfect environment. In addition, for some page jump logic, the operation on the client of mobile phone A may be triggered to the server of mobile phone B. Therefore, the operation is not performed on the same mobile phone. In conclusion, the second solution has some defects, and it can solve many problems. Therefore, in the follow-up implementation project of Xianyu modular splitting, the step for seeking a perfect solution won't stop.

0 0 0
Share on

XianYu Tech

56 posts | 4 followers

You may also like

Comments