從hybrid_stack_manager遷移到flutter_boost (iOS角度)

當前項目是flutter和iOS、安卓原生客戶端混合使用的,有flutter和原生頁面互相調用的需求,因此以前使用了hybrid_stack_manager來管理混合棧。ios

如上圖所示,hybrid_stack_manager在原生端直接使用了UINavigationController,在不一樣的FlutterWrapperViewController中共用同一個FlutterViewController。 當調用打開flutter的方法時,首先要獲取當前的navigationController而後push。

//Push
    UINavigationController *currentNavigation = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController;
    FlutterViewWrapperController *viewController = [[FlutterViewWrapperController alloc] initWithURL:[NSURL URLWithString:aUrl] query:query nativeParams:params];
    viewController.viewWillAppearBlock = ^(){
        //Process first & later message sending according distinguishly.
        if(sIsFirstPush){
            [HybridStackManager sharedInstance].mainEntryParams = arguments;
            sIsFirstPush = FALSE;
        }
        else{
            [methodChann invokeMethod:@"openURLFromFlutter" arguments:arguments result:^(id  _Nullable result) {
            }];
        }
    };
    [currentNavigation pushViewController:viewController animated:YES];//直接使用navigationController
複製代碼

在flutter端緩存

pushPageWithOptionsFromFlutter({RouterOption routeOption, bool animated}) {
  Widget page =
      Router.sharedInstance().pageFromOption(routeOption: routeOption);
  if (page != null) {
    XMaterialPageRoute pageRoute = new XMaterialPageRoute(
        settings: new RouteSettings(name: routeOption.userInfo),
        animated: animated,
        builder: (BuildContext context) {
          return page;
        });

    Navigator.of(globalKeyForRouter.currentContext).push(pageRoute); //直接使用Navigator
    HybridStackManagerPlugin.hybridStackManagerPlugin
        .updateCurFlutterRoute(routeOption.userInfo);
  } else {
    HybridStackManagerPlugin.hybridStackManagerPlugin.openUrlFromNative(
        url: routeOption.url,
        query: routeOption.query,
        params: routeOption.params);
  }
  NavigatorState navState = Navigator.of(globalKeyForRouter.currentContext);
  List<Route<dynamic>> navHistory = navState.history;
}
複製代碼

FlutterEngine消耗的資源很大,並且啓用多個flutterEngine會形成不一樣flutter頁面通訊困難,因此在hybrid_stack_manager中不一樣的flutterWrapperViewController中也是共用同一個FlutterViewController和flutterEngine的。這樣基本解決了flutter和原生頁面互相打開的問題。app

可是咱們很快就發現要打開flutter頁面必須經過push的方法,存在很大的侷限,在app使用tabbarViewController的時候,若是有一個tab中的頁面是flutter頁面,不能經過navigationController push的方式加載。 screenshot.png 框架

因此必須另外使用一個FlutterViewController和flutterEngine,這樣會致使以下問題: DF39C9D2-61F0-4F72-925D-60E0BF2F5E7B.jpg
1.一個flutterEngine有四個線程,並且每一個engine會維護本身的圖片緩存,兩個flutterEngine會帶來加倍的消耗,會影響到app性能。 2.不一樣flutter engine沒有共享內存,通訊必須使用port的方式。 3.調試帶來不便,多個flutter engine存在的時候,ide找不到要調試的端口,咱們經過手動註釋掉另一個flutterViewController的方式來臨時解決。

Flutter Boost一樣是閒魚團隊出品的混合棧框架,遷移到這個框架後,解決了上面提到的全部問題。 screenshot.png ide

這個圖片看上去和hybrid_stack_manager的那張圖差很少,但其實只有在中間FlutterViewController共用那邊是類似的,在原生端和flutter端打開頁面的方式都是徹底不同的。

FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
[vc setName:name params:params];
[self.navigationController pushViewController:vc animated:animated];
複製代碼

從上面代碼能夠看到,FLBFlutterViewContainer是flutter頁面的原生容器,每個flutter頁面都對應一個FLBFlutterViewContainer,經過調用setName:params方法到flutter端找到對應的widget,加載到這個原生容器中。 最關鍵的是[self.navigationController pushViewController:vc animated:animated];這句代碼咱們業務代碼本身調用的,並不像hybrid_stack_manager中強制有框架調用navigationController 的push方法。換句話說,flutter boost只完成從flutter widget到原生頁面的轉化,具體怎麼用是留給業務代碼控制的。 在flutter端也沒有直接使用navigator push這樣簡單粗暴的方式,而是本身維護了一個導航棧。性能

BoostContainer _onstage;//當前顯示的頁面
final List<BoostContainer> _offstage = <BoostContainer>[];//以前的頁面,用作緩存
複製代碼

