【Flutter混合开发】开发一个简单的快速启动框架

news/2024/10/27 0:23:35/

在这里插入图片描述

目录

  • 前言
  • 启动插件
    • Flutter代码
    • Android代码
    • IOS代码
  • 启动模块
  • 使用
    • android端
    • ios端

前言

因为在移动端中启动Flutter页面会有短暂空白,虽然官方提供了引擎预热机制,但是需要提前将所有页面都进行预热,这样开发成本较高,在研究了闲鱼的FlutterBoost插件后,我看看能不能自己实现一个简单的快速启动框架。

这篇文章用到的知识点都在《flutter混合开发:native与flutter交互》中详细讲解了,大家可以先读一下这篇文章再来看本文。本文不再赘述这些内容,直接上干货。

启动插件

创建一个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;}Map<String, BasePage> 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<StatefulWidget> createState() {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。

这里getRouteFactory和getPage共用一个路由map,所以不论是页面内切换还是页面切换都保持统一。

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<StatefulWidget> 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<String>("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

package com.bennu.flutter_bootimport 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.StringCodecobject BootEngine {public var flutterBoot : BasicMessageChannel<String>? = nullfun init(context: Application){var flutterEngine = FlutterEngine(context)flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())FlutterEngineCache.getInstance().put("main", flutterEngine)flutterBoot = BasicMessageChannel<String>(flutterEngine.dartExecutor.binaryMessenger, "startPage", StringCodec.INSTANCE)}
}

这个是单例,初始化并预热FlutterEngine,同时创建BasicMessageChannel用于后续交互。需要在Application的onCreate中调用它的init函数来初始化。

2)FlutterBootActivity

package com.bennu.flutter_bootimport 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.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<String, String>?)函数来启动,传递路由名称和参数。在onResume的时候通过BasicMessageChannel将这两个数据send给flutter处理。

IOS代码

ios与android类似

1)FlutterBootEngine

FlutterBootEngine.h

#ifndef FlutterBootEngine_h
#define FlutterBootEngine_h#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>@interface FlutterBootEngine : NSObject+ (nonnull instancetype)sharedInstance;- (FlutterBasicMessageChannel *)channel;
- (FlutterEngine *)engine;
- (void)initEngine;
@end#endif /* FlutterBootEngine_h */
FlutterBootEngine.m
#import "FlutterBootEngine.h"
#import <Flutter/Flutter.h>@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

#ifndef FlutterBootViewController_h
#define FlutterBootViewController_h#import <Flutter/FlutterViewController.h>@interface FlutterBootViewController : FlutterViewController- (nonnull instancetype)initWithRoute:(nonnull NSString*)routeparams:(nullable NSDictionary*)params;@end#endif /* FlutterBootViewController_h */
FlutterBootViewController.m
#import "FlutterBootViewController.h"
#import "FlutterBootEngine.h"@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

#ifndef FlutterBoot_h
#define FlutterBoot_h#import "FlutterBootEngine.h"
#import "FlutterBootViewController.h"#endif /* FlutterBoot_h */

这个是swift的桥接文件,通过它swift就可以使用我们上面定义的类。

这样我们的plugin就开发完成了,可以发布到pub上。我这里是push到git仓库中,通过git的方式依赖使用。

启动模块

创建一个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{State<StatefulWidget> 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即可,点击按钮可以跳转到页面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<StatefulWidget> createState() {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开发完后,就可以在andorid/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中使用了,首先要初始化,如下:

import android.app.Application
import 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<String, String>()params.put("key", "123")startActivity(FlutterBootActivity.build(this, "second", params))
}

一个启动无参的页面1,一个启动有参的页面2。

测试可以发现无论打开哪个页面都非常快,几乎没有加载时间。这样就实现了快速启动。

ios端

ios端稍微复杂一些,需要先了解一下ios如何加入flutter,见《flutter混合开发:在已有ios项目中引入flutter》

我选用的是framework的方式引入,所以在flutter module项目下通过命令编译打包framework

flutter build ios-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中初始化,如下:

import UIKit
import Flutter
import flutter_boot@UIApplicationMain
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)}
}

