Use it on the code to start Flutter hybrid development - FlutterBoost

Abstract:  Apps of a certain scale usually have a set of mature and general basic libraries, especially Ali apps, which generally need to rely on many basic libraries in the system.

Open source address: https://github.com/alibaba/flutter_boost

Why mix solutions

Apps with a certain scale usually have a set of mature and general basic libraries, especially Ali apps, which generally need to rely on many basic libraries in the system. Then the cost and risk of developing an app from scratch using Flutter are high. Therefore, incremental migration in Native App is a robust way to apply Flutter technology to existing Native App.

Xianyu precipitated a set of its own mixed technical solutions in practice. During this process, we have communicated closely with the Google Flutter team, listened to some official suggestions, and also made selections and specific implementations of solutions according to the specific circumstances of our business.

Officially proposed hybrid solution

1 Fundamentals

The Flutter technology chain is mainly composed of the Flutter Engine implemented by C++ and the Framework implemented by Dart (its supporting compilation and construction tools are not discussed here). Flutter Engine is responsible for thread management, Dart VM state management, and Dart code loading. The Framework implemented by the Dart code is the main API that the business is exposed to. Concepts such as Widget are the Framework content at the Dart level.

At most one Dart VM can be initialized in a process. However, a process can have multiple Flutter Engines, and multiple Engine instances share the same Dart VM.

Let's look at the specific implementation. Every time a FlutterViewController is initialized on iOS, an engine will be initialized, which means that there will be a new thread (theoretically, threads can be reused) to run Dart code. Android similar Activity will have a similar effect. If you start multiple engine instances, note that the Dart VM is still shared at this time, but the code loaded by different Engine instances runs in separate Isolates.

2Official advice

Engine deep sharing

In terms of hybrid options, we discussed some possible options with Google. The official suggestion given by Flutter is that in the long run, we should support the ability to support multi-window drawing in the same engine, at least logically, the FlutterViewController shares the resources of the same engine. In other words, we want all drawing windows to share the same main Isolate.

But the official long-term advice is currently not well supported.

multi-engine mode

The main problem we solve in the hybrid solution is how to deal with alternating Flutter and Native pages. Google engineers gave a Keep It Simple solution: For continuous Flutter pages (Widgets), it only needs to be opened in the current FlutterViewController, and for intermittent Flutter pages, we initialize a new engine.

For example, we perform the following set of navigation operations:

We just need to create different Flutter instances in Flutter Page1 and Flutter Page3.

The advantage of this solution is that it is easy to understand and logically clear, but there are also potential problems. If a Native page and a Flutter
page are alternated, the number of Flutter Engines will increase linearly, and the Flutter Engine itself is a relatively heavy object.

Problems with multi-engine mode

  • Redundant resource problem. Isolate between each engine in multi-engine mode is independent of each other. Logically, there is no harm in this, but the bottom layer of the engine actually maintains memory-consuming objects such as image cache. Imagine that each engine maintains its own image cache, and the memory pressure will be very high.
  • Plugin registration problem. Plugins rely on Messenger to deliver messages, and currently Messenger is implemented by FlutterViewController (Activity). If you have multiple FlutterViewControllers, the registration and communication of plugins will become confusing and difficult to maintain, and the source and destination of message delivery will become uncontrollable.
  • Page differentiation between Flutter Widget and Native. Flutter's page is Widget, and Native's page is VC. Logically, we hope to eliminate the differences between Flutter pages and Naitve pages, otherwise additional complexity will be encountered when performing page embedding and other unified operations.
  • Increase the complexity of communication between pages. If all Dart code runs on the same engine instance, they share an Isolate, and a unified programming framework can be used for communication between widgets. Multiple engine instances also make this more complicated.

Therefore, considering various aspects, we did not adopt a multi-engine hybrid solution.

Current Situation and Thinking

