Airing

Airing

哲学系学生 / 小学教师 / 程序员,个人网站: ursb.me
github
email
zhihu
medium
tg_channel
twitter_id

Flutter Boost 混合開發實踐與源碼解析

1. 簡介#

Flutter Boost 是閒魚團隊開發的一個 Flutter 混合開發框架,項目背景可以看看閒魚的這篇文章:碼上用它開始 Flutter 混合開發 ——FlutterBoost

文章中主要講述了多引擎存在一些實際問題,所以閒魚目前採用的混合方案是共享同一個引擎的方案。而 Flutter Boost 的 Feature 如下:

  • 可重用通用型混合方案
  • 支持更加複雜的混合模式,比如支持首頁 Tab 這種情況
  • 無侵入性方案:不再依賴修改 Flutter 的方案
  • 支持通用頁面生命週期
  • 統一明確的設計概念

Flutter Boost 採用共享引擎的模式來實現,主要思路是由 Native 容器 Container 通過消息驅動 Flutter 頁面容器 Container,從而達到 Native Container 與 Flutter Container 的同步目的。簡單的理解,閒魚想做到把 Flutter 容器做成瀏覽器的感覺。填寫一個頁面地址,然後由容器去管理頁面的繪製。在 Native 端我們只需要關心如何初始化容器,然後設置容器對應的頁面標誌即可。

鑒於網上沒有相關的接入文檔和使用教程,我這幾天也恰好抽空研究了一下,遂整理成文,僅供參考。由於篇幅原因,本文只研究 Android 端的接入與源碼,iOS 的部分後續有機會則補充文章來講解。

注:本文接入的 Flutter Boost 版本為 1.12.13,對應支持的 Flutter SDK 版本為 1.12.13-hotfixes,是目前最新的版本。但 Flutter Boost 版本更新之後,接入方式和使用方式可能會有一些改變,故參考本文時請認準 1.12.13 版本。

2. 接入#

2.1 創建 Flutter Module#

在開始之前,我們需要保證工程目錄如下結構所示:

--- flutter_hybrid
		--- flutter_module
		--- FlutterHybridAndroid 
		--- FlutterHybridiOS

即,iOS 工程與 Android 工程與 flutter_module 目錄在同一層級。如此,方便管理,同時也保證後續集成代碼中路徑的一致性。

接著,我們來創建 Flutter Module:

cd flutter_hybrid
flutter create -t module flutter_module

需要注意的是,如果要創建支持 Android X 的 flutter module,命令上需要加上 --androidx 參數,即:

flutter create --androidx -t module flutter_module 

注:如果安裝依賴過慢,可以切換為國內的依賴鏡像源。

export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

當然我們也可以通過新版的 Android Studio 來可視化創建一個 Flutter Module (需 3.6.1 以上版本,並給 IDE 裝上 Flutter 與 Dart 插件),具體方法可以見官網介紹(https://flutter.dev/docs/development/add-to-app/android/project-setup),此處不再贅述。但個人建議使用本文介紹的更為通用的方法去創建並集成 Flutter Module。

2.2 集成 Flutter Module#

創建好 Flutter Module 之後,需要在 Native 工程中集成 flutter_module。具體有兩種方式:

  1. 源碼依賴
  2. arr 依賴

2.2.1 源碼依賴集成#

源碼依賴的優點是開發、調試方便,也就是在 Android 工程的 settings.gradle 和 app 目錄下的 build.gradle 文件中加入對 flutter_module 的依賴即可。

首先,在 settings.gradle 文件中,增加以下代碼:

include ':app'                                     // 已存在的內容

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

setBindingevaluate 增加之後我們就可以在 app/build.gradle 中增加 :flutter 依賴:

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

同時也需要在該文件中的 android () 配置指定一下編譯時的 Java 版本為 Java 8,否則會報錯

compileOptions {
  sourceCompatibility 1.8
  targetCompatibility 1.8
}

最後執行一下 gradle sync 下載依賴庫。如果集成成功,會在左側的項目目錄中看到與項目同級的 flutter_module 文件夾。

2.2.2 arr 依賴集成#

如果需要用遠程打包,而遠程的機器上沒有 flutter 環境,就可以把 flutter 打包成 arr 文件進行依賴。生成 aar 文件之後再在主工程裡引用,flutter aar 中包含了 flutter sdk 的代碼,所以這種方式是不需要 flutter 環境的,也適合第三方快速接入。

cd .android/
./gradlew flutter:assembleDebug

2.3 添加 Flutter Boost 依賴#

首先在 Flutter Module 項目中加入 flutter-boost 依賴,即在 pubspec.yaml 文件中的 dev_dependencies 配置增加 flutter-boost 依賴:

dev_dependencies:
  flutter_boost:
     git:
        url: 'https://github.com/alibaba/flutter_boost.git'
        ref: '1.12.13'

以上 flutter boost 支持 AndroidX,如果想支持 support,則需要切換分支:

flutter_boost:
    git:
        url: 'https://github.com/alibaba/flutter_boost.git'
        ref: 'task/task_v1.12.13_support_hotfixes'

編輯完之後在 flutter_module 目錄下執行以下命令安裝依賴。

flutter packages get

之後在 Android 工程中的 app 目錄下的 build.gradle 文件中增加 :flutter_boost 依賴,

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

因為 Flutter Boost 是以 Flutter Plugin 的形式集成到我們的項目中來的,所以我們還需要做一些工作,首先在 app 目錄下的 build.gradle 文件的頭部增加以下代碼:

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

這需要我們在 Android 工程下的 local.properties 文件中指定以下我們本地的 Flutter SDK 的位置(沒有該文件就新建一個):

flutter.sdk = /Users/airing/flutter

最後再在工程目錄下的 settings.gradle 中增加以下代碼引入 flutter-plugin:

def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()

def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
    pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}

