標籤:

為現有 iOS項目集成 Flutter

為現有 iOS項目集成 Flutter

Flutter 在經過一番洗禮後終於迎來了 Release Preview 1版本,也吸引了更多人的關注。使用 Flutter從頭開始寫一個 App非常輕鬆,但越來越多的人發現 Flutter貌似並不很友好地支持現有的 App接入。所以本文帶大家了解一下如何讓現有 App支持 Flutter。

環境

Flutter:0.5.1

Xcode 9.4.1

Flutter工程:flutter/examples/hello_world

Debug: Flutter Hot Restart

我們都知道在開發環境下,Flutter的 hot restart 對 UI快速成型是非常有幫助的,要讓現有的 App支持 Flutter並且開啟 Hot restart也不難。

Flutter(engine) 基礎庫

首先,在你的項目裡面拖入 Flutter.framework,這個庫是 Flutter Engine,承載了 Dart運行時和繪圖引擎。Flutter.framework和命令行工具版本是一一對應的,如果你不知道從哪裡找這個文件,可以直接在 Flutter源碼項目裡面進行一次 flutter run,然後你就能在 /<project>/ios/Flutter/目錄下面找到了,直接拖進項目即可。

Flutter ViewController

接下來需要把 Flutter的基礎代碼引入現有工程,有了基礎的 Flutter ViewController才可以顯示 Flutter視圖。這一步很簡單,只需要在你現有的 ViewController中 Push過去就可以了:

- (void)jumpToFlutter {

FlutterViewController *viewController = [FlutterViewController new];

[self.navigationController pushViewController:viewController animated:YES];

}

但是還需要注意需要把 AppDelegate裡面的生命周期事件傳遞給 Flutter

Release Preview 1後文檔中提到有 FlutterAppLifeCycleProvider這個協議,但是 0.5.1還沒有,所以這裡先野路子來了

  1. 直接讓現有的 AppDelegate繼承 FlutterAppDelegate即可,但這帶來的負面影響是 root ViewController被設置為 Flutter ViewController
  2. 自行改造 AppDelegate。很多中大型 App喜歡在開發中將業務模塊化,幸好 Flutter APPDelegate並不是很難改造過來,也能支持模塊 Delegate。首先讓你的 Delegate遵循 FlutterPluginRegistry協議

// AppDelegate 或者模塊的 Delegate

@interface DemoFlutterBaseAppDelegate : NSObject <ModularApplicationDelegate, FlutterPluginRegistry>

/**

FlutterBinaryMessenger

this determines which view controller is the flutter view controller

nomally, flutter view controller provides the binary messages

@return root Flutter ViewController

*/

- (NSObject<FlutterBinaryMessenger> *)binaryMessenger;

/**

FlutterTextureRegistry

this determines which view controller is the flutter view controller

nomally, flutter view controller provides the custom textures

@return root Flutter ViewController

*/

- (NSObject<FlutterTextureRegistry> *)textures;

@end

然後在實現中支持 Flutter messenger 和 texture

// Registrar 聲明和實現

@interface DemoFlutterAppDelegateRegistrar : NSObject<FlutterPluginRegistrar>

@property(nonatomic, copy) NSString *pluginKey;

@property(nonatomic, strong) DemoFlutterBaseAppDelegate *appDelegate;

- (instancetype)initWithPlugin:(NSString*)pluginKey appDelegate:(DemoFlutterBaseAppDelegate*)delegate;

@end

@implementation DemoFlutterAppDelegateRegistrar

- (instancetype)initWithPlugin:(NSString*)pluginKey appDelegate:(DemoFlutterBaseAppDelegate*)appDelegate {

self = [super init];

NSAssert(self, @"Super init cannot be nil");

_pluginKey = [pluginKey copy];

_appDelegate = appDelegate;

return self;

}

- (NSObject<FlutterBinaryMessenger>*)messenger {

return [_appDelegate binaryMessenger];

}

- (NSObject<FlutterTextureRegistry>*)textures {

return [_appDelegate textures];

}

- (void)publish:(NSObject*)value {

_appDelegate.pluginPublications[_pluginKey] = value;

}

- (void)addMethodCallDelegate:(NSObject<FlutterPlugin>*)delegate

channel:(FlutterMethodChannel*)channel {

[channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {

[delegate handleMethodCall:call result:result];

}];

}

- (void)addApplicationDelegate:(NSObject<FlutterPlugin>*)delegate {

[_appDelegate.pluginDelegates addObject:delegate];

}