然后在合适的地方启动flutter页面即可,如下:

@objc func showMain() {let flutterViewController =FlutterBootViewController(route: "main", params: nil)present(flutterViewController, animated: true, completion: nil)}@objc func showSecond() {let params : Dictionary<String, String> = ["key" : "123"]let flutterViewController =FlutterBootViewController(route: "second", params: params)present(flutterViewController, animated: true, completion: nil)}

同样分别打开两个页面,可以看到启动几乎没有加载时间,同时参数也正确传递。


http://www.ppmy.cn/news/240142.html

相关文章

vue3-实战-05-管理后台顶部tabbar开发-全局守卫

目录 1-顶部tabbar组件静态搭建与拆分 2-菜单折叠效果 3-顶部面包屑动态展示 4-刷新和全屏 4.1-点击刷新操作 4.2-全屏 4.3-退出登录 5-路由鉴权 1-顶部tabbar组件静态搭建与拆分 分析一下&#xff0c;顶部分为左右两侧&#xff0c;左侧是面包屑&#xff0c;右边是 刷新…

揭秘!为摩根大通服务的量子计算机“庐山真面目”

丹佛北部的一栋单调且没有窗户的办公楼就是计算机行业的奇迹发源地之一。在这里有一个数据中心、两台商用量子计算机正在为摩根大通等客户运行&#xff0c;还在建设第三个原型机&#xff0c;这就是Quantinuum的量子实验室。 量子计算机之所以奇妙&#xff0c;是因为它们按照量…

32:确定你的public继承塑膜出is-a关系

“继承”可以是单一继承或多重继承&#xff0c;每一个继承连接可以是public&#xff0c;protected或private&#xff0c;也可以是virtual或non-virtual。然后是成员函数的各个选项&#xff1a;virtual&#xff1f;non-virtual&#xff1f;pure virtual&#xff1f;以及成员函数…

Java003——记事本编写和运行第一个Java程序HelloWorld

一、使用记事本创建Java并运行 1.1、设置文件显示后缀名 目的是为了方便查看文件类型 1.2、创建一个HelloWorld.java文件 java程序文件都是以.java后缀结尾的 1.3、编写Java程序 编写以下程序&#xff0c;并保存 public class HelloWorld {public static void main(Strin…

黑鲨3能升级鸿蒙5g吗,黑鲨3Pro与红魔5G,同为5G游戏手机,二者相比区别在哪

黑鲨3Pro与红魔5G&#xff0c;同为5G游戏手机&#xff0c;二者相比区别在哪 2020-03-17 16:42:20 1点赞 0收藏 4评论 对于游戏手机&#xff0c;还是有很多用户都是非常关注的&#xff0c;在前一段时间&#xff0c;黑鲨也是发布了旗下全新的腾讯黑鲨3系列游戏手机&#xff0c;其…

java基础面试

目录 0,高级特性 1,设计模式的6大原则和23种设计模式 2,jvm a,内存模型 使用元空间代替永久代的原因&#xff1a; 内存分配原则&#xff1a; b,GC机制 #垃圾回收器 c,类加载 #类加载器 3,集合框架 4,并发 5,并发包java.util.concurrent 6,io/nio(jdk1.4) …

鲁大师发布2021年度手机报告,去年最强的手机一文看完

1月19日&#xff0c;鲁大师2021第四届牛角尖奖颁奖典礼顺利举办&#xff0c;本次共颁出了14个奖项&#xff0c;包括手机的7个奖项、PC的6个奖项和1个电动车智能评测奖项。“牛角尖”奖数据依托自过去一年鲁大师数据中心用户通过鲁大师客户端进行手机、电脑评测得到的真实数据和…

小米android10怎么样,感觉小米10太贵不完美?这些Android旗舰也许就有你的菜!

昨天CFan对比了三星Galaxy S20和小米10系列的异同(详见《Galaxy S20和小米10谁更值&#xff1f;看完这篇文章你就懂了&#xff01;》)。除了这两款新秀外&#xff0c;接下来我们还将迎来很多旗舰级的Android新品&#xff0c;下面咱们就来一一盘点。 中兴天机 Axon 10s Pro&…