plugins.each { name, path ->
    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
    include ":$name"
    project(":$name").projectDir = pluginDirectory
}

同樣的,修改完 Android 工程的依賴之後,需要 gradle sync 一下。

至此,Flutter Boost 集成成功。接下來,我們使用 Flutter Boost 進行混合開發。而混合開發,主要涉及到兩個場景:

  1. 在 Native 項目中加入 Flutter 頁面,即 Add a single Flutter screen。
  2. 在 Native 頁面中嵌入 Flutter 模塊,即 Add a Flutter Fragment。

這兩種方式在 Flutter 的官網上都有實踐講解,我們這裡主要看看如果使用 Flutter boost 究竟要如何實現的,並順便探究一下其實現原理。

3. 混合開發 1: Flutter View#

image

3.1 在 Flutter Module 中使用 Flutter Boost#

首先引入依賴

import 'package:flutter_boost/flutter_boost.dart';

隨後在 main 方法中運行的 rootWidget 中註冊兩個新的頁面,以便 Native 工程可以跳轉過來。

import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'simple_page_widgets.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();

    FlutterBoost.singleton.registerPageBuilders({
      'first': (pageName, params, _) => FirstRouteWidget(),
      'second': (pageName, params, _) => SecondRouteWidget(),
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Boost example',
        builder: FlutterBoost.init(),
        home: Container(
            color:Colors.white
        ));
  }
}

代碼很簡單:

  1. 在 initState 的時候使用 FlutterBoost.singleton.registerPageBuilders 註冊頁面
  2. 在 bulider 中初始化 FlutterBoost。

3.2 在 Android 工程中使用 Flutter Boost#

在 Android 項目中增加一個 Flutter 頁面,即是添加一個 Flutter Activity(iOS 即是添加一個新的 FlutterViewController,這裡不再花篇幅去講解 iOS 的實現了,有興趣的同學可以自己去閱讀 Flutter Boost 的示例代碼和源碼)。

這裡我們在 AndroidManifest.xml 的 Application 配置中添加一個 Flutter Boost Activity:

<activity
  android:name="com.idlefish.flutterboost.containers.BoostFlutterActivity"
  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/page_loading"/>
</activity>

除此之外還需要在 AndroidManifest.xml 中添加 flutterEmbedding 版本設置:

<meta-data android:name="flutterEmbedding"
	android:value="2">
</meta-data>

接著要進行初始化 FlutterBoost 的工作,建議在 Application 的 onCreate 方法中初始化:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        INativeRouter router = new INativeRouter() {
            @Override
            public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
               String  assembleUrl=Utils.assembleUrl(url,urlParams);
                PageRouter.openPageByUrl(context,assembleUrl, urlParams);
            }

        };

        FlutterBoost.BoostLifecycleListener boostLifecycleListener= new FlutterBoost.BoostLifecycleListener(){

            @Override
            public void beforeCreateEngine() {

            }

            @Override
            public void onEngineCreated() {

            }

            @Override
            public void onPluginsRegistered() {

            }

            @Override
            public void onEngineDestroy() {

            }

        };

        Platform platform = new FlutterBoost
                .ConfigBuilder(this,router)
                .isDebug(true)
                .whenEngineStart(FlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED)
                .renderMode(FlutterView.RenderMode.texture)
                .lifecycleListener(boostLifecycleListener)
                .build();
        FlutterBoost.instance().init(platform);
    }
}

