美文网首页Flutter
基于多Engine、Navigator2.0实现混合栈管理方案实

基于多Engine、Navigator2.0实现混合栈管理方案实

作者: 南顾夏浅 | 来源:发表于2021-08-25 19:48 被阅读0次

技术基石

最初我们是FlutterBoost的使用者,在1.12.x版本前后因为版本更新和使用问题上遇到了很大瓶颈,于是我们基于Flutter单engine开发了一款适合自己的混合栈管理方案,实现的功能比较单一。随着Flutter2.0发布,支持多Engine,Navigator2.0等更优秀的API出现,我们着手开发一款新的混合栈管理方案。

多Engine

虽然现在多Engine还在实验阶段,但对比1.x版本已经有很大提升。在第一个Engine创建之后其他的Engine都是基于第一个spawn(fork)的,他们之间共享GPU上下文、字体映射、图形缓冲区,真正需要新开辟的资源只有DartVM隔离内存,托管 Dart/UI 线程的新 pthread 线程。官方介绍新增一个Engine在AOT模式下大约会增加180K内存。

https://flutter.dev/docs/development/add-to-app/multiple-flutters
https://flutter.dev/go/multiple-flutters

Navigator2.0

这也是Flutter2.0的一个重大更新,为什么需要新的API?

  • 路由栈切换不灵活。只提供了 push pop等api,比如从【home => mine => settings】到【home => list => detail】这样的路由栈变化,在1.0版本将会是一个噩梦。
  • 无法对路由进行参数解析。从类似/details/:id路由配置中传递参数。
  • 嵌套路由的情况下子路由无法监听系统返回键,实现复杂。

Navigator2.0

  • 支持web。浏览器本身比手机终端更难把控,因为浏览器可以随便更改地址,导致路由的变化,怎么办。那么接收到根路由的变化,将会重新获取所有页面的配置信息。
  • 开发者完全把控路由,只需改变app状态就可以刷新当前路由栈。
    提供这个特性,我们可以随意定义各种 push pop popUntil 等操作,而且还能配套混合模式调用原生的方法。

https://medium.com/flutter/learning-flutters-new-navigation-and-routing-system-7c9068155ade
https://flutter.cn/community/tutorials/understanding-navigator-v2

要做些什么?

考虑框架要支持大部分的APP技术架构,实现以下特性

  • 支持 基本的 push、pop ,并携带参数
  • 支持 Uri 类型参数 push(兼容ARouter),提供Schema链接解析入口
  • 支持 自定义 MethodChannel 实现 从 FlutterBoost 的迁移
  • 数据同步 实现 一些 类似Cookie、用户信息在原生和Flutter之间的同步
  • 支持 Mock 原生和Flutter交互
  • 支持 Android Fragment,因为可能存在一些业务必须继承原生VC
  • 不修改 flutter-embed 任何代码,减少维护成本。

TODO

  • 真正意义支持 popUntil。其实现在的 pop 是通过 popUntil API 实现的
  • 支持多级路由配置,比如 /home/detail

框架设计

NavigatorStack队列

image.png
  • 只有当从Native打开一个Flutter页面的时候,创建新的engine
  • 监听所有的原生页面生命周期,当打开一个原生页面时,将页面信息添加到StackManager中
  • 当打开Flutter页面时,即调用了push,将Flutter页面信息保存在Flutter侧的StackManager中并同步到原生的StackManager。即所有的页面信息栈维护在原生,Flutter只维护当前纯Flutter的路由栈。
  • Flutter pop 时检查当前栈内的路由表,当个数大于1时正常pop,否则退出当前Flutter Activity

一个完整的push流程

image.png
可以看到 NavigatorStackManager中 队列在不断填充

数据共享

之所以做这个功能,是因为在开发的过程中,一般需要从原生同步一些数据,比如cookie,一般需要借助methodchannel实现,并且为了确保页面在网络请求之前能拿到数据,在网络请求之前都会 waite cookie获取,十分麻烦。于是找到一种机制,依赖java对象字段改变监听PropertyChangeListener,当数据发生改变是通知所有的Flutter Activity,当然对应的Flutter侧对象也是可监听的,可以实现在原生数据变化时Flutter页面同步变化,大大减少了开发时间。

页面数据传递

为了支持通过Uri的方式打开Flutter页面,将Uri参数通过提供的接口进行转化为 path 和 arguments,再通过 navigate-channel 传递到Flutter侧实现页面跳转。在数据传递中,一直保持着path和arguments参数。

实现pushForResult时,因为StackManager中保存着将要退出Activity的弱引用,在finish之前setResult。打开Flutter的native页面只需重写onActivityResult

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (data != null) {
    Log.e("AAA", "requestCode : " + requestCode + "; resultCode : " + resultCode);
    Log.e("AAA", (data.getSerializableExtra("result")).toString());
  }
}

Push Handler

当Flutter打开一个Native页面时(通过MuffinNavigator.of(context).pushNamed('/native_second', {'data': "data from Home Screen"});),原生在初始化时添加PushNativeHandler可自定义push。

public interface PushNativeHandler {

  void pushNamed(Activity activity, String pageName, @NonNull HashMap<String, Object> data);
}

当Native页面通过Uri打开Flutter页面时(通过MuffinNavigator.push(Uri.parse("meijianclient://meijian.io?url=first&name=uri_test"));),原生在初始化时添加PushFlutterHandler实现Uri参数的解析。

public interface PushFlutterHandler {

  String getPath(Uri uri);