- (NSString*)lookupKeyForAsset:(NSString*)asset {

return [FlutterDartProject lookupKeyForAsset:asset];

}

- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {

return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package];

}

@end

// AppDelegate實現

@interface DemoFlutterBaseAppDelegate ()

@property(nonatomic, strong) DemoFlutterBaseViewController *rootController;

@property(readonly, nonatomic) NSMutableArray* pluginDelegates;

@property(readonly, nonatomic) NSMutableDictionary* pluginPublications;

@end

@implementation DemoFlutterBaseAppDelegate

/*

* ... 這裡寫轉發各種聲明周期事件給 plugin

*/

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

for (id<FlutterPlugin> plugin in _pluginDelegates) {

if ([plugin respondsToSelector:_cmd]) {

[plugin application:application didFinishLaunchingWithOptions:launchOptions];

}

}

return YES;

}

#pragma mark - getters for flutter

// 返回 FlutterViewController實例

- (FlutterViewController *)rootController {

// ...

}

- (NSObject<FlutterBinaryMessenger> *)binaryMessenger

{

if ([self.rootController conformsToProtocol:@protocol(FlutterBinaryMessenger)]) {

return (NSObject<FlutterBinaryMessenger> *)self.rootController;

}

return nil;

}

- (NSObject<FlutterTextureRegistry> *)textures

{

if ([self.rootController conformsToProtocol:@protocol(FlutterTextureRegistry)]) {

return (NSObject<FlutterTextureRegistry> *)self.rootController;

}

return nil;

}

- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {

NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@", pluginKey);

self.pluginPublications[pluginKey] = [NSNull null];

return [[DemoFlutterAppDelegateRegistrar alloc] initWithPlugin:pluginKey appDelegate:self];

}

- (BOOL)hasPlugin:(NSString*)pluginKey {

return _pluginPublications[pluginKey] != nil;

}

- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {

return _pluginPublications[pluginKey];

}

@end

這樣 Flutter的運行環境其實就準備好了,無論是 Hot Restart還是 AOT都可以支持。接下來我們實現 Debug Hot Restart

首先在你的 Flutter代碼目錄下執行一遍 Flutter build bundle,這可以幫助我們打包出一個 Flutter Asset,然後把這個 flutter_assets 目錄拖入項目。

對你的項目進行一次 build,確保能夠得到一個 .app 文件。然後新建一個文件夾叫做 Payload,把 .app文件放入 Payload文件夾,然後壓縮成 zip文件。這個文件便可以被 Flutter命令行工具使用了。

flutter run --use-application-binary /path/to/Payload.zip

然後效果如圖:

其實這裡並沒有實現 Hot Reload,主要是目前 flutter工具鏈支持度還不好,不過 Hot Restart也夠用了。

Release: Flutter AOT

Release模式和 Debug下不一樣,我們需要做幾件事情:

  1. Flutter.framework替換成 flutter/bin/cache/artifacts/engine/ios-release/Flutter.framework,因為上一步我們用的庫其實是 JIT Runtime
  2. 在 Flutter代碼項目下面執行 flutter build aot --release --target-platform ios --ios-arch armv7,arm64 然後我們可以在 build目錄下拿到一個打包好的 App.framework,不過別忘記在裡面放一個 Info.plist。並且把這個庫拖到工程裡面
  3. 刪除工程裡面的 flutter_assets文件夾下的 isolate_snapshot_datakernel_blob.binplatform.dillvm_snapshot_data這幾個文件
  4. 編譯打包給真機運行,效果如下:

The End

其實 Flutter官方有支持現有 App 集成的計劃,並且現在文檔也有一部分介紹,但其實整體工具鏈還沒支持上來,目前所支持的程度和上文的方法也大同小異。

如果有需要的話,現有項目完全可以採用上面的方法集成,為了減少工作流程,還需要做一些工作,比如:

  1. 項目中提供 App.framework 、 Flutter.framework的空殼,方便在 debug 和 release下隨時用腳本替換
  2. Debug時可以先打出包給 Flutter開發者用,也可以直接添加一個 build post action,直接調用 flutter 命令行,把 Xcode和 flutter整合起來,不要像上文一樣全都手動,容易漏掉必要流程
  3. Release AOT的自動化肯定要做,並且要和現有 CI整合

>>>>閱讀全文


推薦閱讀:

Android O最給力改進:App啟動速度快兩倍!
凱立德Android版報詳細路名設置方法
內核空間鏡像攻擊|利用ARM MMU硬體特性開啟安卓8終端的上帝模式
小米8跟三星s9+的差距大嗎?

TAG:Android | 科技 |