官方混合方案多引擎的弊端
- 官方方案在Native和Flutter頁面交叉跳轉時於Flutter Engine數量會線性增加導致記憶體暴增(這裡指圖片快取等比較消耗記憶體的物件)。
- 多個FlutterViewController,外掛的註冊和通訊將會變得混亂難以維護,訊息的傳遞的源頭和目標也變得不可控。
- Flutter頁面和Native頁面差異化,一些統一操作增加複雜度。
- Flutter應用中全域性變數在各獨立頁面不能共享的問題。
- iOS平臺記憶體洩露的問題。
官方目前就共享同一個引擎做混合開發沒有很好的支援。
混合棧管理問題
混合開發(RN,Flutter,Native)導致的混合棧的管理一直是個比較煩的問題,iOS端的表現主要包括對導航棧的一些特殊處理(增刪改之類),之前做RN和Native混合開發就遇見過類似的問題。本身Native有自己的一套導航棧,RN自己也有一套Navigation
的管理(我們這裡使用社群維護的React Navigation
),每次Native開啟RN是開啟一個新的VC,但RN頁面通過React Navigation
開啟一個或多個RN頁面時實際上是在同一個VC中,這就導致RN和Native交叉跳轉多次後混合棧變得混亂,Native並不知道棧裡面實際有多少個頁面,想要直接返回到這些交叉頁面的某一個頁面(可能是Native或者RN)也變得困難,所以我們在處理RN和Native混合棧跳轉實踐過程新增了很多特殊處理,包括什麼時候使用新開VC的push
,什麼時候使用不新開VC的push
,以及手勢返回什麼時候使用Native響應,什麼時候使用RN響應,跨多個頁面pop
如何計算要pop
幾個等一系列問題,讓開發和維護都變得複雜。
當然Flutter和Native混合開發也要面對類似的問題,再加上之前的RN,導航棧的管理就更加棘手了。
FlutterBoost
官方介紹:
The philosophy of FlutterBoost is to use Flutter as easy as using a WebView
趟過坑的大廠(阿里巴巴-閒魚技術)很明顯已經走在了前面,開源了名為FlutterBoost的外掛。它採用共享引擎的模式實現,解決的多引擎的很多弊端。統一管理Flutter頁面對映和跳轉,讓push
和pop
的導航棧操作和Native保持一致,在我們在開發過程中就無須關心Native和Flutter導航棧交叉跳轉所帶來的各種問題。
目前FlutterBoost支援穩定版本的flutter v1.9.1-hotfixes
,最新的1.12
正在適配中。
整合
- 在Flutter模組的
pubspec.yaml
新增依賴:
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: '0.1.63'
複製程式碼
- Flutter模組下執行
flutter pub get
更新依賴資源。 - 在Native工程下執行
pod install
將Flutter相關產物依賴帶到Native。
這裡需要注意每次更改pubspec.yaml檔案重新做flutter pub get
後,都要做pod install
重新依賴產物。不然產物有變更而Native沒有同步更新導致報錯執行不起來。
在Flutter模組中使用
- 在
runApp()
函式傳入的RootWidget
中註冊所有的Flutter頁面。
@override
void initState() {
super.initState();
FlutterBoost.singleton.registerPageBuilders({
'account/about': (pageName, params, _) => AboutWidget(),
'account/feedback': (pageName, params, _) => FeedbackWidget(),
});
}
複製程式碼
- 在
RootWidget
的build
方法中初始化FlutterBoost
。
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: FlutterBoost.init(),
}
複製程式碼
- 在Flutter模組中開啟和關閉頁面
FlutterBoost.singleton.open('account/feedback');
FlutterBoost.singleton.open('native', urlParams: {'id': '123456'});
FlutterBoost.singleton.open('native', urlParams: {'id': '123456'}, exts: {});
複製程式碼
FlutterBoost.singleton.close('account/feedback', result: {}, exts: {});
FlutterBoost.singleton.closeCurrent(result: {}, exts: {});
複製程式碼
根據頁面需求呼叫方法即可,還有一些更細節的用法如Native和Flutter間回傳值等,有需要的可以去FlutterBoost的example裡面瞭解。
在Native中使用
使用之前我們先了解兩個類和一個協議:
FlutterBoostPlugin
(共享引擎的管理,開啟和關閉頁面)FLBFlutterViewContainer
(在官方的FlutterViewController
上做了一層封裝)FLBPlatform
(實現此協議後統一處理FlutterBoostPlugin
開關頁面的回撥)
具體使用:
- 工程啟動時候做
FlutterBoostPlugin
的初始化
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
PlatformRouterImp *router = [PlatformRouterImp new];
[FlutterBoostPlugin.sharedInstance startFlutterWithPlatform:router
onStart:^(FlutterEngine *engine) {
}];
}
複製程式碼
其中PlatformRouterImp
主要是實現FLBPlatform
協議,讓使用FlutterBoostPlugin
開啟和關閉頁面時能夠統一處理。
@protocol FLBPlatform;
@interface PlatformRouterImp : NSObject<FLBPlatform>
@property (nonatomic,strong) UINavigationController *navigationController;
@end
複製程式碼
@implementation PlatformRouterImp
- (void)open:(NSString *)name urlParams:(NSDictionary *)params exts:(NSDictionary *)exts completion:(void (^)(BOOL))completion {
BOOL animated = [exts[@"animated"] boolValue];
MyFlutterViewController *vc = [[MyFlutterViewController alloc] init];
[vc setName:name params:params];
[self.navigationController pushViewController:vc animated:animated];
- (void)present:(NSString *)name urlParams:(NSDictionary *)params exts:(NSDictionary *)exts completion:(void (^)(BOOL))completion {
BOOL animated = [exts[@"animated"] boolValue];
MyFlutterViewController *vc = [[MyFlutterViewController alloc] init];
[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];
MyFlutterViewController *vc = (id)self.navigationController.presentedViewController;
if ([vc isKindOfClass:MyFlutterViewController.class] && [vc.uniqueIDString isEqual: uid]) {
[vc dismissViewControllerAnimated:animated completion:^{}];
} else {
[self.navigationController popViewControllerAnimated:animated];
}
}
@end
複製程式碼
之後每次使用FlutterBoostPlugin
開啟和關閉頁面都會回撥給實現了FLBPlatform
協議的PlatformRouterImp
。
[FlutterBoostPlugin open:@"account/Feedback" urlParams:nil exts:nil onPageFinished:^(NSDictionary *params) {
} completion:^(BOOL isComplete) {
}];
複製程式碼
MyFlutterViewController
繼承自FLBFlutterViewContainer
,目前主要用來處理Native頁面和Flutter頁面交叉跳轉時Native導航條是否展示的問題,混合開發雖然Flutter頁面外層也是一個VC,但我們並不希望導航條UI樣式也使用Native的,Flutter頁面應該有自己的導航條樣式和邏輯,這樣也更利於日後的維護和擴充。
混合踩坑
- Native頁面手勢返回到Flutter頁面會跳一下(只有真機會出現模擬器沒問題)。
這個問題本來最初一直以為是FlutterBoost
的問題,定位了好久才發現鍋在Native。我們Native工程每次跳到下一個頁面會把上一個頁面的截圖儲存在記憶體中,然後在每次手勢返回時展示此截圖。問題就出在截圖的程式碼。
使用renderInContext
方法截圖
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
UIGraphicsBeginImageContextWithOptions(window.bounds.size, window.opaque, 0);
[appdelegate.window.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
複製程式碼
使用drawViewHierarchyInRect
方法截圖
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
UIGraphicsBeginImageContextWithOptions(window.bounds.size, window.opaque, 0);
[window drawViewHierarchyInRect:window.bounds afterScreenUpdates:NO];
UIImage *snapshotImage=UIGraphicsGetImageFromCurrentImageContext();
複製程式碼
renderInContext
是對view
的layer
渲染到當前的上下文中。drawViewHierarchyInRect
是對view
進行一個快照,然後將快照渲染到當前的上下文中。
在Flutter頁面使用renderInContext
這種方式截圖會導致截圖不完整,只擷取了一部分(每次手勢返回會先展示不完整的截圖再展示頁面,所以會有跳動的感覺),具體原因猜測跟Flutter的渲染方式有關係,若有明白的大神還望指點~
- Flutter頁面的
ListView
滾動很卡頓
這個問題最初出現在模擬器,發現也有很多人遇見類似卡頓的問題,說Debug
模式的Flutter效能不高,有卡頓也正常,當時還在想佈局這麼簡單的List
也能卡成這樣,嘆氣~,然後果斷真機跑一下,What?還有同樣的問題,定位良久發現是手勢處理的鍋。
這個問題是由於為了響應Native的手勢返回在處理手勢時將RRDFlutterViewController
的手勢給攔截了,導致ListView
的滑動剛被觸發就取消了,這就造成了類似很卡的效果。
解決方式將觸控事件傳遞給FlutterView。
gestureRecognizer.cancelsTouchesInView = NO;
複製程式碼
總結
FlutterBoost初步使用感覺還是不錯的,幫助我們解決了很多官方的坑,阿里巴巴-閒魚技術也確實投入了不少人力在支援,目前來看是跟著Flutter的穩定版本不斷做更新的。
後續有坑和總結還會在本文中作補充。
友情提示:如果你也在用FlutterBoost,遇見問題多去看看官方Demo和issue,相信大部分都會有覺解方案。