0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

怎么开发启动框架plugin

谷歌开发者 来源:BennuCTech 作者:BennuC 2022-06-24 16:28 次阅读
本文原作者: BennuC原文发布于: BennuCTech

在移动端中启动 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;  }
  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{
  @override  State createState() {    return _PageNotFount();  }
}
class _PageNotFount extends BaseState<PageNotFount>{
  @override  Widget buildImpl(BuildContext context) {    return Scaffold(      body: Center(        child: Text("page not found"),      ),    );  }}

它的作用就是管理路由,是一个单例,用一个 map 来维护路由映射。其中三个函数比较重要:
  • registerRoute: 注册路由,一般在启动时调用;
  • getRouteFactory: 返回 RouteFactory,将它赋值给 MaterialApponGenerateRoute 字段;
  • getPage: 通过 route 名称返回页面 widget。
这里 getRouteFactorygetPage 共用一个路由 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{
  @override  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 "";    });  }
  @override  Widget build(BuildContext context) {    return buildImpl.call(context, curPage);  }
}

是一个抽象类,真正的 Flutter app 需要继承它。主要是封装了一个 BasicMessageChannel 用来与 Android/iOS 交互,并根据收到的消息处理页面内的切换,实现快速启动。

继承它的子类需要实现 registerRoutes 函数,在这里使用 RouteManagerregisterRoute 将每个页面注册一下即可。

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;
  @override  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_boot
import 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.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)    }}
这个是单例,初始化并预热 FlutterEngine,同时创建 BasicMessageChannel 用于后续交互。需要在 ApplicationonCreate 中调用它的 init 函数来初始化。 2. FlutterBootActivity
package com.bennu.flutter_boot
import android.content.ComponentNameimport android.content.Contextimport android.content.Intentimport android.os.Bundleimport android.os.PersistableBundleimport io.flutter.embedding.android.FlutterActivityimport 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,提供一个 build (context: Context, routeName: String, params: Map?) 函数来启动,传递路由名称和参数。在 onResume 的时候通过 BasicMessageChannel 将这两个数据 send 给 Flutter 处理。

iOS

iOS 与 Android 类似

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

这也是一个单例,初始化并启动 FlutterEngine,并创建一个 FlutterBasicMessageChannel 与 Flutter 交互。

需要在 iOS 项目的 AppDelegate 初始化时调用它的 initEngine 函数。

2. FlutterBootViewController

FlutterBootViewController.h
#ifndef FlutterBootViewController_h#define FlutterBootViewController_h
#import 
@interface FlutterBootViewController : FlutterViewController
- (nonnull instancetype)initWithRoute:(nonnull NSString*)route                       params:(nullable NSDictionary*)params;
@end
#endif /* FlutterBootViewController_h */FlutterBootViewController.m#import "FlutterBootViewController.h"#import "FlutterBootEngine.h"
@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时机有点晚,会先显示一下上一个页面才更新到新页面,所以换成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

创建一个 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{
  @override  State createState() {    return _FirstPage();  }}
class _FirstPage extends BaseState<FirstPage>{
  void _goClick() {    Navigator.of(context).pushNamed("second", arguments: {"key":"123"});  }
  @override  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.    );  }}

继承 BasePageBaseState 即可,点击按钮可以跳转到页面 2。

2. SecondPage.dart

import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:flutter_boot/BasePage.dart';
class SecondPage extends BasePage{
  @override  State createState() {    return _SecondPage();  }
}
class _SecondPage extends BaseState<SecondPage>{
  @override  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 {  @override  Widget build(BuildContext context, Widget page) {    return MaterialApp(      title: 'Flutter Demo',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: page,      onGenerateRoute: RouteManager.instance.getRouteFactory(),    );  }
  @override  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')  //module    provided rootProject.findProject(":flutter_boot") //plugin}

注意 plugin 的名称是之前在 module 中的 pubspec.yaml 定义的。

然后就可以在 Android 中使用了,首先要初始化,如下:

importandroid.app.Applicationimport com.bennu.flutter_boot.BootEngine
public 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 中初始化,如下:
importUIKitimport Flutterimport flutter_boot
@UIApplicationMainclass 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 页面即可,如下:
@objcfuncshowMain(){    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)  }

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

原文标题:Flutter 混合开发: 开发一个简单的快速启动框架 | 开发者说·DTalk

文章出处:【微信公众号:谷歌开发者】欢迎添加关注!文章转载请注明出处。



审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Android
    +关注

    关注

    12

    文章

    3851

    浏览量

    125637
  • 框架
    +关注

    关注

    0

    文章

    297

    浏览量

    17044
  • 代码
    +关注

    关注

    30

    文章

    4555

    浏览量

    66751
  • Plugin
    +关注

    关注

    0

    文章

    9

    浏览量

    2988

原文标题:Flutter 混合开发: 开发一个简单的快速启动框架 | 开发者说·DTalk

文章出处:【微信号:Google_Developers,微信公众号:谷歌开发者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    谷歌模型框架是什么软件?谷歌模型框架怎么用?