  HashMap<String, Object> getArguments(Uri uri);
}

上面的例子我的现实可能是

//meijianclient://meijian.io?url=first&name=uri_test
public class DefaultPushFlutterHandler implements PushFlutterHandler {
  @Override public String getPath(Uri uri) {
    return "/" + uri.getQueryParameter("url");
  }

  @Override public HashMap<String, Object> getArguments(Uri uri) {
    HashMap<String, Object> arguments = new HashMap<>();
    for (String queryParameterName : uri.getQueryParameterNames()) {
      if (!TextUtils.equals("url", queryParameterName)) {
        arguments.put(queryParameterName, uri.getQueryParameter(queryParameterName));
      }
    }
    return arguments;
  }
}

接入Muffin

1.在Flutter项目中添加依赖 
 muffin: ^0.0.1
 
2.路由配置&&数据共享配置&&各种配置
   void main() async {
     ///确保channel初始化  
     WidgetsFlutterBinding.ensureInitialized();
     ///如果需要数据同步,则添加下面的代码,将原生的数据同步到Flutter侧
     await Share.instance.init([BasicInfo.instance]);
     ///添加 channel method mock
     Muffin.instance.addMock(MockConfig('someMethod', (key, value) => {}));
     ///get Navigator Widget
     runApp(await getApp());
    }

    Future<Widget> getApp() async {
     ///初始化 Navigator,配置页面路由信息
     ///initRoute参数:在单独运行时可以配置打开默认的页面
     ///initArguments参数:在单独运行时可以配置打开默认的页面参数
     ///emptyWidget参数:在跳转时没有找对应的页面,则显示定义的空页面
     final navigator = MuffinNavigator(routes: {
       '/home': (arguments) => MuffinRoutePage(child: HomeScreen()),
       '/first': (arguments) => MuffinRoutePage(
            child: FirstScreen(
          arguments: arguments,
        ))
    },
        initRoute:'/',
        initArguments:{},
        emptyWidget: CustomEmptyView()
    );
    return MaterialApp.router(
      ///路由解析  
      routeInformationParser: MuffinInformationParser(navigator: navigator),
      routerDelegate: navigator,
      ///系统返回键监听
      backButtonDispatcher: MuffinBackButtonDispatcher(navigator: navigator),
   );
  }

3.原生,在Applocation中初始化 Muffin
   //普通初始化,第二个参数为 各种提供给上层的接口实现
   Muffin.init(this, options());

   private Muffin.Options options() {
    //数据同步对象   
    List<DataModelChangeListener> models = new ArrayList<>();
    models.add(BasicInfo.getInstance());

    return new Muffin.Options()
    //Flutter 跳转 Native 时提供给上层的接口
    .setPushNativeHandler((activity, pageName, data) -> {
      //根据 pageName 和 data 拼接成 schema 跳转
      if (TextUtils.equals("/main", pageName)) {
        Intent intent = new Intent(activity, MainActivity.class);
        activity.startActivity(intent);
      }
    })
    //Native Uri 类型跳转到 Flutter 接口,可参考默认实现
    .setPushFlutterHandler(new DefaultPushFlutterHandler())
    //带有数据同步能力
    .setModels(models)
    //新增自定义VC,使用【MuffinFlutterFragment】, 参考[BaseFlutterActivity]
    //默认使用【MuffinFlutterActivity】
    .setAttachVc(BaseFlutterActivity.class);
  }
4.在 Manifest.xml文件中配置 FlutterActivity  
5. 好了,Muffin已经集成完了。

资源

github https://github.com/meijian-io/muffin

pub https://pub.dev/packages/muffin

相关文章

  • 基于多Engine、Navigator2.0实现混合栈管理方案实

    技术基石 最初我们是FlutterBoost的使用者,在1.12.x版本前后因为版本更新和使用问题上遇到了很大瓶颈...

  • 栈的三种实现

    一、基于deque实现 优点:利用deque动态管理内存,栈的内存无上限,STL中的栈也是基于deque实现的。 ...

  • vue技术栈项目实战设计方案

    本篇文章基于实战下的线上项目,给出基于vue技术栈实现方案。 一、所需工具 npm:包管理工具,前端开发基本都会用...

  • Flutter的iOS应用篇之混合开发篇

    本篇,重点介绍如何让Flutter能够开发,实现业务需求。这部分包括:混合栈的管理,混合下的能力补齐和包管理。 混...

  • 混合栈管理

    Flutter 管理页面有两个概念:Route 和 Navigator。 Navigator 是一个路由管理的 W...

  • MLSQL Cluster 路由策略

    前言 MLSQL Cluster 具备多MLSQL Engine 实例管理功能,实现负载均衡,多业务切分等等功能。...

  • 实现栈_基于数组

    基于动态数组实现栈声明栈的接口 实现类及测试

  • 基于动态数组的实现 Java实现 基于链表的栈的实现 Java实现

  • 算法解剖 - 数据结构篇 - 栈

    目录 栈是一种什么样的数据结构? 栈的实现 栈的应用<1> 基于它的LIFO策略,可以逆序排列<2> 模拟计算机实...

  • 栈的实现

    基于顺序表的栈实现: 测试代码: 基于顺序表的链表实现: 基础数据类: 测试代码: 以为这个会比链表东西会多一些,...

网友评论

    本文标题:基于多Engine、Navigator2.0实现混合栈管理方案实

    本文链接:https://www.haomeiwen.com/subject/aoeeiltx.html