We mentioned earlier that there are some practical problems with multiple engines, so the hybrid solution currently adopted by Xianyu is a solution that shares the same engine. This solution is based on the fact that we can only see at most one page at any time, and of course there are certain scenarios where you can see multiple ViewControllers, but we won't discuss these special scenarios here.

We can understand this solution simply: we use the shared Flutter View as a canvas, and then use a Native container as the logical page. Every time we open a container, we notify the Flutter View to draw the current logical page through the communication mechanism, and then put the Flutter View into the current container.

This solution cannot support the existence of multiple level logical pages at the same time, because you must operate from the top of the stack when switching pages, and it is no longer possible to perform level switching while maintaining the state. For example: There are two pages A and B, and B is currently at the top of the stack. Switching to A requires popping B from the top of the stack. At this time, the state of B is lost. If we want to switch back to B, we can only reopen the state of the page before B, which cannot be maintained.

For example, in the process of pop, the official Dialog of Flutter may be killed by mistake. And for stack-based operations, we rely on a property modification of the Flutter framework, which makes this solution intrusive.

For details, you can refer to the open source project address of the old scheme:

https://github.com/alibaba-flutter/hybridstackmanager

A new generation of hybrid technology solutions FlutterBoost

1 Refactoring plan

In the process of Xianyu promoting Flutter, more complex page scenarios gradually exposed the limitations and problems of the old scheme. So we started a new hybrid solution codenamed FlutterBoost (a nod to the C++ Boost library). Our main goals for this new hybrid program are:

  • Reusable general-purpose hybrid solution
  • Support for more complex hybrid modes, such as supporting the homepage Tab
  • Non-intrusive solution: no longer rely on modifying Flutter's solution
  • Support common page life cycle
  • Unified and clear design concept

Similar to the old scheme, the new scheme is still implemented in a shared engine mode. The main idea is that the native container Container drives the Flutter page container Container through messages, so as to achieve the synchronization purpose of the Native Container and the Flutter Container. We hope that the content rendered by Flutter is driven by the Naitve container.

Simply understand, we want to make the Flutter container feel like a browser. Fill in a page address, and then the container manages the drawing of the page. On the Native side, we only need to care if the container is initialized, and then set the page flag corresponding to the container.

2 Main Concepts

3Native layer concept

  • Container: Native container, platform Controller, Activity, ViewController
  • Container Manager: The manager of the container
  • Adaptor: Flutter is the adaptation layer
  • Messaging: Channel-based message communication

4Dart Layer Concept

  • Container: The container used by Flutter to hold Widgets, which is implemented as a derived class of Navigator-
  • Container Manager: Management of Flutter containers, providing APIs such as show and remove
  • Coordinator: The coordinator, which accepts Messaging messages, is responsible for calling the state management of the Container Manager.
  • Messaging: Channel-based message communication

5 Understanding of the page

The objects and concepts of representing pages in Native and Flutter are inconsistent. In Native, our concept of pages is generally ViewController, Activity. For Flutter, our concept of pages is Widget. We hope to unify the concept of pages, or weaken the page concept corresponding to the Widget that abstracts Flutter itself. In other words, when a Native page container exists, FltteBoost guarantees that there must be a Widget as the content of the container. Therefore, when we understand and perform routing operations, we should take the Native container as the criterion. Flutter Widget depends on the state of the Native page container.

Then when we talk about pages in the concept of FlutterBoost, we refer to the Native container and its attached Widget. All page routing operations, opening or closing pages, are actually direct operations on the Native page container. No matter where the routing request comes from, it will eventually be forwarded to Native to implement the routing operation. This is also the reason why the Platform protocol needs to be implemented when accessing FlutterBoost.

On the other hand, we cannot control the business code to push new Widgets through the Navigator of Flutter itself. For the case where the business directly uses the Navigator to operate widgets without using FlutterBoost, including non-full-screen widgets such as Dialog, we recommend that the business itself be responsible for managing its state. This type of Widget does not belong to the page concept defined by FlutterBoost.

Understanding the page concept here is crucial to understanding and using FlutterBoost.

6 The main difference from the old scheme

Earlier we mentioned that the old solution maintains a single Navigator stack structure at the Dart layer for Widget switching. The new solution introduces the concept of Container on the Dart side, instead of using the stack structure to maintain existing pages, but maintaining all current pages in the form of flattened key-value mapping, each page has a unique id. This structure naturally supports the search and switching of pages, and is no longer subject to the problem of stack top operation, and some previous problems caused by pop are easily solved. There is also no need to rely on modifying the Flutter source code to perform page stack operations, which removes the intrusiveness of implementation.

In fact, the Container we introduced is the Navigator, that is to say, a Native container corresponds to a Navigator. So how is this done?

7 Implementation of Multiple Navigator

Flutter provides an interface for you to customize Navigator at the bottom. We implemented an object that manages multiple Navigators. Currently, there is at most one visible Flutter Navigator, and the page contained in this Navigator is the page corresponding to our currently visible container.

There is a one-to-one correspondence between the Native container and the Flutter container (Navigator), and the life cycle is also synchronized. When a Native container is created, a Flutter container is also created, and they are associated with the same id. When the Native container is destroyed, the Flutter container is also destroyed. The state of the Flutter container is to follow the Native container, which is what we call the Native driver. The Manager manages and switches the containers currently displayed on the screen in a unified manner.

We describe the process of creating a new page with a simple example:

  • Create a Native container (iOS ViewController, Android Activity or Fragment).
  • The Native container notifies the Flutter Coordinator that a new container is created through a message mechanism.
  • The Flutter Container Manager is then notified and is responsible for creating the corresponding Flutter container and loading the corresponding Widget page in it.
  • When the Native container is displayed on the screen, the container sends a message to the Flutter Coordinator to notify the id of the page to be displayed.
  • The Flutter Container Manager finds the Flutter Container with the corresponding id and sets it as the visible container in the foreground.

This is the main logic for creating a new page. Operations such as destroying and entering the background are also driven by Native container events.

 

Summarize

At present, FlutterBoost has supported all Flutter-based development services in the Xianyu client in the production environment, provided support for more complex mixed scenarios, and stably provided services for hundreds of millions of users.

At the beginning of the project, we hoped that FlutterBoost could solve the general problem of connecting to Flutter in the hybrid mode of Native App. So we made it a reusable Flutter plugin, hoping to attract more interested friends to participate in the construction of the Flutter community. In the limited space, we shared the experience and code accumulated by Xianyu in the Flutter hybrid technology solution. Interested students are welcome to actively communicate and study with us.

Extended supplement

1 Performance related

When switching between two Flutter pages, because we only have one Flutter View, we need to save the screenshot of the previous page. If there are many screenshots of the Flutter page, it will take up a lot of memory. Here we use the file memory L2 cache strategy, only save 2-3 screenshots in memory at most, and load the rest of the written files on demand. In this way, we can maintain a relatively stable level of memory while ensuring the user experience.

In terms of page rendering performance, Flutter's AOT advantages are fully revealed. When pages are switched quickly, Flutter can switch the corresponding pages very sensitively, logically creating a feeling of multiple pages in Flutter.

2Release1.0 support

At the beginning of the project, we developed based on the Flutter version currently used by Xianyu, and then conducted the Release 1.0 compatibility upgrade test and found no problems.

3 access

As long as it is a project that integrates Flutter, it is very convenient to introduce FlutterBoost in the form of a plug-in in the form of official dependencies. It only needs a small amount of code access to the project to complete the access.
For detailed access documentation, please refer to the official project documentation on the GitHub homepage.

4 is now open source

At present, the new generation of hybrid stack has been fully applied in Xianyu. We are very happy to give back the accumulated technology to the community. Everyone is welcome to contribute, communicate together, and work together to build the Flutter community.

Project open source address: https://github.com/alibaba/flutter_boost

Original link

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324206464&siteId=291194637