    谷歌模型框架通常指的是谷歌开发的用于机器学习和人工智能的软件框架,其中最著名的是TensorFlow。TensorFlow是一个开源的机器学习框架,由谷歌的机器学习团队
    的头像 发表于 03-01 16:25 235次阅读

    谷歌模型框架是什么?有哪些功能和应用?

    谷歌模型框架(Google Model Framework)并不是一个特定的框架,而是指谷歌开发的一系列软件框架,用于支持机器学习和人工智能的应用。以下是一些与谷歌模型
    的头像 发表于 02-29 18:11 659次阅读

    米哈游宣布启动鸿蒙原生应用开发

    12月18日,米哈游宣布将基于HarmonyOS NEXT启动鸿蒙原生应用开发,成为又一家启动鸿蒙原生应用开发的头部游戏厂商。 作为一家创立于2011年的科技型文创企业,上海米哈游网络
    的头像 发表于 12-18 10:07 292次阅读

    基于springboot和vue框架的Java

    本文将详细介绍基于Spring Boot和Vue框架进行Java应用开发的实践。首先,将介绍Spring Boot和Vue框架的基本概念及其优点。然后,将详细介绍如何搭建Spring Boot
    的头像 发表于 12-03 15:15 510次阅读

    javaweb的三大框架有哪些

    在JavaWeb开发中,有许多流行的框架可供选择。这些框架旨在提高开发效率、简化开发过程,并为开发
    的头像 发表于 12-03 11:47 761次阅读

    springboot启动流程

    Spring Boot 是一个快速开发框架,基于 Spring 框架,用于简化项目的配置和部署。它提供了自动配置、嵌入式服务器和一套默认的代码结构,使得开发者可以快速地
    的头像 发表于 11-22 16:04 355次阅读

    OpenHarmony AI框架开发指导

    结构如下图所示。 图1 AI引擎框架 2、搭建环境 准备开发板:Hi3516DV300或Hi3518EV300 下载源码 二、技术规范 1、代码管理规范 AI引擎框架包含client、server
    发表于 09-19 18:54

    深度学习框架是什么?深度学习框架有哪些?

    深度学习框架是什么?深度学习框架有哪些?  深度学习框架是一种软件工具,它可以帮助开发者轻松快速地构建和训练深度神经网络模型。与手动编写代码相比,深度学习
    的头像 发表于 08-17 16:03 1771次阅读

    labview通用视觉框架参考

    labview通用视觉软件框架,机器视觉通用框架 通用视觉框架源代码。可以参考用于开发常规案例。里面有部分函数,用户也可随意编辑函数,开发
    发表于 08-04 15:58 34次下载

    labview视觉通用平台框架源代码

    本帖最后由 1281788316 于 2024-4-19 13:52 编辑 labview通用视觉软件框架,机器视觉通用框架 通用视觉框架源代码。可以参考用于开发常规案例。里面有
    发表于 08-03 16:57

    基于Java的接口快速开发框架——magic-api

    magic-api 是一个基于Java的接口快速开发框架,编写接口将通过magic-api提供的UI界面完成,自动映射为HTTP接口,无需定义Controller、Service、Dao、Mapper、XML、VO等Java对象即可完成常见的HTTP API接口
    发表于 07-19 11:42 440次阅读
    基于Java的接口快速<b class='flag-5'>开发</b><b class='flag-5'>框架</b>——magic-api

    Openwrt开发指南 第13章 OpenWrt启动流程

    任何系统的启动都是开发人员首要关注的问题,因为只有了解了系统的启动流程和启动机制,才能真正掌握一个系统,如果对启动
    的头像 发表于 06-29 09:09 2562次阅读
    Openwrt<b class='flag-5'>开发</b>指南 第13章 OpenWrt<b class='flag-5'>启动</b>流程

    OpenHarmony自动化测试框架开发指南

    OpenHarmony 自动化测试框架是 OpenHarmony 提供的支持 JS/TS 语言的单元及 UI 测试框架,支持开发者针对应用接口或系统接口进行单元测试,并且可基于 UI 操作进行 UI 自动化脚本的编写。
    的头像 发表于 05-15 09:35 721次阅读
    OpenHarmony自动化测试<b class='flag-5'>框架开发</b>指南

    智能上位机框架

    ,还包括ORM、图表制作、WebGis等等,使用过框架的小伙伴都知道,框架最大的好处在于快 速、高效。 但是很多时候,我们都处于一个框架使用者的角色,很少去真正地开发
    发表于 05-08 11:02 1次下载
    智能上位机<b class='flag-5'>框架</b>

    C#|上位机开发新手指南(三)框架

    在上位机开发中,Windows Forms是使用最广泛的C#框架之一。Windows Forms是.NET Framework中的一个GUI框架,提供了丰富的GUI控件和易于使用的编程 模型,可以
    发表于 05-08 10:27 3次下载
    C#|上位机<b class='flag-5'>开发</b>新手指南(三)<b class='flag-5'>框架</b>