Flutter-boot 你整合成功了嗎?

萌呆寶發表於2019-12-09

flutter-boot

首先區分兩個概念'flutter-boot''flutter_boost'都是alibba開源的flutter工具,但是是兩個完全不同的東西。

FlutterBoost

新一代Flutter-Native混合解決方案。 FlutterBoost是一個Flutter外掛,它可以輕鬆地為現有原生應用程式提供Flutter混合整合方案。FlutterBoost的理念是將Flutter像Webview那樣來使用。在現有應用程式中同時管理Native頁面和Flutter頁面並非易事。 FlutterBoost幫你處理頁面的對映和跳轉,你只需關心頁面的名字和引數即可(通常可以是URL)。

Flutter-boot

這是一個幫助你在已有原生應用的情況下,搭建flutter混合開發環境的工具。 我們提供了標準的混合工程結構,同時支援混合棧(一套原生和flutter之前頁面通訊和過渡的方案)的快速接入。

以上是摘抄GitHub上flutter-boot 和 flutter_boost的官方介紹,也因為官方文件上的整合步驟太過簡略,在整合的過程中才過了不少的坑,所以都記錄下來並且分享出來,希望可以給看文章的你,帶去一些解決方案和思路。

本文主要記錄一次flutter-boot的整合過程,對於flutter_boost的使用放在後續的文章中再介紹。

Native工程環境

已有一個維護迭代過多個版本的Native工程,並且已經做過元件化,拆分了多個元件(就是常見的組合元件的方法,通過CocoaPods的方式新增安裝各個元件,製作CocoaPods遠端私有庫,將其發不到公司的gitlab或GitHub,使工程能夠Pod下載下來),本次整合flutter-boot的目的是要把其中一個元件,使用flutter重寫。

這裡強調做過元件化,是因為很多問題都是因為元件化了才引起的如果只是一個單純的工程整合的話,會簡單不少,而且後續如果只是某個元件需要依賴flutter,還需要有一些特殊的操作。

Flutter-boot整合步驟和遇到的問題

這裡可以跟著我一起來操作,下面的步驟我都寫得儘量詳細,遇到的問題都有截圖或者文字描述,以及解決方案跟在後面。

  1. $ cd somepath/my_repo

這裡可以是你的Native路徑 , 也可以是一個新建立的資料夾,這裡我是用了一個新的資料夾路徑,目的是保證之後的路徑和官方文件上的目錄結構一致。

somepath/my_repo
└──my_android
└──my_ios
└──my_flutter
    └──.git
    └──.gitignore
    └──android_shell
    └──ios_shell
    └──android
    └──ios
複製程式碼
  1. $ flutter-boot init

問題一:link 失敗,這裡忘記截圖了。。。特別抱歉

解決方案:

  • 出現這個問題的原因,是因為flutter-boot的版本支援問題,官方文件上還要求裝1.5.0的版本(其實已經支援到1.9了),但是一般我們電腦上的版本都比這個高,所以,要先降低版本,切換到master分支,然後執行init命令
擁有^1.5.0的flutter環境
複製程式碼
  • 找到npm包中flutter-boot的原始碼,修改一下判斷版本號的程式碼,不過。。。不推薦這種方式(但是親測有效,畢竟來回切換版本號太浪費時間了)
  1. $ 請輸入flutter工程名稱: my_flutter_module (可以回車跳過,會自動生成一個叫my_flutter_module的module)
  2. $ 請輸入flutter倉庫地址,回車跳過
  3. $ ? 是否存在iOS工程? Yes
  4. $ ? iOS工程本地地址 somepath/my_repo/my_ios 這裡輸入你的native工程目錄

問題二:podfile內容copy出錯,你也可能不會遇到這個問題,這裡是flutter-boot本身的bug,只去匹配了字串'end',剛好我們使用了這個日曆的庫,就到這裡把podfile檔案中的內容給截斷了

pod 'FSCalendar', '2.8.0' # 日曆
複製程式碼

Flutter-boot 你整合成功了嗎?