FlutterBoost 在 Android 工程中初始化需要進行 4 步工作:

  1. 註冊路由跳轉方法(後續會說 PageRouter 的實現)
  2. 增加 flutter boost 的生命週期監聽函數,可以在 Flutter Engine 創建之前、創建之後、銷毀之後與 Flutter Plugin 註冊之後回調事件。
  3. 聲明 Flutter boost 配置,把路由和生命週期函數配置上。
  4. 初始化 Flutter boost。

接著要在 Android 工程中實現一個頁面路由的工具類 PageRouter,這裡直接擺上 Flutter Boost 示例代碼中的實現了,比較全面:

public class PageRouter {

    public final static Map<String, String> pageName = new HashMap<String, String>() {{

        put("first", "first");
        put("second", "second");
        put("tab", "tab");
        put("sample://flutterPage", "flutterPage");
    }};

    public static final String NATIVE_PAGE_URL = "sample://nativePage";
    public static final String FLUTTER_PAGE_URL = "sample://flutterPage";
    public static final String FLUTTER_FRAGMENT_PAGE_URL = "sample://flutterFragmentPage";

    public static boolean openPageByUrl(Context context, String url, Map params) {
        return openPageByUrl(context, url, params, 0);
    }

    public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {

        String path = url.split("\\?")[0];

        Log.i("openPageByUrl",path);

        try {
            if (pageName.containsKey(path)) {
                Intent intent = BoostFlutterActivity.withNewEngine().url(pageName.get(path)).params(params)
                        .backgroundMode(BoostFlutterActivity.BackgroundMode.opaque).build(context);
                if(context instanceof Activity){
                    Activity activity=(Activity)context;
                    activity.startActivityForResult(intent,requestCode);
                }else{
                    context.startActivity(intent);
                }
                return true;
            } else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) {
                context.startActivity(new Intent(context, FlutterFragmentPageActivity.class));
                return true;
            } else if (url.startsWith(NATIVE_PAGE_URL)) {
                context.startActivity(new Intent(context, NativePageActivity.class));
                return true;
            }

            return false;

        } catch (Throwable t) {
            return false;
        }
    }
}

3.3 在 Native 項目中打開 Flutter 頁面#

調用比較簡單,在 Native 頁面上的按鈕綁定上 onClick 監聽來實現點擊打開我們註冊的 Flutter 中的 first 頁面,還可以順便傳上一個 map 參數:

@Override
public void onClick(View v) {
    Map params = new HashMap();
    params.put("test1","v_test1");
    params.put("test2","v_test2");

    PageRouter.openPageByUrl(this, "first", params);
}

我們回顧一下我們在 3.1 中 Flutter 中註冊頁面的代碼,發現有一個 params 參數,沒錯那就是 Native 打開 Flutter 時傳過來的參數,我們可以打印出來或者傳給 widget 做額外的處理:

FlutterBoost.singleton.registerPageBuilders({
      'first': (pageName, params, _) => {
        print("flutterPage params:$params");

        return FirstRouteWidget(params:params);
      },
      'second': (pageName, params, _) => SecondRouteWidget(),
});

3.4 在 Flutter 頁面中打開 Native 頁面#

同樣的,我們可能還會遇到一種場景,在 Native 中打開 Flutter 頁面之後,我們 Flutter 中的業務又需要再打開一個新的 Native 頁面,那需要怎麼做?在 Flutter 中使用 FlutterBoost.singleton.open 即可,如下:

// 後面的參數會在native的IPlatform.startActivity方法回調中拼接到url的query部分。
// 例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
     .open("sample://nativePage", urlParams: <dynamic,dynamic>{
      "query": {"aaa": "bbb"}
}),

當然,這個方法不單單支持打開 Native 頁面,也可以打開一個新的 Flutter 頁面,只需要寫好路由名就好,這裡不再贅述。

注:得益於 Flutter 的 JIT 編譯模式,我們可以通過 flutter attach 命令來實現 hot reload 功能,在開發 Flutter 頁面時無需重新編譯工程。

4. 混合開發 2:Flutter Fragment#

image

我們假設工程中存在一個 Activity,配置如下:

<activity
     android:name=".FlutterFragmentPageActivity"
     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/page_loading"/>
</activity>

而對應 layout 中我們要加入一個 FrameLayout 組件作為佔位符:

<FrameLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/fragment_stub"/>

最後,在代碼中拿到對應 url 的 Flutter widget,塞到佔位組件裡即可:

@Override
public void onClick(View v) {
    FlutterFragment mFragment = new FlutterFragment.NewEngineFragmentBuilder().url("flutterFragment").build();
    getSupportFragmentManager()
        .beginTransaction()
        .replace(R.id.fragment_stub, mFragment)
        .commit();
}

5. Flutter Boost 源碼解析#

本節主要簡單分析一下 Flutter Boost 的原理,只有知根知底才能用得得心應手。由於篇幅問題,不可能全部的源碼都分析一遍,本節只分析具有代表性的源碼,其餘的原理基本一致,留給讀者自行閱讀。

那本節就從 Dart 端切入,關注其中兩個 api,一個是註冊頁面的 registerPageBuilders,另一個是打開頁面的 open,看看 Flutter Boost 是如何實現它們的。

5.1 註冊頁面#

我們在使用 Flutter Boost 的流程中,第一步是要在 Flutter 中註冊頁面,調用的 registerPageBuilders 函數,那我們來看一下這個函數是如何實現的。

在 flutter_boost.dart 文件中,我們很容易就找到了這個函數的入口:

///Register a map builders
void registerPageBuilders(Map<String, PageBuilder> builders) {
  ContainerCoordinator.singleton.registerPageBuilders(builders);
}

它調用了 ContainerCoordinator 單例的 registerPageBuilders,那我們接著看 container_coordinator.dart 文件中這個函數的實現:

final Map<String, PageBuilder> _pageBuilders = <String, PageBuilder>{};
  PageBuilder _defaultPageBuilder;

///Register page builder for a key.
void registerPageBuilder(String pageName, PageBuilder builder) {
  if (pageName != null && builder != null) {
    _pageBuilders[pageName] = builder;
  }
}

其中 PageBuilder 我們可以找到定義,是一個 Widget,那麼這個函數其實就將我們註冊的 Widget 塞到一個 Map 裡,而我們指定的路由名,就是它的 key。那我們接著要關注的是 _pageBuilders 定義好之後會怎麼被使用?

BoostContainerSettings _createContainerSettings(
      String name, Map params, String pageId) {
    Widget page;

    final BoostContainerSettings routeSettings = BoostContainerSettings(
        uniqueId: pageId,
        name: name,
        params: params,
        builder: (BuildContext ctx) {
          //Try to build a page using keyed builder.
          if (_pageBuilders[name] != null) {
            page = _pageBuilders[name](name, params, pageId);
          }

          //Build a page using default builder.
          if (page == null && _defaultPageBuilder != null) {
            page = _defaultPageBuilder(name, params, pageId);
          }

          assert(page != null);
          Logger.log('build widget:$page for page:$name($pageId)');

          return page;
        });

    return routeSettings;
  }

可以發現,它在 _createContainerSettings 中 build widget 之後返回一個 routeSetting,該變量在 _nativeContainerWillShow 中被 pushContainer 調用,而 _nativeContainerWillShow 會在 _onMethodCall 中被調用。

bool _nativeContainerWillShow(String name, Map params, String pageId) {
    if (FlutterBoost.containerManager?.containsContainer(pageId) != true) {
      FlutterBoost.containerManager
          ?.pushContainer(_createContainerSettings(name, params, pageId));
    }
    return true;
  }
Future<dynamic> _onMethodCall(MethodCall call) {
    Logger.log("onMetohdCall ${call.method}");

    switch (call.method) {
      // 省略無關代碼
      case "willShowPageContainer":
        {
          String pageName = call.arguments["pageName"];
          Map params = call.arguments["params"];
          String uniqueId = call.arguments["uniqueId"];
          _nativeContainerWillShow(pageName, params, uniqueId);
        }
        break;
    }
  	// 省略無關代碼
}

以上兩段代碼的作用是當 Dart 端監聽到來自 Native 的通信之後,如果 Native 傳遞了一個要打開一個頁面容器的信息(willShowPageContainer)之後,FlutterBoost 的容器管理器就會根據用戶註冊配置的路由頁面去打開一個新的容器。而這裡的 pushContainer 主要做一些路由管理和綁定監聽等操作,我們就不再細看這部分的邏輯了,主要還是看看 _onMethodCall 的 Native 與 Dart 的互相通信。

