學習並使用新的技術與開發方案是每個程式設計師的必修之路。混合開發作為當下比較流行的專案解決方案,已經發展出了很多成熟的技術語言,如Weex、Rect Native、Flutter等,而Flutter因自繪引擎特性得已自成一派,受許多開發者追捧。
接入流程
這部分主要講述如何在原iOS專案中新增整合Flutter模組,以及在引入Flutter資源過程中踩過的一些坑,對於其他開發場景可能會不太適用。我會在文章末尾附上一些開發過程中查閱的部落格和網站,希望能夠幫助大家更好的瞭解混合開發。
整合Flutter資源
安裝Flutter SDK
使用及開發Flutter模組前,需要開發者前往Flutter官網下載對應版本的SDK檔案(如果是協同開發,SDK版本同步很重要),並根據自己的需要安裝到電腦上。推薦安裝到根目錄下新建的development目錄下,方便日後更新。 接下來需要配置一些環境變數,以Mac OS系統為例,首先在根目錄下的 ~/.bash_profile 中新增如下語句:
#FLUTTER_INSTALL_PATH為Flutter SDK安裝目錄名稱,如development
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
export PATH=/Users/[user]/[FLUTTER_INSTALL_PATH]/flutter/bin:$PATH
複製程式碼
配置儲存後,執行Flutter doctor命令,如果輸出結果正常,則配置完成。
有的小夥伴可能會發現配置完後每次重新開啟終端都要重新 source ~/.bash_profile 才可以正常使用 flutter 命令,這是因為 zsh 載入的是 ~/.zshrc 檔案,而 .zshrc 檔案中並沒有定義任務環境變數。 解決辦法是在 .zshrc 裡面加入一行 source ~/.bash_profile,配置完成後就不用重複使用 source ~/.bash_profile 了。
使用Cocoapods引入Flutter專案
如果Flutter專案維護週期不是特別頻繁,開發者可以選擇匯入Flutter Framework的方向引入Flutter專案,過程和使用其他靜態庫的方式一樣,比較容易上手。 在Cocoapods的 Podfile 檔案中加入以下命令:
#將
flutter_application_path = '../xxxx_flutter/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'xxxx' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
install_all_flutter_pods(flutter_application_path)
end
複製程式碼
執行 pod install 後,iOS專案就成功的把Flutter專案資源整合進來了。值得一提的是,Flutter不支援 Bitcode ,需要開發者在 Build Settings 禁用 Bitcode。如果 Archive 過程中出現在錯誤,還需要在 Podfile 中新增:
#放在檔案的最後面就可以
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end
複製程式碼
以上是將Flutter引入到現有iOS專案中的解決方案,每次Flutter資源有較大更新時,都需要在Flutter檔案目錄下執行 flutter pub get,然後執行 pod install,使用Framework方式整合則不需要這個步驟。
在Swift中使用Flutter
礙於Flutter對iOS Native支援的侷限性,在iOS中使用Flutter原生API,很容易產生記憶體洩漏以及一些未知的錯誤。在本篇文章中,我推薦使用鹹魚團隊提供的 FlutterBoost 框架,在其內部整合了對Flutter的模組的封裝介面,可以更輕鬆便捷地使Native和Flutter進行互動。 FlutterBoost OC程式碼編寫,在Swift中需要先使用橋接檔案宣告,接下來按照文件要求實現對應的 Route 管理類:
class FlutterRouteManager: NSObject, FLBPlatform {
// MARK: - open
static func openPage(_ url: String, _ present: Bool = false, completion: (([AnyHashable : Any]) -> ())? = nil) {
FlutterBoostPlugin.open(route.name(), urlParams: present ? ["present": present] : [:], exts: ["animated": true], onPageFinished: { (result) in
completion?(result)
}) { (state) in
//
}
}
// MARK: - delegate
func open(_ url: String, urlParams: [AnyHashable : Any], exts: [AnyHashable : Any], completion: @escaping (Bool) -> Void) {
var animated = true
if let extsAnimated = exts["animated"] as? Bool {
animated = extsAnimated
}
let viewController = ZYFlutterViewController()
viewController.setName(url, params: urlParams)
navigationController()?.pushViewController(viewController, animated: animated)
completion(true)
}
func present(_ url: String, urlParams: [AnyHashable : Any], exts: [AnyHashable : Any], completion: @escaping (Bool) -> Void) {
var animated = true
if let extsAnimated = exts["animated"] as? Bool {
animated = extsAnimated
}
let viewController = ZYFlutterViewController()
viewController.setName(url, params: urlParams)
navigationController()?.present(viewController, animated: animated, completion: {
completion(true)
})
}
func close(_ uid: String, result: [AnyHashable : Any], exts: [AnyHashable : Any], completion: @escaping (Bool) -> Void) {
var animated = true
if let extsAnimated = exts["animated"] as? Bool {
animated = extsAnimated
}
let presentedVC = navigationController()?.presentingViewController
let viewController = presentedVC as? FLBFlutterViewContainer
if viewController?.uniqueIDString() == uid {
viewController?.dismiss(animated: animated, completion: {
completion(true)
})
} else {
navigationController()?.popViewController(animated: animated)
}
}
}
複製程式碼
當我們需要開啟某個Flutter頁面時,只需要呼叫對應的類方法就可以實現。
FlutterRouteManager.openPage(<#T##url: String##String#>, <#T##present: Bool##Bool#>, completion: <#T##(([AnyHashable : Any]) -> ())?##(([AnyHashable : Any]) -> ())?##([AnyHashable : Any]) -> ()#>)
複製程式碼
通過嘗試後,我們已經可以正常開啟某個Flutter頁面,但是在頁面跳轉的過程中,我們可以看到一個很明顯的 Launch 頁面,這顯然不符合我們的程式要求。接下來我們需要在iOS程式啟動時,提前註冊Flutter資源。通過 FlutterBoost,我們可以使用以下方式來完成。
#在AppDelegate檔案或恰當的時機使用如下方式預置Flutter資源
FlutterBoostPlugin.sharedInstance().startFlutter(with: FlutterRouteManager()) { (flutterEngine) in
DispatchQueue.main.async {
//bind channel
//可以在這裡繫結和Flutter的互動控制程式碼
}
}
複製程式碼
Native與Flutter互動
在Flutter中提供與JS類似的與Native進行互動的介面,FlutterMethodChannel 類能夠很好的幫助開發者完成類似的需求,在Swift中,我們可以使用以下程式碼來實現向Flutter端傳遞訊息。
let assetPluginChannel = FlutterMethodChannel(name: "AssetPlugin", binaryMessenger: messenger)
assetPluginChannel?.invokeMethod("Givemetext", arguments: nil, result: { (result) in
//互動結果
})
複製程式碼
在開發中也可以使用訊息回傳的方式來達到互動的目的,像下面這段程式碼一樣。
#使用flutterResult可以在需要時傳遞引數到flutter
assetPluginChannel?.setMethodCallHandler { (methodCall, flutterResult) in
DispatchQueue.main.async {
//根據method或者arguments完成需要的動作
switch methodCall.method {
case "someText":
print("Show me some text!")
default:
break
}
}
}
複製程式碼
存在的問題
- 在iOS頁面跳轉過程中可以發現還是會有一些記憶體洩漏的現象出現,如果要跳轉的Flutter頁面資源較多,還可能會出現卡頓的現象,流暢性不如原生開發的頁面。
- 如果某個Flutter頁面中跳轉了下一級Flutter頁面,使用iOS自帶的側滑功能,會同時將兩個頁面都 pop 掉,需要開發者在這裡處理好相容的問題。
- 使用Cocoapods方式整合Flutter專案,不支援在 Debug 模式下進行 Archive,如果這種打包方式是必要的,建議使用Framework方式進行整合。