在移動端中啟動 Flutter 頁面會有短暫空白,雖然官方提供了引擎預熱機制,但是需要提前將所有頁面都進行預熱,這樣開發成本較高,在研究了閑魚的 FlutterBoost 插件后,看看能不能自己實現一個簡單的快速啟動框架。
開發啟動框架 plugin
創建一個 Flutter Plugin 項目,并添加 git,然后編寫三端代碼:
Flutter 代碼首先是 Flutter 端的代碼
1. RouteManager
import'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:flutter_boot/BasePage.dart';class RouteManager{factory RouteManager() => _getInstance();static RouteManager get instance => _getInstance();static RouteManager _instance;RouteManager._internal(){}static RouteManager _getInstance(){if(_instance == null){_instance = new RouteManager._internal();}return _instance;}Maproutes = Map(); void registerRoute(String route, BasePage page){routes[route] = page;}RouteFactory getRouteFactory(){return getRoute;}MaterialPageRoute getRoute(RouteSettings settings){if(routes.containsKey(settings.name)){return MaterialPageRoute(builder: (BuildContext context) {return routes[settings.name];}, settings: settings);}else{return MaterialPageRoute(builder: (BuildContext context) {return PageNotFount();});}}BasePage getPage(String name){if(routes.containsKey(name)) {return routes[name];}else{return PageNotFount();}}}class PageNotFount extends BasePage{StatecreateState() {return _PageNotFount();}}class _PageNotFount extends BaseState<PageNotFount>{Widget buildImpl(BuildContext context) {return Scaffold(body: Center(child: Text("page not found"),),);}}
它的作用就是管理路由,是一個單例,用一個 map 來維護路由映射。其中三個函數比較重要:
- registerRoute: 注冊路由,一般在啟動時調用;
- getRouteFactory: 返回 RouteFactory,將它賦值給 MaterialApp 的 onGenerateRoute 字段;
- getPage: 通過 route 名稱返回頁面 widget。
2.BaseApp
import'dart:convert';import 'package:flutter/cupertino.dart';import 'package:flutter/services.dart';import 'package:flutter_boot/RouteManager.dart';abstract class BaseApp extends StatefulWidget{StatecreateState() {registerRoutes();return _BaseApp(build);}Widget build(BuildContext context, Widget page);void registerRoutes();}class _BaseApp extends State<BaseApp>{Function buildImpl;static const bootChannel = const BasicMessageChannel("startPage", StringCodec()); Widget curPage = RouteManager.instance.getPage("");_BaseApp(this.buildImpl){bootChannel.setMessageHandler((message) async {setState(() {var json = jsonDecode(message);var route = json["route"];var page = RouteManager.instance.getPage(route);page.args = json["params"];curPage = page;});return "";});}Widget build(BuildContext context) {return buildImpl.call(context, curPage);}}
是一個抽象類,真正的 Flutter app 需要繼承它。主要是封裝了一個 BasicMessageChannel 用來與 Android/iOS 交互,并根據收到的消息處理頁面內的切換,實現快速啟動。
繼承它的子類需要實現 registerRoutes 函數,在這里使用 RouteManager 的 registerRoute 將每個頁面注冊一下即可。
3.BasePage
import'package:flutter/material.dart';abstract class BasePage extends StatefulWidget{dynamic args;}abstract class BaseState<T extends BasePage> extends State<T>{dynamic args;Widget build(BuildContext context) {if(ModalRoute.of(context).settings.arguments == null){args = widget.args;}else{args = ModalRoute.of(context).settings.arguments;}return buildImpl(context);}Widget buildImpl(BuildContext context);}
同樣是抽象類,每個 Flutter 頁面都需要繼承它,它主要是處理兩種啟動方式傳過來的參數,統一到 args 中,這樣子類就可以直接使用而不需要考慮是如何啟動的。
Android 代碼接下來是 plugin 中的 Android 的代碼
1.BootEngine
這個是單例,初始化并預熱 FlutterEngine,同時創建 BasicMessageChannel 用于后續交互。需要在 Application 的 onCreate 中調用它的 init 函數來初始化。 2. FlutterBootActivitypackage com.bennu.flutter_bootimport android.app.Applicationimport io.flutter.embedding.engine.FlutterEngineimport io.flutter.embedding.engine.FlutterEngineCacheimport io.flutter.embedding.engine.dart.DartExecutorimport io.flutter.plugin.common.BasicMessageChannelimport io.flutter.plugin.common.StringCodecobject BootEngine {public var flutterBoot : BasicMessageChannel? = null fun init(context: Application){var flutterEngine = FlutterEngine(context)flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())FlutterEngineCache.getInstance().put("main", flutterEngine)flutterBoot = BasicMessageChannel(flutterEngine.dartExecutor.binaryMessenger, "startPage", StringCodec.INSTANCE) }}
package com.bennu.flutter_bootimport android.content.ComponentNameimport android.content.Contextimport android.content.Intentimport android.os.Bundleimport android.os.PersistableBundleimport io.flutter.embedding.android.FlutterActivityimport org.json.JSONObjectclass FlutterBootActivity : FlutterActivity() {companion object{const val ROUTE_KEY = "flutter.route.key"fun build(context: Context, routeName : String, params : Map<String, String>?) : Intent{var intent = withCachedEngine("main").build(context)intent.component = ComponentName(context, FlutterBootActivity::class.java)var json = JSONObject()json.put("route", routeName)var paramsObj = JSONObject()params?.let {for(entry in it){paramsObj.put(entry.key, entry.value)}}json.put("params", paramsObj)intent.putExtra(ROUTE_KEY, json.toString())return intent}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)}override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {super.onCreate(savedInstanceState, persistentState)}override fun onResume() {super.onResume()var route = intent.getStringExtra(ROUTE_KEY)BootEngine.flutterBoot?.send(route)}override fun onDestroy() {super.onDestroy()}}
繼承 FlutterActivity,提供一個 build (context: Context, routeName: String, params: Map
iOS
iOS 與 Android 類似
1.FlutterBootEngine FlutterBootEngine.h
@interface FlutterBootEngine : NSObject+ (nonnull instancetype)sharedInstance;- (FlutterBasicMessageChannel *)channel;- (FlutterEngine *)engine;- (void)initEngine;@endFlutterBootEngine.m@implementation FlutterBootEnginestatic FlutterBootEngine * instance = nil;FlutterEngine * engine = nil;FlutterBasicMessageChannel * channel = nil;+(nonnull FlutterBootEngine *)sharedInstance{if(instance == nil){instance = [self.class new];}return instance;}+(id)allocWithZone:(struct _NSZone *)zone{if(instance == nil){instance = [[super allocWithZone:zone]init];}return instance;}- (id)copyWithZone:(NSZone *)zone{return instance;}- (FlutterEngine *)engine{return engine;}- (FlutterBasicMessageChannel *)channel{return channel;}- (void)initEngine{engine = [[FlutterEngine alloc]initWithName:@"flutter engine"];channel = [FlutterBasicMessageChannel messageChannelWithName:@"startPage" binaryMessenger:engine.binaryMessenger codec:[FlutterStringCodec sharedInstance]];[engine run];}@end
這也是一個單例,初始化并啟動 FlutterEngine,并創建一個 FlutterBasicMessageChannel 與 Flutter 交互。
需要在 iOS 項目的 AppDelegate 初始化時調用它的 initEngine 函數。
2. FlutterBootViewController
FlutterBootViewController.h@interface FlutterBootViewController : FlutterViewController- (nonnull instancetype)initWithRoute:(nonnull NSString*)routeparams:(nullable NSDictionary*)params;@endFlutterBootViewController.m@implementation FlutterBootViewControllerNSString * mRoute = nil;NSDictionary * mParams = nil;- (nonnull instancetype)initWithRoute:(nonnull NSString *)route params:(nullable NSDictionary *)params{self = [super initWithEngine:FlutterBootEngine.sharedInstance.engine nibName:nil bundle:nil];mRoute = route;mParams = params;return self;}//viewDidAppear時機有點晚,會先顯示一下上一個頁面才更新到新頁面,所以換成viewWillAppear- (void)viewWillAppear:(BOOL)animated{[super viewWillAppear:animated];if(mParams == nil){mParams = [[NSDictionary alloc]init];}NSDictionary * dict = @{@"route" : mRoute, @"params" : mParams};NSData * jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:NULL];NSString * str = [[NSString alloc]initWithData:jsonData encoding:NSUTF8StringEncoding];NSLog(@"%@", str);[FlutterBootEngine.sharedInstance.channel sendMessage:str];}@end
同樣新增一個使用路由名和參數的構造函數,然后在 viewWillAppear 時通知 Flutter。
注意這里如果改成 viewDidAppear 時機有點晚,會先顯示一下上一個頁面才更新到新頁面,所以換成 viewWillAppear。
3. FlutterBoot.h
這個是 swift 的橋接文件,通過它 swift 就可以使用我們上面定義的類。 這樣我們的 plugin 就開發完成了,可以發布到 pub 上。我這里是 push 到 git 倉庫中,通過 git 的方式依賴使用。
開發 Flutter module
創建一個 Flutter module,然后引入我們的 plugin,在 pubspec.yaml 中:dependencies:flutter:sdk: flutter...flutter_boot:git: https://gitee.com/chzphoenix/flutter-boot.git
然后我們開發兩個頁面用于測試。
1. FirstPage.dart
import 'package:flutter/material.dart';import 'package:flutter_boot/BasePage.dart';class FirstPage extends BasePage{StatecreateState() {return _FirstPage();}}class _FirstPage extends BaseState<FirstPage>{void _goClick() {Navigator.of(context).pushNamed("second", arguments: {"key":"123"});}Widget buildImpl(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("Flutter Demo Home Page"),),body: Center(child: ...,),floatingActionButton: FloatingActionButton(onPressed: _goClick,tooltip: 'Increment',child: Icon(Icons.add),), // This trailing comma makes auto-formatting nicer for build methods.);}}
繼承 BasePage 和 BaseState 即可,點擊按鈕可以跳轉到頁面 2。
2. SecondPage.dart
import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:flutter_boot/BasePage.dart';class SecondPage extends BasePage{StatecreateState() {return _SecondPage();}}class _SecondPage extends BaseState<SecondPage>{Widget buildImpl(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("test"),),body:Text("test:${args["key"]}"));}}
這個頁面獲取傳遞過來的參數 key,并展示。
3. main.dart
import 'package:flutter/material.dart';import 'package:flutter_boot/BaseApp.dart';import 'package:flutter_boot/RouteManager.dart';import 'FirstPage.dart';import 'SecondPage.dart';void main() => runApp(MyApp());class MyApp extends BaseApp {Widget build(BuildContext context, Widget page) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: page,onGenerateRoute: RouteManager.instance.getRouteFactory(),);}void registerRoutes() {RouteManager.instance.registerRoute("main", FirstPage());RouteManager.instance.registerRoute("second", SecondPage());}}
入口繼承 BaseApp,并實現 registerRoutes,注冊這兩個頁面。
注意這里的 onGenerateRoute 使用 RouteManager.instance.getRouteFactory (),這樣一次注冊就可以了,不必自己去實現。
引入移動端
Module 開發完后,就可以在 Android/iOS 上使用了。Android 端
在 Android 上比較簡單,在 Android 項目中引入剛才的 module 即可,然后需要在 Android 的主 module (一般是 app) 的 build.gradle 中引入 module 和 plugin,如下:dependencies{implementation fileTree(dir: "libs", include: ["*.jar"])...implementation project(path: ':flutter') //moduleprovided rootProject.findProject(":flutter_boot") //plugin}
注意 plugin 的名稱是之前在 module 中的 pubspec.yaml 定義的。
然后就可以在 Android 中使用了,首先要初始化,如下:
importandroid.app.Applicationimport com.bennu.flutter_boot.BootEnginepublic class App : Application() {override fun onCreate() {super.onCreate()BootEngine.init(this)...}}
然后合適的時候啟動 Flutter 頁面即可,啟動代碼如下:
button.setOnClickListener {startActivity(FlutterBootActivity.build(this, "main", null))}button2.setOnClickListener {var params = HashMap() params.put("key", "123")startActivity(FlutterBootActivity.build(this, "second", params))}
一個啟動無參的頁面 1,一個啟動有參的頁面 2。 測試可以發現無論打開哪個頁面都非常快,幾乎沒有加載時間。這樣就實現了快速啟動。
iOS 端
iOS 端稍微復雜一些,需要先了解一下 iOS 如何加入 Flutter。
我選用的是 framework 的方式引入,所以在 Flutter module 項目下通過命令編譯打包 framework。
flutterbuildios-framework--xcframework--no-universal--output=./Flutter/然后引入到 iOS 項目中,與上一篇文章不同的是,因為這個 module 中加入了 plugin,所以 framework 產物是四個:
- App.xcframework
- flutter_boot.xcframework (這個就是我們的 plugin 中的 iOS 部分)
- Flutter.xcframework
- FlutterPluginRegistrant.xcframework
這四個都需要引入到 iOS 項目中。
然后 AppDelegate 需要繼承 FlutterAppDelegate (如果無法繼承,則需要處理每個生命周期,您可以查看: https://flutter.cn/docs/development/add-to-app/ios/add-flutter-screen?tab=engine-swift-tab)。
然后在 AppDelegate 中初始化,如下:
然后在合適的地方啟動 Flutter 頁面即可,如下:importUIKitimport Flutterimport flutter_bootclass AppDelegate: FlutterAppDelegate {override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {FlutterBootEngine.sharedInstance().initEngine()return true}override func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)}}
funcshowMain(){let flutterViewController =FlutterBootViewController(route: "main", params: nil)present(flutterViewController, animated: true, completion: nil)}func showSecond() {let params : Dictionary<String, String> = ["key" : "123"]let flutterViewController =FlutterBootViewController(route: "second", params: params)present(flutterViewController, animated: true, completion: nil)}
同樣分別打開兩個頁面,可以看到啟動幾乎沒有加載時間,同時參數也正確傳遞。
原文標題:Flutter 混合開發: 開發一個簡單的快速啟動框架 | 開發者說·DTalk
文章出處:【微信公眾號:谷歌開發者】歡迎添加關注!文章轉載請注明出處。
審核編輯:湯梓紅
-
Android
+關注
關注
12文章
4024瀏覽量
133970 -
框架
+關注
關注
0文章
404瀏覽量
18421 -
代碼
+關注
關注
30文章
4967瀏覽量
73954 -
Plugin
+關注
關注
0文章
9瀏覽量
3171
原文標題:Flutter 混合開發: 開發一個簡單的快速啟動框架 | 開發者說·DTalk
文章出處:【微信號:Google_Developers,微信公眾號:谷歌開發者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
從網絡接口到 DMA,一套面向工程師的 FPGA 網絡開發框架
LuatOS框架的使用(上)
Renesas SMARC EVK 開發板啟動指南
開源鴻蒙技術大會2025丨開源鴻蒙應用開發再提速,跨平臺框架PMC(籌)正式啟動孵化
NVIDIA助力新一代機器人開發開源框架
PYQT 應用程序框架及開發工具
LuatOS腳本開發入門:嵌入式運行框架全解析!
嵌入式開發新選擇:LuatOS腳本框架入門教程
ElfBoard技術貼|【RK3588】ELF 2開發板開機自啟動詳解
華為正式啟動HarmonyOS 6開發者Beta
ArkUI-X框架LogInterface使用指南
ArkUI-X中Plugin生命周期開發指南
網關智商暴增!涂鴉T5AI網關開發框架讓設備秒懂人話,兼容國內外頂尖AI大模型
怎么開發啟動框架plugin
評論