5.2 通信#

ContainerCoordinator(BoostChannel channel) {
    assert(_instance == null);

    _instance = this;

    channel.addEventListener("lifecycle",
        (String name, Map arguments) => _onChannelEvent(arguments));

    channel.addMethodHandler((MethodCall call) => _onMethodCall(call));
  }

Flutter Boost 中負責通信的是 BoostChannel,它的本質上是 MethodChannel 的一層封裝,而 MethodChannel 是 Native 與 Flutter 通信的方案之一,有興趣的同學可以自己查閱 MethodChannel 相關的資料加以了解。

image

可以閱讀 Flutter 官網對 MethodChannel 的介紹:https://flutter.dev/docs/development/platform-integration/platform-channels

5.3 打開頁面#

最後我們再來看一個打開頁面的函數 open,它的實現在庫中也容易找到:

Future<Map<dynamic, dynamic>> open(String url,
      {Map<dynamic, dynamic> urlParams, Map<dynamic, dynamic> exts}) {
    Map<dynamic, dynamic> properties = new Map<dynamic, dynamic>();
    properties["url"] = url;
    properties["urlParams"] = urlParams;
    properties["exts"] = exts;
    return channel.invokeMethod<Map<dynamic, dynamic>>('openPage', properties);
  }

可以發現,它的工作其實就是包裝好參數後把 openPage 的消息發送給 Native。那我們再來看看 Native 端接受到這個消息之後作何處理吧!在 Android 端的 Flutter Boost 源碼中可以找到 FlutterBoostPlugin.java 這個文件,其中有 MethodChannel 的邏輯來監聽 Dart 端的消息:

class BoostMethodHandler implements MethodChannel.MethodCallHandler {

        @Override
        public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {

            FlutterViewContainerManager mManager = (FlutterViewContainerManager) FlutterBoost.instance().containerManager();
            switch (methodCall.method) {
                // 省略無關的分支
                case "openPage": {
                    try {
                        Map<String, Object> params = methodCall.argument("urlParams");
                        Map<String, Object> exts = methodCall.argument("exts");
                        String url = methodCall.argument("url");

                        mManager.openContainer(url, params, exts, new FlutterViewContainerManager.OnResult() {
                            @Override
                            public void onResult(Map<String, Object> rlt) {
                                if (result != null) {
                                    result.success(rlt);
                                }
                            }
                        });
                    } catch (Throwable t) {
                        result.error("open page error", t.getMessage(), t);
                    }
                }
                break;
                default: {
                    result.notImplemented();
                }
            }
        }
    }

在收到來自 Dart 的 openPage 消息後,Android 端的容器管理器(FlutterViewContainerManager)會根據 Dart 端攜帶來的配置數據打開一個容器,而這個 openContainer 通過閱讀源碼,可以發現它最後是一個抽象方法,需要我們自己在業務側實現。回看我們在 3.2 節中在 Android 中初始化 Flutter Boost 第一步工作,做的就是實現這個 openContainer,而它最後交由我們封裝的 PageRouter 工具類來實現了,即 context.startActivity ()。

至此,我們在 Android 工程中集成了 Flutter Boost 來實現 Flutter 在 Android 項目中的混合開發。本文只是初步分析了下 Flutter Boost 的源碼,後續有機會會補上詳細的分析和 iOS 端的接入使用。敬請期待。

附:Flutter Boost 接入實踐(iOS 篇)#

1. 前言#

我們給 Android 接入 Flutter Boost 之後,現在我們來看看如何給 iOS 工程(OC)接入 Flutter Boost。

本文將簡單梳理一下 iOS 工程接入的 Flutter Boost 的流程,以作為前文的補充。

參見前文:Flutter Boost 混合開發實踐與源碼解析(以 Android 為例),Flutter Module 也依舊用前文生成的,目錄結構依舊如前文所述,不再贅述。

2. 接入#

2.1 工程準備#

準備一個配有 Cocoapods 的空白工程,在 Podfile 中配置我們之前準備好的 Flutter Module 作為依賴:

flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'FlutterHybridiOS' do
	install_all_flutter_pods(flutter_application_path)
end

接著在工程根目錄下運行 pod install ,即可集成上 Flutter Module。看到我們的 Pods 中多了以下幾個模塊,即說明集成成功。

image.png

如不了解 Cocoapods,請參見 CocoaPods 使用指南

2.2 實現路由類#

這一塊直接參照 Flutter Boost 官方提供的 example 就好了:https://github.com/alibaba/flutter_boost/blob/master/example/ios/Runner/PlatformRouterImp.h

PlatformRouterImp.h:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <flutter_boost/FlutterBoost.h>

NS_ASSUME_NONNULL_BEGIN

@protocol FLBPlatform;

/**
 * 實現平台側的頁面打開和關閉,不建議直接使用用於頁面打開,建議使用FlutterBoostPlugin中的open和close方法來打開或關閉頁面;
 * FlutterBoostPlugin帶有頁面返回數據的能力
 */
@interface PlatformRouterImp : NSObject<FLBPlatform>
@property (nonatomic,strong) UINavigationController *navigationController;
@end

NS_ASSUME_NONNULL_END

PlatformRouterImp.m:

#import "PlatformRouterImp.h"
#import <flutter_boost/FlutterBoost.h>

@interface PlatformRouterImp()
@end

@implementation PlatformRouterImp

#pragma mark - Boost 1.5
- (void)open:(NSString *)name
   urlParams:(NSDictionary *)params
        exts:(NSDictionary *)exts
  completion:(void (^)(BOOL))completion
{
    BOOL animated = [exts[@"animated"] boolValue];
    FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
    [vc setName:name params:params];
    [self.navigationController pushViewController:vc animated:animated];
    if(completion) completion(YES);
}

- (void)present:(NSString *)name
   urlParams:(NSDictionary *)params
        exts:(NSDictionary *)exts
  completion:(void (^)(BOOL))completion
{
    BOOL animated = [exts[@"animated"] boolValue];
    FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
    [vc setName:name params:params];
    [self.navigationController presentViewController:vc animated:animated completion:^{
        if(completion) completion(YES);
    }];
}

- (void)close:(NSString *)uid
       result:(NSDictionary *)result
         exts:(NSDictionary *)exts
   completion:(void (^)(BOOL))completion
{
    BOOL animated = [exts[@"animated"] boolValue];
    animated = YES;
    FLBFlutterViewContainer *vc = (id)self.navigationController.presentedViewController;
    if([vc isKindOfClass:FLBFlutterViewContainer.class] && [vc.uniqueIDString isEqual: uid]){
        [vc dismissViewControllerAnimated:animated completion:^{}];
    }else{
        [self.navigationController popViewControllerAnimated:animated];
    }
}
@end

可以看到,Flutter Boost 支持常規 push,也支持打開模態彈窗,也支持手動 pop。

2.3 綁定路由管理#

AppDelegate.h:

#import <UIKit/UIKit.h>
#import <flutter_boost/FlutterBoost.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (nullable, nonatomic, strong) UIWindow *window;
@end

AppDelegate.m:

#import "AppDelegate.h"
#import "PlatformRouterImp.h"
#import <flutter_boost/FlutterBoost.h>

@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    PlatformRouterImp *router = [PlatformRouterImp new];
    [FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:router
                                                        onStart:^(FlutterEngine *engine) {
                                                            
                                                        }];
	UITabBarController *tabVC = [[UITabBarController alloc] init];
	UINavigationController *rvc = [[UINavigationController alloc] initWithRootViewController:tabVC];
    router.navigationController = rvc;
    
	return YES;
}

3. 使用#

- (void)openClick:(UIButton *)button
{
    [FlutterBoostPlugin open:@"first" urlParams:@{kPageCallBackId:@"MycallbackId#1"} exts:@{@"animated":@(YES)} onPageFinished:^(NSDictionary *result) {
        NSLog(@"call me when page finished, and your result is:%@", result);
    } completion:^(BOOL f) {
        NSLog(@"page is opened");
    }];
}

- (void)openPresentClick:(UIButton *)button
{
    [FlutterBoostPlugin open:@"second" urlParams:@{@"present":@(YES),kPageCallBackId:@"MycallbackId#2"} exts:@{@"animated":@(YES)} onPageFinished:^(NSDictionary *result) {
        NSLog(@"call me when page finished, and your result is:%@", result);
    } completion:^(BOOL f) {
        NSLog(@"page is presented");
    }];
}

同樣的,這裡可在 Native 端用兩種不同的方式去打開我們在 Flutter Module 中註冊好的路由名。

至此,我們成功在 iOS 工程中接入了 Flutter Boost,那就開啟我們的混編之旅吧~

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。