解決方案:

  • 在執行flutter-boot init 之前檢查一下你的podfile檔案,看是否也存在這種字元中含有'end',可以先刪除掉,init命令執行完之後再手動新增,注意原有target和新生成的Runner target中都要新增
  • 檢視從那裡開始copy截斷,手動把後面的補齊
  • 注意 只copy pod 引用 不需要copy post_install中的內容(如果你的podfile中有post_install的話)
post_install do |installer_representation|
    installer_representation.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            ...
        end
    end
end
複製程式碼

問題三:

Flutter-boot 你整合成功了嗎?

這裡是你工程的post_install和init命令生成的fbpodhelper.rb中的post_install衝突導致,

解決方案:

  • 還是因為降低了flutter版本導致的,使用flutter 1.9版本就不會有這個問題,但是1.5會報這個錯誤
  • 還有同事反饋,pod版本升級到最新也不會有這個問題,我的是1.6.1,目前最新已經是1.8以上了,我還沒有試這個-。-
  • 修改fbpodhelper.rb中如下程式碼:
flutter_application_path = fbFlutterPath
require File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
複製程式碼

改為:

 # ============= Flutter config begin ============== #
  
  flutter_application_path = './fpf_flutter/'
  podhelper_path = File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
  
  podhelper_content = File.read(podhelper_path);
  podhelper_post_isntall = "post_install do |installer|";
  # 當post_install重複時需要去重以避免發生pod install錯誤:multiple post_install hooks is unsupported
  if podhelper_content.scan(/(#{podhelper_post_isntall})/).length > 0 then
    
    podhelper_buffer = podhelper_content.gsub(podhelper_post_isntall, "def update_configs(installer, framework_dir)")
    eval(podhelper_buffer, binding)
    
    else
    eval(File.read(podhelper_path), binding)
  end
  
  # ============= Flutter config end ============== #
複製程式碼
  1. 混合工程初始化完成
info [init] 你可以在建立Android工程後呼叫 flutter-boot link來關聯flutter
info [init] 混合工程初始化完成
複製程式碼
  1. 執行flutter

你以為到這裡就真的完成了嗎,run一下~~ 記得要選擇 Runner target,好像確實可以成功。但是flutter-boot的作用是你可以有兩個開發視角,flutter視角下,不需要關心native,native視角,甚至可以不用安裝flutter環境。那麼,去flutter視角看一下,把flutter module拖入VSCode開啟,然後fn+f5,執行起來

那麼在flutter視角執行起來了嗎

問題四: Run Flutter Build Script 中的指令碼./my_flutter_module/.ios/Flutter/flutter_export_environment.sh 路徑找不到,一個很奇怪的現象,pod install成功後檢視路徑,沒問題,但是flutter run之後,這裡的路徑就改變了,相對路徑錯誤,確實找不到這個指令碼

解決方案:

  • $ cd ./my_flutter_module/.ios/Flutter/podhelper.rb
  • 開啟這個檔案,其實就是一個ruby指令碼,找到設定flutter_export_environment.sh路徑的程式碼,做一下修改,改為:
flutter_export_environment_path = File.join(project_directory_pathname, relative, 'flutter_export_environment.sh');
複製程式碼
  1. AppDelegate 中註冊flutter 引擎

In AppDelegate.h:

@import UIKit;
@import Flutter;

@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
複製程式碼

In AppDelegate.m:

@import FlutterPluginRegistrant; // Only if you have Flutter Plugins

#import "AppDelegate.h"

@implementation AppDelegate

// This override can be omitted if you do not have any Flutter Plugins.
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
  [self.flutterEngine runWithEntrypoint:nil];
  [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end
複製程式碼

In ViewController.m

#import "AppDelegate.h"
#import "ViewController.h"
@import Flutter;

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button addTarget:self
               action:@selector(handleButtonAction)
     forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"Press me" forState:UIControlStateNormal];
    [button setBackgroundColor:[UIColor blueColor]];
    button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
    [self.view addSubview:button];
}

- (void)handleButtonAction {
    FlutterEngine *flutterEngine = [(AppDelegate *)[[UIApplication sharedApplication] delegate] flutterEngine];
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self presentViewController:flutterViewController animated:false completion:nil];
}
@end
複製程式碼

至此,flutter-boot 環境整合成功!!! 喜大普奔啊,終於不報錯了~~

但是,回到我們最初整合flutter-boot的目的,是要使用flutter重寫某一個業務線。因為是元件化的工程,所以flutter引擎的註冊也不希望暴露在APPdelegate中,而是放在一個管理各個業務線分發的平臺元件中,那麼就涉及到,某一個元件中要使用flutter。

用唄,主工程都能用,元件一樣能用,然後,當你在元件程式碼中新增如下程式碼之後:

#import <Flutter/Flutter.h>
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
複製程式碼

找不著,各種找不著啊。。。好像各個業務線元件根本就不能引用flutter以及flutter_module

Flutter-boot 你整合成功了嗎?
解決方案:

  • 各業務線的podspec檔案中新增依賴:
  • $ s.dependency 'Flutter'
  • $ s.dependency 'FlutterPluginRegistrant'

到這裡整合flutter-boot 才算取得了階段性的勝利✌️,希望這篇文章,可以給廣大勇於嘗試flutter-boot的開發人員,帶去一些福利(主要是節省一些時間,都不是什麼難解決的問題,就是得不斷的嘗試)。

在這個過程中,也去看了GitHub上他們所有開放和關閉的issues,並且嘗試了N次,過程比較坎坷,不過成功了還是很可的事?。

2019.12.11 補充 flutter-boot 推到遠端之後,其他同事拉程式碼,裝flutter-boot之後遇到的一些問題記錄:

問題一:

Flutter-boot 你整合成功了嗎?

產生原因: 曾用 root 使用者進行了區域性安裝npm包,留下所屬權為 root 的檔案,導致普通使用者 無法訪問 root的檔案內容。

解決方案:

  • 找到報無許可權資料夾:/usr/ local/lib/node_ modules/ flutter -boot/node_ modules/core-js
  • 檢視無許可權資料夾的許可權:ls -la /usr/ local/lib/node_ modules/ flutter -boot/node_ modules/core-js
    Flutter-boot 你整合成功了嗎?
    發現許可權擁有者是root,但應該是我們本機使用者
  • 更改許可權擁有者(後面是使用者名稱和資料夾名): sudo chown -R 使用者名稱 /usr/ local/lib/node_ modules/ flutter -boot/node_ modules/core-js
  • 再次檢視資料夾許可權就改成使用者了,npm包就可以正常下載啦。

問題二:my_flutter_module中資料缺失

就是別的同事拉下flutter_module中的程式碼裡面,沒有engine這個資料夾

Flutter-boot 你整合成功了嗎?

解決方案:

  • .gitignore中去掉對這個資料夾的忽略,然後再push一次
    Flutter-boot 你整合成功了嗎?

問題三: native視角如何使用

其實這個也不能算是個問題啦,應該歸到上面的使用步驟裡面,畢竟這個flutter-boot的環境搭建起來,是要給大家一起用的,又不是指給自己用?

其實切換到native視角,就會覺得這個工具,和我最開始選擇用flutter-boot的初衷有一些偏離。最初,我們是希望有一套完全不影響原生開發的框架,去接入flutter,完全不影響是什麼意思呢,領導希望,native開發,不需要安裝flutter環境,不需要,不需要,不需要,重要的地方強調一下,但是通過這幾天對flutter-boot的研究,發現根本做不到這一點。

首先別人拉下native程式碼和flutter程式碼之後,需要執行link命令去做軟連線,但是本身flutter-boot就是依賴flutter庫的,可能說依賴不太準確,但是link命令中就有判斷flutter版本的邏輯,如果沒有flutter的開發環境,這一步就過不去,那你的native和flutter如何連結上呢??

解決方案:

  • 這個問題其實沒有解決,只是有一個思路
  • flutter的所有產物,都是一個framework ,我們可以手動把這些framework拖入到工程中,不用pod引入
  • 其實大部分依賴的framework 都只需要拖進去一次(這裡不考慮更新)
  • 只有App.framework 需要頻繁的修改,這裡可以自己進入一些指令碼,重定向他的產物
  • native工程中之前的依賴和各種path 都需要修改
  • 這個方案,理論上可行,但是需要改動的地方太大,還有各種維護的問題,成本都很大

如果看這篇文章的小夥伴,有更好的方式,歡迎交流!!!

相關文章