前言
在跨平台开发中
Flutter
优势很明了,简单总结 :
- 接近原生的性能
- 热重载
- 丰富的组件
考虑项目有大量原生业务,我们也不可能基于 Flutter 重构所有的业务。
因此只能在原有的基础上混合使用 Flutter 来开发新业务或重构旧业务。
参考闲鱼,哈罗等,他们也提供相应的解决混合开发方案 flutter_boost 和 flutter_thrio。
目前我们采用 flutter_boost 作为我们 tw591 项目的解决方案。
Flutter 混编现有项目
正式接入:
创建 Flutter 项目
flutter create -t module flutter_module
, 建议创建完可以执行用 AndoirdStudio 编译执行,看是否正常,不过记得删除生成的ios 文件,否则会遇第4条说的flutter_export_environment 问题将创建的 flutter_module 项目到自己目标仓库中
在 Podfile 文件添加如下脚本
// 注意:这个指定路径即是你的 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_export_environment.sh 文件路径错误
处理方式是删除flutter_module/ios 文件
用AndoirdStudio 或者 VSCode 运行一遍,产生新的 flutter_export_environment.sh 文件
- 集成闲鱼 flutter_boost 混合开发方案
- 注意:flutter_boost 版本需要和我们 flutter 版本对应,例如:flutter_boost:v1.17.1-hotfixes 对应的flutter sdk:1.17.1
- 使用 Flutter –version 命令查看当前版本
- 打开 flutter_module 文件夹 下pubspec.yaml 文件,添加依赖
flutter_boost: git: url: 'https://github.com/alibaba/flutter_boost.git' ref: 'v1.17.1-hotfixes'
- 执行命令
flutter packages get
- 最后在项目中执行
pod install --repo-update
- 执行 install 后原生项目 Flutter 目录,会将我们 Flutter 项目打包成 framework
Native 部分
- TWFlutterUtil 单例类用来注册 flutter 引擎,同时封装一层调用,避免 FlutterBoostPlugin 直接调用
- TWFlutterPlatformRouter 基于 Flutter_boost DEMO 提供的基础上,根据我们项目业务做了调整,主要是 flutter 与 Native 平台交互的 Router
- TWFlutterJumpUtil 主要是处理 Flutter 与 Native 相互路由跳转间的业务逻辑
- TWFlutterNativePageName 定义页面路由名称
- TWFlutterNativeEventUtil 主要是处理 Flutter 与 Native 事件的业务逻辑
示例 TWFlutterUtil 部分
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)
}
}
Native 打开 Flutter 页面
页面采用文件
page
页面类名
,作为注册 id
//在 iOS 中 TWFlutterUtil 打开更多页面
TWFlutterUtil.open("TWMorePage",
urlParams: ["isDebug":API_DEBUG == 1 ? "1" : "0"],
exts: ["isHideNavigationBar":"1"],
onPageFinished: { (result) in
}) { (finish) in
}
Flutter 部分
- TWFlutterBoostPage 主要是 flutter 项目的入口页面
- TWRouterBoost 用于注册原生调用 flutter 的路由
- TWFlutterNativeEvent 用于 flutter 与 native 的事件处理
- TWRouterFlutterNative 路由跳转业务逻辑处理
示例 TWFlutterBoostPage 部分
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 {
_TWFlutterBoostAppState createState() => _TWFlutterBoostAppState();
}
class _TWFlutterBoostAppState extends State<TWFlutterBoostApp> {
void initState() {
super.initState();
//初始化注册...
TWRouterBoost routerBoost = TWRouterBoost();
routerBoost.registerRouter();
}
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;
});
}
}
Flutter 打开 Native 页面
采用项目路由方式打开
注意: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 方法导出
Thanks
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues