溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Flutter Boost的簡介及使用

發(fā)布時間:2021-08-24 17:23:23 來源:億速云 閱讀:831 作者:chen 欄目:開發(fā)技術(shù)

本篇內(nèi)容主要講解“Flutter Boost的簡介及使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“Flutter Boost的簡介及使用”吧!

目錄
  • 一、Flutter Boost簡介

  • 二、Flutter Boost集成

    • 2.1 Android集成

    • 2.2 iOS集成

  • 三、Flutter Boost架構(gòu)

    • 四、FlutterBoost3.0更新

      • 4.1 不入侵引擎

      • 4.2 不區(qū)分Androidx和Support分支

      • 4.3 雙端設(shè)計統(tǒng)一,接口統(tǒng)一

      • 4.4 支持 【打開flutter頁面不再打開容器】 場景

      • 4.5 生命周期的精準(zhǔn)通知

      • 4.6 其他Issue

    一、Flutter Boost簡介

    眾所周知,F(xiàn)lutter是一個由C++實現(xiàn)的Flutter Engine和由Dart實現(xiàn)的Framework組成的跨平臺技術(shù)框架。其中,F(xiàn)lutter Engine負責(zé)線程管理、Dart VM狀態(tài)管理以及Dart代碼加載等工作,而Dart代碼所實現(xiàn)的Framework則負責(zé)上層業(yè)務(wù)開發(fā),如Flutter提供的組件等概念就是Framework的范疇。

    隨著Flutter的發(fā)展,國內(nèi)越來越多的App開始接入Flutter。為了降低風(fēng)險,大部分App采用漸進式方式引入Flutter,在App里選幾個頁面用Flutter來編寫,但都碰到了相同的問題,在原生頁面和Flutter頁面共存的情況下,如何管理路由,以及原生頁面與Flutter頁面之間的切換和通信都是混合開發(fā)中需要解決的問題。然而,官方?jīng)]有提供明確的解決方案,只是在混合開發(fā)時,官方建議開發(fā)者,應(yīng)該使用同一個引擎支持多窗口繪制的能力,至少在邏輯上做到FlutterViewController是共享同一個引擎里面的資源。換句話說,官方希望所有的繪制窗口共享同一個主Isolate,而不是出現(xiàn)多個主Isolate的情況。不過,對于現(xiàn)在已經(jīng)出現(xiàn)的多引擎模式問題,F(xiàn)lutter官方也沒有提供好的解決方案。除了內(nèi)存消耗嚴(yán)重外,多引擎模式還會帶來如下一些問題。

    • 冗余資源問題。多引擎模式下每個引擎的Isolate是相互獨立的,雖然在邏輯上這并沒有什么壞處,但是每個引擎底層都維護了一套圖片緩存等比較消耗內(nèi)存的對象,因此設(shè)備的內(nèi)存消耗是非常嚴(yán)重的。

    • 插件注冊問題。在Flutter插件中,消息傳遞需要依賴Messenger,而Messenger是由FlutterViewController去實現(xiàn)的。如果一個應(yīng)用中同時存在多個FlutterViewController,那么插件的注冊和通信將會變得混亂且難以維護。

    • Flutter組件和原生頁面的差異化問題。通常,F(xiàn)lutter頁面是由組件構(gòu)成的,原生頁面則是由ViewController或者Activity構(gòu)成的。邏輯上來說,我們希望消除Flutter頁面與原生頁面的差異,否則在進行頁面埋點和其它一些操作時增加一些額外的工作量。

    • 增加頁面通信的復(fù)雜度。如果所有的Dart代碼都運行在同一個引擎實例中,那么它們會共享同一個Isolate,可以用統(tǒng)一的框架完成組件之間的通信,但是如果存在多個引擎實例會讓Isolate的管理變得更加復(fù)雜。

    如果不解決多引擎問題,那么混合項目的導(dǎo)航棧如下圖所示。

    Flutter Boost的簡介及使用

    目前,對于原生工程混編Flutter工程出現(xiàn)的多引擎模式問題,國內(nèi)主要有兩種解決方案,一種是字節(jié)跳動的修改Flutter Engine源碼方案,另一種是閑魚開源的FlutterBoost。由于字節(jié)跳動的混合開發(fā)的方案沒有開源,所以現(xiàn)在能使用的就剩下FlutterBoost方案。

    FlutterBoost是閑魚技術(shù)團隊開發(fā)的一個可復(fù)用頁面的插件,旨在把Flutter容器做成類似于瀏覽器的加載方案。為此,閑魚技術(shù)團隊為希望FlutterBoost能完成如下的基本功能:

    • 可復(fù)用的通用型混合開發(fā)方案。

    • 支持更加復(fù)雜的混合模式,比如支持Tab切換的場景。

    • 無侵入性方案,使用時不再依賴修改Flutter的方案。

    • 支持對頁面生命周期進行統(tǒng)一的管理。

    • 具有統(tǒng)一明確的設(shè)計概念。

    并且,最近Flutter Boost升級了3.0版本,并帶來了如下的一些更新:

    • 不侵入引擎,兼容Flutter的各種版本,F(xiàn)lutter sdk的升級不需要再升級FlutterBoost,極大降低升級成本。

    • 不區(qū)分Androidx和Support分支。

    • 簡化架構(gòu)和接口,和FlutterBoost2.0比,代碼減少了一半。

    • 雙端統(tǒng)一,包括接口和設(shè)計上的統(tǒng)一。

    • 支持打開Flutter頁面,不再打開容器場景。

    • 頁面生命周期變化通知更方便業(yè)務(wù)使用。

    • 解決了2.0中的遺留問題,例如,F(xiàn)ragment接入困難、頁面關(guān)閉后不能傳遞數(shù)據(jù)、dispose不執(zhí)行,內(nèi)存占用過高等。

    二、Flutter Boost集成

    在原生項目中集成Flutter Boost只需要將Flutter Boost看成是一個插件工程即可。和其他Flutter插件的集成方式一樣,使用FlutterBoost之前需要先添加依賴。使用Android Studio打開混合工程的Flutter工程,在pubspec.yaml中添加FlutterBoost依賴插件,如下所示。

    flutter_boost:
        git:
            url: 'https://github.com/alibaba/flutter_boost.git'
            ref: 'v3.0-hotfixes'

    需要說明的是,此處的所依賴的FlutterBoost的版本與Flutter的版本是對應(yīng)的,如果不對應(yīng)使用過程中會出現(xiàn)版本不匹配的錯誤。然后,使用flutter packages get命令將FlutterBoost插件拉取到本地。

    2.1 Android集成

    使用Android Studio打開新建的原生Android工程,在原生Android工程的settings.gradle文件中添加如下代碼。

    setBinding(new Binding([gradle: this]))
    evaluate(new File(
      settingsDir.parentFile,
      'flutter_library/.android/include_flutter.groovy'))

    然后,打開原生Android工程app目錄下的build.gradle文件,繼續(xù)添加如下依賴腳本。

    dependencies {
      implementation project(':flutter_boost')
      implementation project(':flutter')
    }

    重新編譯構(gòu)建原生Android工程,如果沒有任何錯誤則說明Android成功了集成FlutterBoost。使用Flutter Boost 之前,需要先執(zhí)行初始化。打開原生Android工程,新建一個繼承FlutterApplication的Application,然后在onCreate()方法中初始化FlutterBoost,代碼如下。

    public class MyApplication extends FlutterApplication {
    
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            FlutterBoost.instance().setup(this, new FlutterBoostDelegate() {
    
                @Override
                public void pushNativeRoute(String pageName, HashMap<String, String> arguments) {
                    Intent intent = new Intent(FlutterBoost.instance().currentActivity(), NativePageActivity.class);
                    FlutterBoost.instance().currentActivity().startActivity(intent);
                }
    
                @Override
                public void pushFlutterRoute(String pageName, HashMap<String, String> arguments) {
                    Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(FlutterBoostActivity.class, FlutterBoost.ENGINE_ID)
                            .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
                            .destroyEngineWithActivity(false)
                            .url(pageName)
                            .urlParams(arguments)
                            .build(FlutterBoost.instance().currentActivity());
                    FlutterBoost.instance().currentActivity().startActivity(intent);
                }
    
            },engine->{
                engine.getPlugins();
            } );
        }
    }

    然后,打開原生Android工程下的AndroidManifest.xml文件,將Application替換成自定義的MyApplication,如下所示。

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              package="com.idlefish.flutterboost.example">
    
        <application
            android:name="com.idlefish.flutterboost.example.MyApplication"
            android:label="flutter_boost_example"
            android:icon="@mipmap/ic_launcher">
    
            <activity
                android:name="com.idlefish.flutterboost.containers.FlutterBoostActivity"
                android:theme="@style/Theme.AppCompat"
                android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
                android:hardwareAccelerated="true"
                android:windowSoftInputMode="adjustResize" >
                <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background"/>
    
            </activity>
            <meta-data android:name="flutterEmbedding"
                       android:value="2">
            </meta-data>
        </application>
    </manifest>

    由于Flutter Boost 是以插件的方式集成到原生Android項目的,所以我們可以在Native 打開和關(guān)閉Flutter模塊的頁面。

    FlutterBoost.instance().open("flutterPage",params);
    FlutterBoost.instance().close("uniqueId");

    而Flutter Dart的使用如下。首先,我們可以在main.dart文件的程序入口main()方法中進行初始化。

    void main() {
      runApp(MyApp());
    }
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    class _MyAppState extends State<MyApp> {
       static Map<String, FlutterBoostRouteFactory>
           routerMap = {
        '/': (settings, uniqueId) {
          return PageRouteBuilder<dynamic>(
              settings: settings, pageBuilder: (_, __, ___)
              => Container());
        },
        'embedded': (settings, uniqueId) {
          return PageRouteBuilder<dynamic>(
              settings: settings,
              pageBuilder: (_, __, ___) =>
              EmbeddedFirstRouteWidget());
        },
        'presentFlutterPage': (settings, uniqueId) {
          return PageRouteBuilder<dynamic>(
              settings: settings,
              pageBuilder: (_, __, ___) =>
              FlutterRouteWidget(
                    params: settings.arguments,
                    uniqueId: uniqueId,
                  ));
        }};
       Route<dynamic> routeFactory(RouteSettings settings, String uniqueId) {
        FlutterBoostRouteFactory func =routerMap[settings.name];
        if (func == null) {
          return null;
        }
        return func(settings, uniqueId);
      }
    
      @override
      void initState() {
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return FlutterBoostApp(
          routeFactory
        );
      }

    當(dāng)然,還可以監(jiān)聽頁面的生命周期,如下所示。

    class SimpleWidget extends StatefulWidget {
      final Map params;
      final String messages;
      final String uniqueId;
    
      const SimpleWidget(this.uniqueId, this.params, this.messages);
    
      @override
      _SimpleWidgetState createState() => _SimpleWidgetState();
    }
    
    class _SimpleWidgetState extends State<SimpleWidget>
        with PageVisibilityObserver {
      static const String _kTag = 'xlog';
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        print('$_kTag#didChangeDependencies, ${widget.uniqueId}, $this');
    
      }
    
      @override
      void initState() {
        super.initState();
       PageVisibilityBinding.instance.addObserver(this, ModalRoute.of(context));
       print('$_kTag#initState, ${widget.uniqueId}, $this');
      }
    
      @override
      void dispose() {
        PageVisibilityBinding.instance.removeObserver(this);
        print('$_kTag#dispose, ${widget.uniqueId}, $this');
        super.dispose();
      }
    
      @override
      void onForeground() {
        print('$_kTag#onForeground, ${widget.uniqueId}, $this');
      }
    
      @override
      void onBackground() {
        print('$_kTag#onBackground, ${widget.uniqueId}, $this');
      }
    
      @override
      void onAppear(ChangeReason reason) {
        print('$_kTag#onAppear, ${widget.uniqueId}, $reason, $this');
      }
    
      void onDisappear(ChangeReason reason) {
        print('$_kTag#onDisappear, ${widget.uniqueId}, $reason, $this');
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('tab_example'),
          ),
          body: SingleChildScrollView(
              physics: BouncingScrollPhysics(),
              child: Container(
                  child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Container(
                    margin: const EdgeInsets.only(top: 80.0),
                    child: Text(
                      widget.messages,
                      style: TextStyle(fontSize: 28.0, color: Colors.blue),
                    ),
                    alignment: AlignmentDirectional.center,
                  ),
                  Container(
                    margin: const EdgeInsets.only(top: 32.0),
                    child: Text(
                      widget.uniqueId,
                      style: TextStyle(fontSize: 22.0, color: Colors.red),
                    ),
                    alignment: AlignmentDirectional.center,
                  ),
                  InkWell(
                    child: Container(
                        padding: const EdgeInsets.all(8.0),
                        margin: const EdgeInsets.all(30.0),
                        color: Colors.yellow,
                        child: Text(
                          'open flutter page',
                          style: TextStyle(fontSize: 22.0, color: Colors.black),
                        )),
                    onTap: () => BoostNavigator.of().push("flutterPage",
                        arguments: <String, String>{'from': widget.uniqueId}),
                  )
                  Container(
                    height: 300,
                    width: 200,
                    child: Text(
                      '',
                      style: TextStyle(fontSize: 22.0, color: Colors.black),
                    ),
                  )
                ],
              ))),
        );
      }
    }

    然后,運行項目,就可以從原生頁面跳轉(zhuǎn)到Flutter頁面,如下圖所示效果。

    Flutter Boost的簡介及使用

    2.2 iOS集成

    和Android的集成步驟一樣,使用Xcode打開原生iOS工程,然后在iOS的AppDelegate文件中初始化Flutter Boost ,如下所示。

    @interface AppDelegate ()
    
    @end
    
    @implementation AppDelegate
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
      MyFlutterBoostDelegate* delegate=[[MyFlutterBoostDelegate alloc ] init];
        [[FlutterBoost instance] setup:application delegate:delegate callback:^(FlutterEngine *engine) {
        } ];
    
        return YES;
    }
    @end

    下面是自定義的FlutterBoostDelegate的代碼,如下所示。

    @interface MyFlutterBoostDelegate : NSObject<FlutterBoostDelegate>
    @property (nonatomic,strong) UINavigationController *navigationController;
    @end
    
    @implementation MyFlutterBoostDelegate
    
    - (void) pushNativeRoute:(FBCommonParams*) params{
        BOOL animated = [params.arguments[@"animated"] boolValue];
        BOOL present= [params.arguments[@"present"] boolValue];
        UIViewControllerDemo *nvc = [[UIViewControllerDemo alloc] initWithNibName:@"UIViewControllerDemo" bundle:[NSBundle mainBundle]];
        if(present){
            [self.navigationController presentViewController:nvc animated:animated completion:^{
            }];
        }else{
            [self.navigationController pushViewController:nvc animated:animated];
        }
    }
    
    - (void) pushFlutterRoute:(FBCommonParams*)params {
    
        FlutterEngine* engine =  [[FlutterBoost instance ] getEngine];
        engine.viewController = nil;
    
        FBFlutterViewContainer *vc = FBFlutterViewContainer.new ;
    
        [vc setName:params.pageName params:params.arguments];
    
        BOOL animated = [params.arguments[@"animated"] boolValue];
        BOOL present= [params.arguments[@"present"] boolValue];
        if(present){
            [self.navigationController presentViewController:vc animated:animated completion:^{
            }];
        }else{
            [self.navigationController pushViewController:vc animated:animated];
    
        }
    }
    
    - (void) popRoute:(FBCommonParams*)params
             result:(NSDictionary *)result{
    
        FBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController;
    
        if([vc isKindOfClass:FBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: params.uniqueId]){
            [vc dismissViewControllerAnimated:YES completion:^{}];
        }else{
            [self.navigationController popViewControllerAnimated:YES];
        }
    
    }
    
    @end

    如果要在原生iOS代碼中打開或關(guān)閉Flutter頁面,可以使用下面的方式。

    [[FlutterBoost instance] open:@"flutterPage" arguments:@{@"animated":@(YES)}  ];
    [[FlutterBoost instance] open:@"secondStateful" arguments:@{@"present":@(YES)}];

    三、Flutter Boost架構(gòu)

    對于混合工程來說,原生端和Flutter端對于頁面的定義是不一樣的。對于原生端而言,頁面通常指的是一個ViewController或者Activity,而對于Flutter來說,頁面通常指的是Flutter組件。FlutterBoost框架所要做的就是統(tǒng)一混合工程中頁面的概念,或者說弱化Flutter組件對應(yīng)容器頁面的概念。換句話說,當(dāng)有一個原生頁面存在的時候,F(xiàn)lutteBoost就能保證一定有一個對應(yīng)的Flutter的容器頁面存在。

    FlutterBoost框架其實就是由原生容器通過消息驅(qū)動Flutter頁面容器,從而達到原生容器與Flutter容器同步的目的,而Flutter渲染的內(nèi)容是由原生容器去驅(qū)動的,下面是Flutter Boost 給的一個Flutter Boost 的架構(gòu)示意圖。

    Flutter Boost的簡介及使用

    可以看到,F(xiàn)lutter Boost插件分為平臺和Dart兩端,中間通過Message Channel連接。平臺側(cè)提供了Flutter引擎的配置和管理、Native容器的創(chuàng)建/銷毀、頁面可見性變化通知,以及Flutter頁面的打開/關(guān)閉接口等。而Dart側(cè)除了提供類似原生Navigator的頁面導(dǎo)航接口的能力外,還負責(zé)Flutter頁面的路由管理。

    總的來說,正是基于共享同一個引擎的方案,使得FlutterBoost框架有效的解決了多引擎的問題。簡單來說,F(xiàn)lutterBoost在Dart端引入了容器的概念,當(dāng)存在多個Flutter頁面時,F(xiàn)lutterBoost不需要再用棧的結(jié)構(gòu)去維護現(xiàn)有頁面,而是使用扁平化鍵值對映射的形式去維護當(dāng)前所有的頁面,并且每個頁面擁有一個唯一的id

    四、FlutterBoost3.0更新

    4.1 不入侵引擎

    為了解決官方引擎復(fù)用引起的問題,F(xiàn)lutterBoost2.0拷貝了Flutter引擎Embedding層的一些代碼進行改造,這使得后期的升級成本極高。而FlutterBoost3.0采用繼承的方式擴展FlutterActivity/FlutterFragment等組件的能力,并且通過在適當(dāng)時機給Dart側(cè)發(fā)送appIsResumed消息解決引擎復(fù)用時生命周期事件錯亂導(dǎo)致的頁面卡死問題,并且,F(xiàn)lutterBoost 3.0 也兼容最新的官方發(fā)布的 Flutter 2.0。

    4.2 不區(qū)分Androidx和Support分支

    FlutterBoost2.0通過自己實現(xiàn)FlutterActivityAndFragmentDelegate.Host接口來擴展FlutterActivity和FlutterFragment的能力,而getLifecycle是必須實現(xiàn)的接口,這就導(dǎo)致對androidx的依賴。這也是為什么FlutterBoostView的實現(xiàn)沒有被放入FlutterBoost3.0插件中的原因。而FlutterBoost3.0通過繼承的方式擴展FlutterActivity/FlutterFragment的能力的額外收益就是,可以做到不依賴androidx。

    4.3 雙端設(shè)計統(tǒng)一,接口統(tǒng)一

    很多Flutter開發(fā)者只會一端,只會Android 或者只會IOS,但他需要接入雙端,所以雙端統(tǒng)一能降低他的 學(xué)習(xí)成本和接入成本。FlutterBoost3.0,在設(shè)計上 Android和IOS都做了對齊,特別接口上做到了參數(shù)級的對齊。

    4.4 支持 【打開flutter頁面不再打開容器】 場景

    在Flutter模塊內(nèi)部,F(xiàn)lutter 頁面跳轉(zhuǎn)Flutter 頁面是可以不需要再打開Flutter容器的,不打開容器,能節(jié)省內(nèi)存開銷。在FlutterBoost3.0上,打開容器和不打開容器的區(qū)別表現(xiàn)在用戶接口上僅僅是withContainer參數(shù)是否為true就好。

    InkWell(
      child: Container(
          color: Colors.yellow,
          child: Text(
            '打開外部路由',
            style: TextStyle(fontSize: 22.0, color: Colors.black),
          )),
      onTap: () => BoostNavigator.of().push("flutterPage",
          arguments: <String, String>{'from': widget.uniqueId}),
    ),
    InkWell(
      child: Container(
          color: Colors.yellow,
          child: Text(
            '打開內(nèi)部路由',
            style: TextStyle(fontSize: 22.0, color: Colors.black),
          )),
      onTap: () => BoostNavigator.of().push("flutterPage",
          withContainer: true,
          arguments: <String, String>{'from': widget.uniqueId}),
    )

    4.5 生命周期的精準(zhǔn)通知

    在FlutterBoost2.0上,每個頁面都會收到頁面生命周期通知,而FlutterBoost3.0只會通知頁面可見性實際發(fā)生了變化的頁面,接口也更符合flutter的設(shè)計。

    4.6 其他Issue

    除了上面的一些特性外,F(xiàn)lutter Boost 3.0版本還解決了如下一些問題:

    • 頁面關(guān)閉后參數(shù)的傳遞,之前只有iOS支持,android不支持,目前在dart側(cè)實現(xiàn),Ios 和Android 都支持。

    • 解決了Android 狀態(tài)欄字體和顏色問題。

    • 解決了頁面回退willpopscope不起作用問題。

    • 解決了不在棧頂?shù)捻撁嬉彩盏缴芷诨卣{(diào)的問題

    • 解決了多次setState耗性能問題。

    • 提供了Framgent 多種接入方式的Demo,方便tab 場景的接入。

    • 生命周期的回調(diào)代碼,可以用戶代碼里面with的方式接入,使用更簡單。

    • 全面簡化了,接入成本,包括 dart側(cè),android側(cè)和ios

    • 豐富了demo,包含了基本場景,方便用戶接入 和測試回歸

    到此,相信大家對“Flutter Boost的簡介及使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

    向AI問一下細節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI