美文网首页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实现混合栈管理方案实

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