1. Why practice Flutter

Before talking about Flutter, let’s talk about cross-platform technologies. Since 2010, Hybrid, React Native, Weex, Flutter and other cross-platform technologies have appeared in the market. They address the core concerns of App applications, such as R&D efficiency, consistent performance experience and dynamic. Considering the current situation of Hello, the coexistence of multiple business lines, local life direction of business development and the company’s advocacy of development and efficiency requirements, the solution of the core issues just mentioned is extremely urgent. Flutter was chosen because it runs on both major mobile operating systems and its pixel-level restore ensures a consistent UI across platforms. (Hot Reload/Attach Debug) a whole set of mechanics to ensure efficient development.

2. Business characteristics and technical pain points

With the increase of business, the package of The main App of Hello Travel has become larger and the package size, too many dynamic libraries and other structural governance problems are imminent. The company’s local life direction transformation and development may lead to explosive growth of subsequent business. The company has been trying to Flutter for a long time, but the development process management is in disorder at present. All business lines are not unified in the depth of Flutter and the accumulated native development thinking, the technology stack is not unified, and various code styles and habits coexist. As a result, Flutter does not significantly improve the whole mobile business line. Therefore, in order to ensure the steady development of all businesses, Build a UNIFIED Flutter microservice architecture to avoid invalid development. The containerized Flutter microarchitecture can also speed up the business realization, accelerate the rapid transformation of technology, reduce technology cost, and achieve the consistency of multi-end business implementation.

3, Flutter mixed engineering structure

The Flutter project currently has four types: App, Module, Plugin and Package

  • App: The standard Flutter app project consists of the standard Dart layer and Native platform layer
  • Module: The Purpose of the Flutter shell project is to combine all the Flutter components into a package and generate AAR and POD for app project reference
  • Plugin: a three-pronged Flutter component referenced by module
  • Package: Pure DART side code, a Flutter component that is referenced by Module

The type of Flutter engineering varies depending on the compilation product, The App Project will eventually incorporate the Flutter Module by introducing App. Framework (iOS) and VM /isolate_snapshot_data/instr(Android)

4. Microservices architecture

The architecture overview

Micro service architecture is the core of service independently, after the container package flat layer can interact with each other again, strictly follow the hierarchy, from top to bottom, according to the original developer can quickly adapt to the dart related development, the developer can start with the most familiar native platform layer, the protocol layer, the Channel up step by step, familiar with development, Once you are familiar with the Flutter code, you can delve into UI development and related business development.

Halo has its own build up of the Flutter Engine(based on 1.12.13+ Hotfix.9). Middleware services of the Engine layer, such as calculation of packet size, non-trace burial point, and multimedia resource reuse, are provided. Many functions are required to customize the Flutter Engine internally.

5. What is Sparrow

Sparrow is a Flutter microservice container architecture built by Hallo Travel to solve the confusion of Flutter development process management, the ununity of various business lines on Flutter depth and the accumulated native development thinking, the ununity of technology stack and the coexistence of various code styles and habits. The Api layer is exposed externally, the constraint service layer is managed, and the existing logic of the engine layer is optimized internally to optimize message transfer and improve the stability of Flutter internal versions.

6. What does Sparrow do

The isolated hybrid engineering system avoids the business architecture with unified standards for multi-end differences, provides consistent basic service definition, efficient reuse of basic middleware facilities, and cohesion of native basic service optimization channel transmission, so as to improve the stability of the engine and expand the existing functions of the engine. Dynamic support on Android terminal is not supported on iOS terminal

Manages common UI components, dynamic lists, Bloc and state management

7. Sparrow design and implementation

7.1 sparrow design Overview

Using the buried service as an example, the business, acting as the service requirement originator, initiates the buried requirement and invokes the container API provided by Plume:

ReportAnalytics.reportAnalytics(categoryId,eventId,params);

The internal plume package will functionReportAnalytics.reportAnalytics()Convert to a set of execution intent parameter information: Path (action: / track/reportAnalytics), nativeParams (categoryId, eventId, params), are sent by the sparrow model transformation and selecting appropriate methodChannel burial site service report The Analytics Action, Sparrow internally decides whether the service invocation is dart service or Native base service, and then uses reflection or compile-time injection at the Native service transformation layer to find the appropriate class to perform the reportAnalytics action. Native service transition implementation services search, transmission model transformation related operations, such as the core idea is to make the path (action: / track/reportAnalytics) through native ability to find the right executives, to ensure that does not affect the current native library service.

7.2 Sparrow Plugin Design

The core design idea of Flutter is that Everything’s a widget is a widget. In the world of Flutter, the concepts of views,view controllers,layouts, and more are all based on widgets. Widgets are abstract descriptions of Flutter functionality. Sparrow Plugin has three concepts internally: Page routing, function service and dynamic list card (ListCell and GridCell) correspond to pages, functions and views under development. Sparrow’s core is unified management of these pages, functions and views that receive dynamic injection and output corresponding execution process. So internally we convert the corresponding concepts into external widgets through simple function conversions:

