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

前言

公司iOS項目自從20年從原生引入了Flutter以來,生產力來說不可謂提升不大呀狼,畢竟1個人就可以干兩端,其他端的適配只需要簡單的適配即可损离。從Flutter1.22.6開始一直適配到現(xiàn)在的2.10.5,期間大大小小產生的坑也不少哥艇。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地址

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末械巡,一起剝皮案震驚了整個濱河市刹淌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌讥耗,老刑警劉巖芦鳍,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異葛账,居然都是意外死亡柠衅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門籍琳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菲宴,“玉大人,你說我怎么就攤上這事趋急『嚷停” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵呜达,是天一觀的道長谣蠢。 經常有香客問我,道長查近,這世上最難降的妖魔是什么眉踱? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮霜威,結果婚禮上谈喳,老公的妹妹穿的比我還像新娘。我一直安慰自己戈泼,他們只是感情好婿禽,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布赏僧。 她就那樣靜靜地躺著,像睡著了一般扭倾。 火紅的嫁衣襯著肌膚如雪淀零。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天膛壹,我揣著相機與錄音驾中,去河邊找鬼。 笑死恢筝,一個胖子當著我的面吹牛哀卫,可吹牛的內容都是我干的巨坊。 我是一名探鬼主播撬槽,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼趾撵!你這毒婦竟也來了侄柔?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤占调,失蹤者是張志新(化名)和其女友劉穎暂题,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體究珊,經...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡薪者,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了剿涮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片言津。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖取试,靈堂內的尸體忽然破棺而出悬槽,到底是詐尸還是另有隱情,我是刑警寧澤瞬浓,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布初婆,位于F島的核電站,受9級特大地震影響猿棉,放射性物質發(fā)生泄漏磅叛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一萨赁、第九天 我趴在偏房一處隱蔽的房頂上張望宪躯。 院中可真熱鬧,春花似錦位迂、人聲如沸访雪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽臣缀。三九已至坝橡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間精置,已是汗流浹背计寇。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脂倦,地道東北人番宁。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像赖阻,于是被迫代替她去往敵國和親蝶押。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容