怎么开发启动框架plugin

本文原作者: 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{ @override 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{ @override state createstate() { registerroutes(); return _baseapp(build); } widget build(buildcontext context, widget page); void registerroutes();}class _baseapp extends state{ 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 函数,在这里使用 routemanager 的 registerroute 将每个页面注册一下即可。
3. basepage                                             import 'package:flutter/material.dart';abstract class basepage extends statefulwidget{ dynamic args;}abstract class basestate extends state{ 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_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) }}  这个是单例,初始化并预热 flutterengine,同时创建 basicmessagechannel 用于后续交互。需要在 application 的 oncreate 中调用它的 init 函数来初始化。  2. flutterbootactivity                                                                                                       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?) : 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 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 @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 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 
 创建一个 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{ 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. ); }}  
继承 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{ @override state createstate() { return _secondpage(); }}class _secondpage extends basestate{ @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 中使用了,首先要初始化,如下: 
                      import android.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。
  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 uikitimport 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 页面即可,如下:                          @objc func showmain() { let flutterviewcontroller = flutterbootviewcontroller(route: main, params: nil) present(flutterviewcontroller, animated: true, completion: nil) }@objc func showsecond() { let params : dictionary = [key : 123] let flutterviewcontroller = flutterbootviewcontroller(route: second, params: params) present(flutterviewcontroller, animated: true, completion: nil) }  
同样分别打开两个页面,可以看到启动几乎没有加载时间,同时参数也正确传递。
原文标题:flutter 混合开发: 开发一个简单的快速启动框架 | 开发者说·dtalk
文章出处:【微信公众号:谷歌开发者】欢迎添加关注!文章转载请注明出处。

供应村田muRata品牌汽车级陶瓷谐振器CSTNE_G系列
HMC-APH518功率放大器
扬尘监测解决方案:看扬尘监测如何“优雅”地解决
基于3DMM求解三维人脸模型的求解方法
STM32与51单片机有什么区别
怎么开发启动框架plugin
人工智能将应用落地的八大领域
应用监控的选型思考
功率放大器基于压电驱动的无针注射用脉冲电源设计应用
高性价比双核Cortex-A55 RZ/G2L替代Cortex-A8
无人机扰航问题怎样破局
液位计的分类_各类液位计的介绍
氢气传感器在工业控制领域的应用
研究人员开发出一种实验性的贴纸,可贴在皮肤上监测人体健康状况!
X射线检测BGA、CSP焊点图像的评估和判断及其他应用
智慧球场,芯讯通连接体育空间
ORB305产品等级保护达到了几级
环球仪器与Cogiscan携手在车间实现工业4.0
led灯带基础知识及安装
虫情测报灯的功能优势