/// Service transformation function definition, by building the [ServiceSettings] model, through ServiceBuilder into the [Widget] unified management
typedef ServiceBuilder = Widget Function(ServiceSettings settings);

/// Dynamic list Cell card conversion function definition, by building [CardSettings] model, CardBuilder[Widget] unified management
typedef CardBuilder = Widget Function(CardSettings settings);

/// Page routing transformation function definition, unified management by building RouteSettings model, RouteBuilder
typedef RouteBuilder = Widget Function(RouteSettings settings);
Copy the code

Sparrow only needs to manage the ServiceBuilder, CardBuilder, and RouteBuilder widgets, and since these widgets are driven by the xxxSettings model, the Builder contains all the information needed for the related single function.

The Following uses ServiceBuilder management as an example to describe the management process:

  • Service Registration (iOS, Android, DART)
  • Function performs forwarding
  • Parameter model conversion
  • Channel data mock
  • The native terminal manages the search Service

7.2.1 Service Registration

The dart end:

///registered
registerServiceBuilder('action:/bike/getOrderList', 
        (settings) => BikeSerive(serviceSettings: settings));
​
///implementation
Future<dynamic> doAction() async{
  if ("getOrderList"= =this.serviceSettings.action) {
   /// Executing action logic
  }
  return Future.value('callback... ');
}
Copy the code

The iOS side:

@SparrowMethodMod(PlatformServive,platform)
@Sparrow_export_method(@selector(getUser:))
- (void)getUser:(NSDictionary *)user{
    JYFlutterResult callback = user[kSparrowMethodCallBackKey];
    callback(@"Native_callback: User data model...");
}
Copy the code

The Android side:

@SparrowService("platform")
public class PlatformService implements BaseFlutterService {
    @SparrowMethod("getUser")
    public void getUserInfo(ServiceEntity serviceEntity) {
        serviceEntity
        .getCallback()
        .onSuccess("Native_callback: User data model..."); }}Copy the code

The @SparRowMethodMod (PlatformServive, Platform) on iOS and @SparrowService(“platform”) on Android belong to the module service alias, which identifies the module to which the service belongs. The native side module service implementation, if scattered, is also cohesive through this alias.

Dart side use:

// By returning a value
var orderList = SparrowManager.doAction('action:/bike/getOrderList', actionParams);

/ / user behavior acquisition (action/track/reportAnalytics) :
await ReportAnalytics.reportAnalytics(categoryId,eventId,params);

/ / equipment information (action: / system/getSystemInfo)
var systemInfo = System.getSystemInfo();
Copy the code

7.2.2. The function performs forwarding

class Analytics {
  static Future<dynamic> reportAnalytics(
      {Map<String.String> params}
      ) async {
    varnativeParams = ... ;return await SparrowManager
                 .doAction("action:/track/reportAnalytics", nativeParams); }}Copy the code

Sparrow provides both function and URL service calls. Function calls handle compile-time checking, internal conversion to easily managed URL calls, and service initialization uses lazy loading to centrally process common models and contexts.

7.2.3 Mock channel data

var args = {
  'method': 'getUserInfo'
};
​
var res = {
  "code":SparrowCode.Pass.index,
  "msg":"msg"."data": {"name":"The mock data"}};///Changes the service call with the specified parameter to return the specified RES
ChannelMockUtil.instance.createMock(args, res);
Copy the code

Change the service call with the specified parameters to return the specified RES. Within Sparrow, rewrite the original channel’s methodCallHandler to create a standby channel while rewriting the channel’s data flow, and keep the normal transmission of unmock data.

7.2.4 Native terminal Manages search services

SparrowInvocationConfig for iOS manages a set of module services in the form of annotations by the compiler:

// the module is injected into __DATA at compile time
#define SparrowMethodDATA(sectname) __attribute((used, section("__DATA,"#sectname"")))
​
#define SparrowMethodMod(name,alias) \
class NSObject; char * k##name##_mod SparrowMethodDATA(SparrowService) = "{ \""#name"\" : \""#alias"\"}";
​
//2, search for the __DATA injection class specified method when loading image
#define Sparrow_export_method(method) SPARROW_EXPORT_METHOD_INTERNAL(method,fm_export_method_)
​
#define SPARROW_EXPORT_METHOD_INTERNAL(method, token) \
class NSObject; \
+ (NSString *)FM_CONCAT_WRAPPER(token, __LINE__) { \
    returnNSStringFromSelector(method); The \}Copy the code
  1. Compile time injects the module name and the corresponding alias into section __DATA
  2. The tagged method of the section __DATA injected module class when loading the image
  3. SparrowInvocationConfig is created for each module for dart incoming data to hit

Android end use AutoService annotations in the class will compile time SparrowInvocationService logo written to the meta-inf/services/com. Hellobike. Sparrow. Outside. ISparrowInvocat IonService file and read at run time for incoming data to be hit by dart.

7.3. Plume Package design

Plume is designed to decouple the basic services from Sparrow. Plume contains all the basic services provided to dart by a native App. Incoming data is computed and processed within plume to close the service implementation details. Internal functions of native app are divided by Module and Service, and managed by POD and Maven. The functions of plume are relatively centralized, independent and complete. Channels provided by Sparrow will correspond with these Module services one by one, so as to hide the implementation details of iOS and Android and unify the standard business architecture. Provide consistent underlying service definitions.

7.3.1 Overview of Basic Services

Currently, the basic services of Hello Travel are divided into the following items. Managed by POD and Maven, the functions of Plume are relatively centralized, independent and complete. The channels provided by Sparrow correspond to the services one by one, so as to hide the implementation details of iOS and Android, unify the standard business architecture and provide a consistent definition of the basic services.

7.4 Design of native service conversion layer

The core problem of the design of native service transformation layer is to expand the information carried by DART into PATH in two dimensions and hit the appropriate method, and solve the problem of layer to layer dependence through model transformation. 7.4.1 The implementation of iOS Native end design service is difficult to deal with through hard coding, so a layer of transformation is made. Take the network service as an example: You need to implement a POST function for the DART layer to use:

@Sparrow_export_method(@selector(post:))
- (void)post:(NSDictionary *)dict{
    / / implementation
}
Copy the code

After conversion layer processing:

@Sparrow_export_method(@selector(post:params:success:failure:))
- (void)post:(NSString *)url
      params:(NSDictionary *)params
     success:(SparrowNetworkSuccess)success
     failure:(SparrowNetworkFailure)failure {
     / / implementation
}
Copy the code

Principle of conversion layer:

  • Define a layer protocol:
@protocol SparrowNetworkServive <NSObject>
- (void)post:(NSString *)url
      params:(NSDictionary *)params
     success:(SparrowNetworkSuccess)success
     failure:(SparrowNetworkFailure)failure;
@end
Copy the code

To implement network layer services, you need to inherit the SparrowNetworkServive protocol.

  • Create a forwarding class:

The SparrowNetworkInvocation class uses the @sparrowserviceprotocol macro to bind to the SparrowNetworkServive protocol The SparrowNetworkInvocation class explicitly calls the post function in the invocation function. The container provides a layer of strong code constraints to reduce the error rate.

@SparrowServiceProtocol(SparrowNetworkServive,SparrowNetworkInvocation)
@implementation SparrowNetworkInvocation
- (id)invocation:(SEL)action target:(NSObject *)target params:(NSDictionary *)params {
    NSObject<SparrowNetworkServive> *servive = (NSObject<SparrowNetworkServive> *)target;
    if (action == @selector(post:params:success:failure:)) {
        NSString *url = [self _url:params];
        JYFlutterResult result = params[kSparrowMethodCallBackKey];
        [servive post:url
               params:[self _params:params]
              success:[self _successResult:result]
              failure:[self _failureResult:result]];
        return@ (0); }}@end
Copy the code

7.4.2 Android Native Design

Use @SparRowInvocationService (“network”) to mark the current class as a conversion service, where “network” means to convert the service identified by @SparrowService; Need to implement ISparrowInvocationService interface, so that the transformation layer interface is unified.

@Override
public void invoke(Object targetObj, String action, final ServiceEntity serviceEntity){}
Copy the code

Invoke is a method that must be implemented by the transformed service class, in which targetObj is the transformed service instance. Here, a custom interface is required to be implemented by the service instance. After converting parameters passed by DART, the transformation layer invokes the corresponding method of Service through this custom interface. Action is the action that needs to be performed. ServiceEntity wraps parameters, callbacks, context, and so on.

// Identify the conversion service
@SparrowInvocationService("tangram")
public class SparrowTangramInvocation implements ISparrowInvocationService {
    @Override
    public void invoke(Object targetObj, String action, ServiceEntity serviceEntity) {
        // The converted service needs to implement a custom interface
        ISparrowTangramService service = (ISparrowTangramService)targetObj;
        Map<String,String> params = serviceEntity.getParams();
        // Determine the dart layer call action based on action
        if (Actions.getABTestIsEnable.equals(action)) {
            // Parameter processing
            // Call up the interface methodservice.getABTestIsEnable(xx,xx); }}}Copy the code

8, postscript

The emergence of container technology has changed the thinking of software delivery. Sparrow not only addresses r&d efficiency, consistent performance experience and dynamics, but also carries stability indicators, safety and efficiency indicators of Flutter business. In the future, Sparrow will continue to explore and verification in Android hot repair and multimedia resource sharing.