在hybrid_stack_manager中,flutter和原生都是使用navigationController,因此須要保持兩端導航棧的一致。而在flutter boost中徹底放開了原生端頁面的展現方式,在flutter端維護更加自由的導航棧,實現了更加自由的混合導航棧。 screenshot.png
iOS端:在AppDelegate中調用flutterBoost的start方法,在這裏完成初始化flutterEngine,配置pageBuilder等工做

MMCommonNAVI *rootNav = [[MMCommonNAVI alloc]initWithRootViewController: [ZNUIManager sharedInstance].rootTab];
//flutter boost啓動
ZNFlutterRouter *flutterRouter = [ZNFlutterRouter sharedRouter];
flutterRouter.navigationController = rootNav;
[FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:flutterRouter
                                                    onStart:^(FlutterViewController *fvc) {
                                                            
                                                        }];
[rootNav setNavigationBarHidden:YES];
    
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = rootNav;
[self.window makeKeyAndVisible];
複製代碼

須要實現一個本身的路由控制類,而且傳入navigationController。ui

@interface ZNFlutterRouter : NSObject<FLBPlatform>
@property (nonatomic,strong) UINavigationController *navigationController;
+ (ZNFlutterRouter *)sharedRouter;
@end
複製代碼
- (void)openPage:(NSString *)name
          params:(NSDictionary *)params
        animated:(BOOL)animated
      completion:(void (^)(BOOL))completion
{
    //打開原生頁面,從原生或者flutter頁面調用最終都會到這裏
    if([name isEqualToString:@"hrd://product_detail"]){
        // 跳轉至設備詳情
        ZNProductDetailViewController *next = [ZNProductDetailViewController new];
        next.sn = [params objectForKey: @"pid"];
        [[[ZNUIManager sharedInstance] appRootNavi] pushViewController:next animated:YES];
    }
    //打開flutter頁面,全部的flutter頁面都會走到下面,設置不一樣的name就會生成widget對應的原生頁面
    else if([params[@"present"] boolValue]){
        FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
        [vc setName:name params:params];
        [self.navigationController presentViewController:vc animated:animated completion:^{}];
    }else{
        FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
        [vc setName:name params:params];
        [self.navigationController pushViewController:vc animated:animated];
    }
}
複製代碼

打開flutter和原生頁面最終都會走到openPage這個方法中,從原生打開頁面是原生直接調用openPage方法,而從flutter側打開頁面是經過methodChannel調用openPage方法。atom

在flutter側也須要註冊pageName和widget的對應關係。url

static void registerPageBuilder(){
  //註冊flutter頁面
  FlutterBoost.singleton.registerPageBuilders({
    'hrd://product_list':(pageName, params, uniqueId) {
      RouterOption option = new RouterOption(url: pageName,params: params);
      return new ProductList(option);
    },
    //tabbar第三個tab,push進來頁面和直接展現在tab中的頁面採用一樣的配置方式
    'hrd://board_main':(pageName, params, uniqueId) {
      return new BoardMain();
    },
  });
  FlutterBoost.handleOnStartPage();
}
複製代碼

上面代碼能夠看到hrd://product_list 這是對應一個push進來的頁面,而hrd://board_main是app啓動後就初始化在tabbarViewController中的,兩個頁面都是相同的初始化和註冊方式,具體怎麼展現由原生端決定。spa

原生側打開flutter頁面調用方法以下,要傳入pageName和params。

[ZNFlutterRouter.sharedRouter openPage:@"hrd://upload_contract" params:@{@"orderCode":self.orderCode,@"customerCode":self.orderDetailModel.customerCode,@"changeCode":@"",@"state":@"0"} animated:YES completion:^(BOOL finished) {
                    
                }];
複製代碼

flutter打開原生頁面調用方法以下:

FlutterBoost.singleton.openPage(url, params, animated: animated,resultHandler: resultHandler)
複製代碼

須要注意的是,從頁面效果上看原生打開flutter是push過來的,可是因爲是直接把widget放入原生的viewController裏面的,因此第一個flutter頁面的左上角沒有回退按鈕的,咱們須要本身在Appbar中加入回退按鈕並調用回退的方法。

return Scaffold(
  appBar: AppBar(
    title: Text(‘Flutter頁面'),
    elevation: 0.0,
    leading: IconButton(icon: Icon(Icons.arrow_back_ios), onPressed: popCurrentPage),
  ),
  body: Container(),
);
複製代碼

flutter關閉當前頁面的調用方法以下:

FlutterBoost.singleton.closeCurPage(params);
複製代碼

集成flutter_boost的過程是很順利的,不過原理理解廢了一番功夫,主要是因爲源代碼裏面註釋太少了,而代碼量又特別大,我畫了個類圖幫助理解,能夠從這個類圖看到框架中各個類的關係。 Container.jpg

相關文章
相關標籤/搜索