在跨平臺開發中
Flutter
優點很明瞭,簡單總結 :
ios
- 接近原生的性能
- 熱重載
- 豐富的組件
考慮項目有大量原生業務,咱們也不可能基於 Flutter 重構全部的業務。
所以只能在原有的基礎上混合使用 Flutter 來開發新業務或重構舊業務。
參考閒魚,哈羅等,他們也提供相應的解決混合開發方案 flutter_boost 和 flutter_thrio。
目前咱們採用 flutter_boost 做爲咱們 tw591 項目的解決方案。git
正式接入:github
建立 Flutter 項目flutter create -t module flutter_module
, 建議建立完能夠執行用 AndoirdStudio 編譯執行,看是否正常,不過記得刪除生成的ios 文件,不然會遇第4條說的flutter_export_environment 問題數據庫
將建立的 flutter_module 項目到本身目標倉庫中swift
在 Podfile 文件添加以下腳本api
// 注意:這個指定路徑便是你的 flutter_module 項目的目標路徑
flutter_application_path = '../../TWFlutter591/flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'twhouse' do
install_all_flutter_pods(flutter_application_path)
end
複製代碼
處理方式是刪除flutter_module/ios 文件緩存
用AndoirdStudio 或者 VSCode 運行一遍,產生新的 flutter_export_environment.sh 文件markdown
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: 'v1.17.1-hotfixes'
複製代碼
flutter packages get
pod install --repo-update
- TWFlutterUtil 單例類用來註冊 flutter 引擎,同時封裝一層調用,避免 FlutterBoostPlugin 直接調用
- TWFlutterPlatformRouter 基於 Flutter_boost DEMO 提供的基礎上,根據咱們項目業務作了調整,主要是 flutter 與 Native 平臺交互的 Router
- TWFlutterJumpUtil 主要是處理 Flutter 與 Native 相互路由跳轉間的業務邏輯
- TWFlutterNativePageName 定義頁面路由名稱
- TWFlutterNativeEventUtil 主要是處理 Flutter 與 Native 事件的業務邏輯
TWFlutterUtil.swift
網絡
import UIKit
import flutter_boost
@objcMembers class TWFlutterUtil: NSObject {
static let shareInstance = TWFlutterUtil()
var router: TWFlutterPlatformRouter?
var engine: FlutterEngine?
/// 註冊 flutter 引擎
func registerFlutter() {
router = TWFlutterPlatformRouter()
guard let router = router else {return}
FlutterBoostPlugin.sharedInstance().startFlutter(with: router) {[weak self](engine) in
self?.engine = engine
HouseTool.dispatch(afterTime: 2.0) {
TWFlutterNativeEventUtil.sendConfigureInfo()
}
}
}
// MARK: - sendEvent
/** * Native層往Dart層發送事件,事件名稱經過eventName指定 * * @param eventName 事件名稱 * @param arguments 參數 */
open class func sendEvent(_ eventName: String, arguments: [AnyHashable: Any]) {
FlutterBoostPlugin.sharedInstance().sendEvent(eventName, arguments: arguments)
}
/** * 添加監聽Dart層調用Native層的事件 * * @param name 事件名稱 * @param listner 事件監聽器 */
open class func addEventListener(_ listener: @escaping FLBEventListener, name: String) -> FLBVoidCallback {
return FlutterBoostPlugin.sharedInstance().addEventListener(listener, forName: name)
}
// MARK: - open/close Page
/** * 關閉頁面,混合棧推薦使用的用於操做頁面的接口 * * @param uniqueId 關閉的頁面惟一ID符 * @param resultData 頁面要返回的結果(給上一個頁面),會做爲頁面返回函數的回調參數 * @param exts 額外參數 * @param completion 注意必傳 關閉頁面的即時回調,頁面一旦關閉即回調 */
open class func close(_ uniqueId: String, result resultData: [AnyHashable: Any], exts: [AnyHashable: Any], completion: @escaping (Bool) -> Void) {
FlutterBoostPlugin.close(uniqueId, result: resultData, exts: exts, completion: completion)
}
/** * 打開新頁面(默認以push方式),混合棧推薦使用的用於操做頁面的接口;經過urlParams能夠設置爲以present方式打開頁面:urlParams:@{@"present":@(YES)} * * @param url 打開的頁面資源定位符 * @param urlParams 傳入頁面的參數; 如有特殊邏輯,能夠經過這個參數設置回調的id * @param exts 額外參數 * @param resultCallback 當頁面結束返回時執行的回調,經過這個回調能夠取得頁面的返回數據,如close函數傳入的resultData * @param completion 注意必傳 打開頁面的即時回調,頁面一旦打開即回調 */
open class func open(_ url: String, urlParams: [AnyHashable: Any], exts: [AnyHashable: Any], onPageFinished resultCallback: @escaping ([AnyHashable: Any]) -> Void, completion: @escaping (Bool) -> Void) {
FlutterBoostPlugin.open(url, urlParams: urlParams, exts: exts, onPageFinished: resultCallback, completion: completion)
}
/** * Present方式打開新頁面,混合棧推薦使用的用於操做頁面的接口 * * @param url 打開的頁面資源定位符 * @param urlParams 傳入頁面的參數; 如有特殊邏輯,能夠經過這個參數設置回調的id * @param exts 額外參數 * @param resultCallback 當頁面結束返回時執行的回調,經過這個回調能夠取得頁面的返回數據,如close函數傳入的resultData * @param completion 注意必傳 打開頁面的即時回調,頁面一旦打開即回調 */
open class func present(_ url: String, urlParams: [AnyHashable: Any], exts: [AnyHashable: Any], onPageFinished resultCallback: @escaping ([AnyHashable: Any]) -> Void, completion: @escaping (Bool) -> Void) {
FlutterBoostPlugin.present(url, urlParams: urlParams, exts: exts, onPageFinished: resultCallback, completion: completion)
}
}
複製代碼
頁面採用文件
page
頁面類名
,做爲註冊 idapp
//在 iOS 中 TWFlutterUtil 打開更多頁面
TWFlutterUtil.open("TWMorePage",
urlParams: ["isDebug":API_DEBUG == 1 ? "1" : "0"],
exts: ["isHideNavigationBar":"1"],
onPageFinished: { (result) in
}) { (finish) in
}
複製代碼
- TWFlutterBoostPage 主要是 flutter 項目的入口頁面
- TWRouterBoost 用於註冊原生調用 flutter 的路由
- TWFlutterNativeEvent 用於 flutter 與 native 的事件處理
- TWRouterFlutterNative 路由跳轉業務邏輯處理
TWFlutterBoostPage
import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'package:flutter_module/common/tw_app_color.dart';
import 'package:flutter_module/router/tw_router_boost.dart';
class TWFlutterBoostPage extends StatefulWidget {
@override
_TWFlutterBoostAppState createState() => _TWFlutterBoostAppState();
}
class _TWFlutterBoostAppState extends State<TWFlutterBoostApp> {
@override
void initState() {
super.initState();
//初始化註冊...
TWRouterBoost routerBoost = TWRouterBoost();
routerBoost.registerRouter();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Boost example',
//(2)初始化路由
debugShowCheckedModeBanner: true,
theme: ThemeData(
primaryColor: TWAppColor.tw_ff7f00,
dividerColor: TWAppColor.tw_eeeeee,
),
builder: FlutterBoost.init(postPush: _onRoutePushed),
home: Container(
color:Colors.white
));
}
void _onRoutePushed(
String pageName,
String uniqueId,
Map<String, dynamic> params,
Route<dynamic> route,
Future<dynamic> _,
) {
}
}
複製代碼
TWRouterFlutterNative
import 'package:flutter_boost/flutter_boost.dart';
import 'package:flutter_module/features/mine/more/tw_more_page.dart';
class TWRouterFlutterNative {
///************** Flutter to Naitive *******************/
static const String tw_flutterOpenNative = 'TWFlutterOpenNative';
///************** Naitive to Flutter *******************/
static const String tw_flutterMorePage = "TWMorePage";
/// 路由跳轉邏輯處理 map
static Map<String, PageBuilder> routerPageBuilder = <String, PageBuilder>{
tw_flutterMorePage: (String pageName, Map<String, dynamic> params, String _) => TWMorePage(params: params,),
};
}
複製代碼
TWRouterBoost
import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'package:flutter_module/router/tw_router_flutter_native.dart';
import 'package:flutter_module/util/tw_log.dart';
class TWRouterBoost extends NavigatorObserver{
// 初始化註冊路由...
void registerRouter() {
FlutterBoost.singleton.registerPageBuilders(TWRouterFlutterNative.routerPageBuilder);
FlutterBoost.singleton.addBoostNavigatorObserver(this);
}
///************** NavigatorObserver Method *******************/
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
TWLog("flutterboost#didPush");
}
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
TWLog("flutterboost#didPop");
}
void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
TWLog("flutterboost#didRemove");
}
void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {
TWLog("flutterboost#didReplace");
}
}
複製代碼
TWFlutterNativeEvent
import 'package:flutter_boost/flutter_boost.dart';
import 'package:flutter_module/config/tw_configure.dart';
import 'package:flutter_module/util/tw_log.dart';
class TWFlutterNativeEvent {
///************** Native 發送給 Flutter 事件名稱 *******************/
static final String tw_flutterNativeEventConfigureInfo = "EventConfigureInfo";
///************** Flutter 發送給 Native 事件名稱 *******************/
///************** Native 發送給 Flutter 事件統一處理 *******************/
/// 監聽啓動配置信息
static void addConfigureInfo() {
TWLog("開始配置啓動信息...");
FlutterBoost.singleton.channel.addEventListener(TWFlutterNativeEvent.tw_flutterNativeEventConfigureInfo,
(name, arguments) {
TWConfigure.singleton.configure(arguments);
return;
});
}
}
複製代碼
採用項目路由方式打開 注意:
TWFlutterOpenNative
標識 ID ,便是標記從 Flutter 頁面打開 Native 頁面 ,打開 Native 頁面路由 exts 字段 路由部分,建議是原來項目有的,統一Android 和 iOS 的路由規則標準,咱們 iOS 採用 MGJRouter , Android 採用 ARouter。定義一套項目的路由 URL 標準
///************** Private Method *******************/
void clickAction(int index) {
TWLog("點擊 index = $index");
/// 打開評價,
String open_page_url = "app:///xxxx/more_page?entrance=more_evalue&pushAnimation=1";
/// 閒魚庫打開 原生api
FlutterBoost.singleton.open(TWRouterFlutterNative.tw_flutterOpenNative, exts: {
"app_open_url": app_open_url
}).then((value) => print('call me when page is finished. did recieve native route result $value'));
}
複製代碼
用 flutter 重構更多頁面, 體驗感受和原生差很少
爲了減小與原生橋接,儘可能讓 Android 和 iOS 能公用一套,那麼接下來要作的是一些基礎設施建設了。包括 網絡庫,狀態管理,圖片緩存,數據緩存等...
分類 | 地址 | star |
---|---|---|
網絡庫 | Dio | 8.2k |
狀態管理 | bloc | 5.3k |
狀態管理 | provider | 2.8k |
數據庫 | sqflite | 1.8k |
圖片緩存庫 | flutter_cached_network_image | 1.4k |
刷新控件 | flutter_easyrefresh | 2.2k |
刷新控件 | flutter_pulltorefresh | 1.6k |
輪播 | flutter_swiper | 2.6k |
本地通知 | flutter_local_notifications | 1.1k |
Widgets | flukit | 2.6k |
Toast | flutter_oktoast | 288 |
Toast | FlutterToast | 818 |
菜單 | flutter_slidable | 1.4k |
地圖 | flutter_amap | 137 |
地圖 | flutter_amap_location | 249 |
調試工具 | flutter_slidable | 308 |
flutter_module
├──images #圖片資源
├──asset #本地資源
├──lib #項目代碼
├── features # 業務模塊
| ├── home # 首頁
| ├── search # 搜尋
| ├── news # 新聞
| └── mine # 個人
| └──more # 更多 (例:更多頁面)
| ├── model # 模型
| ├── view # 視圖
| ├── tool # 工具
| └── page # 頁面
|
├── page # 入口主頁面
├── common # 通用組件,頭文件,定義常量 (例:顏色常量)
├── util # 工具類,http工具,公共方法
├── widgets # 各類封裝的可複用組件
├── store # 數據相關 緩存庫等
├── blocs # 狀態管理相關
├── config # 配置中心 (例:主題顏色,debug 線上切換方法等)
└── router # 路由
└── 頁面映射配置、observe 方法導出
複製代碼