iOS原生混編Flutter路由指南及解決Flutter首頁閃白屏問題

前言

公司iOS項目自從20年從原生引入了Flutter以來,生產(chǎn)力來說不可謂提升不大,畢竟1個人就可以干兩端,其他端的適配只需要簡單的適配即可。從Flutter1.22.6開始一直適配到現(xiàn)在的2.10.5,期間大大小小產(chǎn)生的坑也不少。Flutter混編的路由方案我們采用的是阿里的flutter_boost方案,最近項目也是登錄模塊用Flutter進行了重構,和原先只在二級頁面使用相比,應用冷啟動就進入Flutter頁面其實十分有挑戰(zhàn),畢竟引擎的啟動要時間。這不,當你的啟動圖和登錄界面使用了特殊的背景圖就會有短暫的閃白屏的效果,如下,其實就是Flutter引擎還沒渲染完畢的真空時間。如是就有了下面的解決方案。

20220729_104447.GIF

iOS接入Flutter及解決首頁閃白屏全過程

一、創(chuàng)建flutter module項目

我們通過xcode新建一個demo ios項目,然后在項目目錄下創(chuàng)建flutter module項目

//創(chuàng)建flutter module項目
flutter create -t module flutter_module

//pubspec文件引入flutter_boost,我這里采用本地引入方式

flutter_boost:
    path: flutter_boost-3.0-null-safety-release.2.1

初始化ios項目 Pod


pod init

Podfile配置代碼如下


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

target 'NaviteMixinFlutterDemo' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!
  install_all_flutter_pods(flutter_application_path)
  # Pods for NaviteMixinFlutterDemo

end


然后pod install。這樣子我們的混編flutter項目就構建完成了


pod install

二、iOS原生注冊flutter引擎及實現(xiàn)flutter_boost路由

注冊Flutter引擎,我們只需使用flutter_boost的方式在原生appDelegate注冊即可。


func registerFlutter(application: UIApplication) {
        FlutterBoost.instance().setup(application, delegate: JFFlutterRoute.shareInstance) { engine in
            guard let _ = engine else { return }
            print("engine success")
            //you can register your channel in there.
        }
    }

實現(xiàn)flutter_boost路由跳轉協(xié)議deleagte。獲取頂層vc的代碼,我往demo項目寫了個擴展,這里不再贅述。


extension JFFlutterRoute: FlutterBoostDelegate {
    internal func pushNativeRoute(_ pageName: String!, arguments: [AnyHashable : Any]!) {
        switch pageName {
        case JFFluterRouteName.nativeMainPage:
            let vc = ViewController()
            let navi = JFNavigationViewController(rootViewController: vc)
            AppDelegate.switchRootVC(vc: navi)
            break
        case JFFluterRouteName.nativePage:
            let vc = ViewController()
            vc.title = "原生二級頁面"
            UIApplication.shared.visibleNavigationController()?.pushViewController(vc, animated: true)
            break
        default:
            break
        }
    }
    
    internal func pushFlutterRoute(_ options: FlutterBoostRouteOptions!) {
        guard let vc = JFFlutterViewController() else { return }
        vc.setName(options.pageName, uniqueId: options.uniqueId, params: options.arguments, opaque: options.opaque)
        UIApplication.shared.visibleNavigationController()?.pushViewController(vc, animated: true)
    }
    
    internal func popRoute(_ options: FlutterBoostRouteOptions!) {
        //只演示push,pop.  present dismiss的處理 自己處理
        UIApplication.shared.visibleNavigationController()?.popViewController(animated: true)
    }
}

三、flutter端注冊路由表


//MyApp Widget中注冊

static Map<String, FlutterBoostRouteFactory> pageMap = {
    JFRoute.loginPage: (settings, uniqueId) {
      return CupertinoPageRoute(
        settings: settings,
        builder: (_) => const LoginPage(
        ),
      );
    },
    JFRoute.demoPage: (settings, uniqueId) {
      return CupertinoPageRoute(
        settings: settings,
        builder: (_) => const DemoPage(
        ),
      );
    },
  };

  Route<dynamic>? routeFactory(RouteSettings settings, String? uniqueId) {
    FlutterBoostRouteFactory? func = pageMap[settings.name!];
    if (func == null) {
      return null;
    }
    return func(settings, uniqueId ?? "");
  }


  Widget appBuilder(Widget home) {
    return MaterialApp(
      home: home,
      debugShowCheckedModeBanner: true,
      ///必須加上builder參數(shù),否則showDialog等會出問題
      builder: (_, __) {
        return home;
      },
    );
  }

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return FlutterBoostApp(
        routeFactory,
        appBuilder: appBuilder,
      );
  }
  
 //簡單封裝一個路由類
 
 class JFRoute {
  
  static var loginPage = "jf://loginPage";
  static var nativeMainPage = "jf://nativeMainPage";
  static var nativePage = "jf://nativePage";
  static var demoPage = "jf://demoPage";

  static pushRoute(String url,
      {Map<String, dynamic>? urlParams,
      bool opque = true,
      bool withContainer = true,}) {
    withContainer = true;
    BoostNavigator.instance.push(
        url, //required
        withContainer: withContainer, //optional
        arguments: urlParams, //optional
        opaque: opque, //optional,default value is true
      );
  }

  static popRoute() {
    BoostNavigator.instance.pop();
  }
}

至此我們只要把啟動根控制器換成flutter登錄頁面,我們一啟動就會顯示一個Flutter登錄頁面.


guard let scene = (scene as? UIWindowScene) else { return }
        self.window = UIWindow(windowScene: scene)
        let vc = JFLoginFluterViewController()
        vc.setName(JFFluterRouteName.loginPage, uniqueId: "", params: [:], opaque: true)
        self.window?.rootViewController = JFNavigationViewController(rootViewController: vc)
        self.window?.makeKeyAndVisible()

四、解決Flutter首頁閃白屏問題

分析原因:由于引擎的啟動要時間,當啟動圖和登錄頁面都用了同一個背景圖時,會有一個白屏的閃縮大概(0.5-1s)左右,機型越好速度越快。那么我們要如何解決這個問題?

  • 方案一

我們可以用一個原生的vc帶一個背景圖,然后flutter vc當做child vc。 這種做法是可行的,但是每次修改都得做一次相同的操作,而且不靈活。 不太推薦。

  • 方案二

也是我目前實現(xiàn)的一個方案,由于UIColor自帶一個api可以通過圖片來渲染出一種特殊的顏色,我們只需要在flutter基類,判斷需要修改背景色的路由,做一次UIImage渲染成顏色的動作即可。當然由于圖片會拉伸,我們直接new一個image是不行的,我么需要用UIImageView來承載圖片,讓它自動撐滿,再對圖片截圖然后緩存起來,這樣渲染出來的圖片就可以啟動圖一摸一樣了。
代碼如下,以及最終效果。

20220729_104243.GIF

 private func tryChangeLoginBgColorIfNeed() {
        guard JFFlutterRoute.needBgViewRoutes.contains(self.name) else { return }
        if let color = JFFlutterLoginBgColor {
            //use cache color
            print("use cache color")
            self.view.backgroundColor = color
            return
        }
        let screenSize = UIScreen.main.bounds.size
        let launchView = UIImageView(image: UIImage(named: "bg"))
        launchView.contentMode = .scaleToFill
        launchView.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height)
        if let image = UIImage.jf_convertViewToImage(view: launchView) {
            let myColor = UIColor(patternImage: image)
            JFFlutterLoginBgColor = myColor
            self.view.backgroundColor = myColor
        }
    }

Demo地址

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容