在移動(dòng)端中啟動(dòng) Flutter 頁(yè)面會(huì)有短暫空白,雖然官方提供了引擎預(yù)熱機(jī)制,但是需要提前將所有頁(yè)面都進(jìn)行預(yù)熱,這樣開發(fā)成本較高,在研究了閑魚的 FlutterBoost 插件后,看看能不能自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的快速啟動(dòng)框架。
開發(fā)啟動(dòng)框架 plugin
創(chuàng)建一個(gè) Flutter Plugin 項(xiàng)目,并添加 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;
}
Map
routes = 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{
State
createState() {return _PageNotFount();
}
}
class _PageNotFount extends BaseState<PageNotFount>{
Widget buildImpl(BuildContext context) {
return Scaffold(
body: Center(
child: Text("page not found"),
),
);
}
}
它的作用就是管理路由,是一個(gè)單例,用一個(gè) map 來(lái)維護(hù)路由映射。其中三個(gè)函數(shù)比較重要:
- registerRoute: 注冊(cè)路由,一般在啟動(dòng)時(shí)調(diào)用;
- getRouteFactory: 返回 RouteFactory,將它賦值給 MaterialApp 的 onGenerateRoute 字段;
- getPage: 通過(guò) route 名稱返回頁(yè)面 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{
State
createState() {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);
}
}
是一個(gè)抽象類,真正的 Flutter app 需要繼承它。主要是封裝了一個(gè) BasicMessageChannel 用來(lái)與 Android/iOS 交互,并根據(jù)收到的消息處理頁(yè)面內(nèi)的切換,實(shí)現(xiàn)快速啟動(dòng)。
繼承它的子類需要實(shí)現(xiàn) registerRoutes 函數(shù),在這里使用 RouteManager 的 registerRoute 將每個(gè)頁(yè)面注冊(cè)一下即可。
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);
}
同樣是抽象類,每個(gè) Flutter 頁(yè)面都需要繼承它,它主要是處理兩種啟動(dòng)方式傳過(guò)來(lái)的參數(shù),統(tǒng)一到 args 中,這樣子類就可以直接使用而不需要考慮是如何啟動(dòng)的。
Android 代碼接下來(lái)是 plugin 中的 Android 的代碼
1.BootEngine
這個(gè)是單例,初始化并預(yù)熱 FlutterEngine,同時(shí)創(chuàng)建 BasicMessageChannel 用于后續(xù)交互。需要在 Application 的 onCreate 中調(diào)用它的 init 函數(shù)來(lái)初始化。 2. FlutterBootActivitypackage com.bennu.flutter_boot
import android.app.Application
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.StringCodec
object 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_boot
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.PersistableBundle
import io.flutter.embedding.android.FlutterActivity
import org.json.JSONObject
class 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,提供一個(gè) 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;
@end
FlutterBootEngine.m
@implementation FlutterBootEngine
static 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
這也是一個(gè)單例,初始化并啟動(dòng) FlutterEngine,并創(chuàng)建一個(gè) FlutterBasicMessageChannel 與 Flutter 交互。
需要在 iOS 項(xiàng)目的 AppDelegate 初始化時(shí)調(diào)用它的 initEngine 函數(shù)。
2. FlutterBootViewController
FlutterBootViewController.h
@interface FlutterBootViewController : FlutterViewController
- (nonnull instancetype)initWithRoute:(nonnull NSString*)route
params:(nullable NSDictionary*)params;
@end
FlutterBootViewController.m
@implementation FlutterBootViewController
NSString * 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時(shí)機(jī)有點(diǎn)晚,會(huì)先顯示一下上一個(gè)頁(yè)面才更新到新頁(yè)面,所以換成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
同樣新增一個(gè)使用路由名和參數(shù)的構(gòu)造函數(shù),然后在 viewWillAppear 時(shí)通知 Flutter。
注意這里如果改成 viewDidAppear 時(shí)機(jī)有點(diǎn)晚,會(huì)先顯示一下上一個(gè)頁(yè)面才更新到新頁(yè)面,所以換成 viewWillAppear。
3. FlutterBoot.h
這個(gè)是 swift 的橋接文件,通過(guò)它 swift 就可以使用我們上面定義的類。 這樣我們的 plugin 就開發(fā)完成了,可以發(fā)布到 pub 上。我這里是 push 到 git 倉(cāng)庫(kù)中,通過(guò) git 的方式依賴使用。
開發(fā) Flutter module
創(chuàng)建一個(gè) Flutter module,然后引入我們的 plugin,在 pubspec.yaml 中:dependencies:
flutter:
sdk: flutter
...
flutter_boot:
git: https://gitee.com/chzphoenix/flutter-boot.git
然后我們開發(fā)兩個(gè)頁(yè)面用于測(cè)試。
1. FirstPage.dart
import 'package:flutter/material.dart';
import 'package:flutter_boot/BasePage.dart';
class FirstPage extends BasePage{
State
createState() {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 即可,點(diǎn)擊按鈕可以跳轉(zhuǎn)到頁(yè)面 2。
2. SecondPage.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_boot/BasePage.dart';
class SecondPage extends BasePage{
State
createState() {return _SecondPage();
}
}
class _SecondPage extends BaseState<SecondPage>{
Widget buildImpl(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("test"),
),
body:Text("test:${args["key"]}")
);
}
}
這個(gè)頁(yè)面獲取傳遞過(guò)來(lái)的參數(shù) 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,并實(shí)現(xiàn) registerRoutes,注冊(cè)這兩個(gè)頁(yè)面。
注意這里的 onGenerateRoute 使用 RouteManager.instance.getRouteFactory (),這樣一次注冊(cè)就可以了,不必自己去實(shí)現(xiàn)。
引入移動(dòng)端
Module 開發(fā)完后,就可以在 Android/iOS 上使用了。Android 端
在 Android 上比較簡(jiǎn)單,在 Android 項(xiàng)目中引入剛才的 module 即可,然后需要在 Android 的主 module (一般是 app) 的 build.gradle 中引入 module 和 plugin,如下:dependencies{
implementation fileTree(dir: "libs", include: ["*.jar"])
...
implementation project(path: ':flutter') //module
provided rootProject.findProject(":flutter_boot") //plugin
}
注意 plugin 的名稱是之前在 module 中的 pubspec.yaml 定義的。
然后就可以在 Android 中使用了,首先要初始化,如下:
importandroid.app.Application
import com.bennu.flutter_boot.BootEngine
public class App : Application() {
override fun onCreate() {
super.onCreate()
BootEngine.init(this)
...
}
}
然后合適的時(shí)候啟動(dòng) Flutter 頁(yè)面即可,啟動(dòng)代碼如下:
button.setOnClickListener {
startActivity(FlutterBootActivity.build(this, "main", null))
}
button2.setOnClickListener {
var params = HashMap
() params.put("key", "123")
startActivity(FlutterBootActivity.build(this, "second", params))
}
一個(gè)啟動(dòng)無(wú)參的頁(yè)面 1,一個(gè)啟動(dòng)有參的頁(yè)面 2。 測(cè)試可以發(fā)現(xiàn)無(wú)論打開哪個(gè)頁(yè)面都非常快,幾乎沒(méi)有加載時(shí)間。這樣就實(shí)現(xiàn)了快速啟動(dòng)。
iOS 端
iOS 端稍微復(fù)雜一些,需要先了解一下 iOS 如何加入 Flutter。
我選用的是 framework 的方式引入,所以在 Flutter module 項(xiàng)目下通過(guò)命令編譯打包 framework。
flutterbuildios-framework--xcframework--no-universal--output=./Flutter/然后引入到 iOS 項(xiàng)目中,與上一篇文章不同的是,因?yàn)檫@個(gè) module 中加入了 plugin,所以 framework 產(chǎn)物是四個(gè):
- App.xcframework
- flutter_boot.xcframework (這個(gè)就是我們的 plugin 中的 iOS 部分)
- Flutter.xcframework
- FlutterPluginRegistrant.xcframework
這四個(gè)都需要引入到 iOS 項(xiàng)目中。
然后 AppDelegate 需要繼承 FlutterAppDelegate (如果無(wú)法繼承,則需要處理每個(gè)生命周期,您可以查看: https://flutter.cn/docs/development/add-to-app/ios/add-flutter-screen?tab=engine-swift-tab)。
然后在 AppDelegate 中初始化,如下:
然后在合適的地方啟動(dòng) Flutter 頁(yè)面即可,如下:importUIKit
import Flutter
import flutter_boot
class 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)
}
同樣分別打開兩個(gè)頁(yè)面,可以看到啟動(dòng)幾乎沒(méi)有加載時(shí)間,同時(shí)參數(shù)也正確傳遞。
原文標(biāo)題:Flutter 混合開發(fā): 開發(fā)一個(gè)簡(jiǎn)單的快速啟動(dòng)框架 | 開發(fā)者說(shuō)·DTalk
文章出處:【微信公眾號(hào):谷歌開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
審核編輯:湯梓紅
-
Android
+關(guān)注
關(guān)注
12文章
3974瀏覽量
130485 -
框架
+關(guān)注
關(guān)注
0文章
404瀏覽量
17940 -
代碼
+關(guān)注
關(guān)注
30文章
4905瀏覽量
70946 -
Plugin
+關(guān)注
關(guān)注
0文章
9瀏覽量
3100
原文標(biāo)題:Flutter 混合開發(fā): 開發(fā)一個(gè)簡(jiǎn)單的快速啟動(dòng)框架 | 開發(fā)者說(shuō)·DTalk
文章出處:【微信號(hào):Google_Developers,微信公眾號(hào):谷歌開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
華為正式啟動(dòng)HarmonyOS 6開發(fā)者Beta
ArkUI-X中Plugin生命周期開發(fā)指南
北京迅為RK3568開發(fā)板OpenHarmony系統(tǒng)南向驅(qū)動(dòng)開發(fā)內(nèi)核HDF驅(qū)動(dòng)框架架構(gòu)

涂鴉Wukong AI硬件開發(fā)框架超強(qiáng)兼容DeepSeek等大模型,助你打造爆款A(yù)I硬件

評(píng)論