zoukankan      html  css  js  c++  java
  • flutter_boot android和flutter源码阅读记录

    版本号0.1.54
    看源码之前,我先去看下官方文档,对于其源码的设计说明,文中所说的原生都是指android

    看完官方文档的说明,我有以下几个疑问

    第一个:容器是怎么设计的?

    第二个:native和flutter的channel的通道是如何设计的?

    第三个:Flutter是适配层到底再做些什么?

    中控中心FlutterBoost

    单独拎出来讲讲,这个类比较简单,就是集合各个模块并让其初始化,同时也是该插件入口处,不管原生和flutter都一样,看源码也是从这里开始看起,但原生和flutter的初始化流程稍微有少许区别,主要还是因为原生是作为容器,flutter的容器是依赖于原生容器。

    原生init

    入口:/flutter_boost/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/MyApplication.java

    FlutterBoost.init从这里开始进入

     FlutterBoost.init(new Platform() {
    
                @Override
                public Application getApplication() {
                    return MyApplication.this;
                }
    
                @Override
                public boolean isDebug() {
                    return true;
                }
    
                @Override
                public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
                    PageRouter.openPageByUrl(context, url, urlParams, requestCode);
                }
    
                @Override
                public IFlutterEngineProvider engineProvider() {
                    //注意这里  覆写了createEngine
                    return new BoostEngineProvider() {
                        @Override
                        public BoostFlutterEngine createEngine(Context context) {
                            return new BoostFlutterEngine(context, new DartExecutor.DartEntrypoint(
                                    context.getResources().getAssets(),
                                    FlutterMain.findAppBundlePath(context),
                                    "main"), "/");
                        }
                    };
                }
    
                @Override
                public int whenEngineStart() {
                    return ANY_ACTIVITY_CREATED;
                }
            });
    
            BoostChannel.addActionAfterRegistered(new BoostChannel.ActionAfterRegistered() {
                @Override
                public void onChannelRegistered(BoostChannel channel) {
                    //platform view register should use FlutterPluginRegistry instread of BoostPluginRegistry
                    TextPlatformViewPlugin.register(FlutterBoost.singleton().engineProvider().tryGetEngine().getPluginRegistry());
                }
            });
        }
    

    上面大部分方法,做过android也知道是干嘛的,这里重点讲讲IFlutterEngineProvider这个接口,这里有3个方法,如下

    /**
     * a flutter engine provider
     */
    public interface IFlutterEngineProvider {
    
        /**
         * create flutter engine, we just hold a single instance now
         * @param context
         * @return
         */
        BoostFlutterEngine createEngine(Context context);
    
        /**
         * provide a flutter engine
         * @param context
         * @return
         */
        BoostFlutterEngine provideEngine(Context context);
    
        /**
         * may return null
         * @return
         */
        BoostFlutterEngine tryGetEngine();
    }
    
    

    抽象成接口,根据项目的实际情况,开发者可以自己实现flutter引擎,或采用官方源码里自己的实现类即BoostEngineProvider,但想不明白 createEngine 和provideEngine 到底有何区别,到底为何弄成两个方法,不就是个提供个flutter引擎实例吗?

    看了下createEngine的实现,主要加载实例BoostFlutterEngine,这个实例看名字也清楚是进行flutter引擎的初始化,设置了dart默认入口点即main,设置了路由起点及插件的声明注册一类
    然后去看provideEngine方法的实现,代码较少,如下

       @Override
        public BoostFlutterEngine provideEngine(Context context) {
            Utils.assertCallOnMainThread();
    
            if (mEngine == null) {
                FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]);
                FlutterMain.ensureInitializationComplete(
                        context.getApplicationContext(), flutterShellArgs.toArray());
    
                mEngine = createEngine(context.getApplicationContext());
    
                final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
                if(stateListener != null) {
                    stateListener.onEngineCreated(mEngine);
                }
            }
            return mEngine;
        }
    

    初始化flutter参数及增加一个回调,没什么特别之处,然后去翻了下flutter.jar的FlutterActivity源码,它的flutter引擎初始化最后是追踪到FlutterFragment,关键代码如下

    public void onAttach(Context context) {
            super.onAttach(context);
            //这里初始化flutter参数
            this.initializeFlutter(this.getContextCompat());
            if (this.flutterEngine == null) {
            //这里是初始化flutter引擎
                this.setupFlutterEngine();
            }
    
            this.platformPlugin = new PlatformPlugin(this.getActivity(), this.flutterEngine.getPlatformChannel());
        }
    
    

    这里是连在一起的,flutter源码没有翻来覆去全看一遍,闲鱼进行这样的接口设计应该是有一定的原因

    这里再单独讲下插件的注册,我们知道native是作为插件库需要原生项目依赖,在初始化中,注意一下插件的注册,是用反射实现的,如下
    路径:flutter_boost/android/src/main/java/com/idlefish/flutterboost/BoostFlutterView.java 中的init方法,代码如下

      private void init() {
     ...
            mFlutterEngine.startRun((Activity)getContext());
    ...
        }
    

    跟随startRun方法深入,就会找到FlutterBoost.singleton().platform().registerPlugins(mBoostPluginRegistry),跟着下去,会发现使用反射方式来实现插件注册 如下代码

     @Override
        public void registerPlugins(PluginRegistry registry) {
            try {
                Class clz = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
                Method method = clz.getDeclaredMethod("registerWith",PluginRegistry.class);
                method.invoke(null,registry);
            }catch (Throwable t){
                throw new RuntimeException(t);
            }
        }
    

    毕竟引擎初始化框架重新编写了,所以在插件的注册上也改变了,init的原生部分就讲解到此

    flutter

    入口:/flutterProject/flutter_boost/example/lib/main.dart
    flutter的源码查看前,大家务必先去看看flutter的初始化流程,Navigator源码解析及Route源码解析,因为不晓得相关初始化流程及Navigator的设计原理,里面的关键调用 大家都可能看不明白,我这边可能也是直接就过了,这里给个链接大家可以去看看

    Flutter 源码解析

    @override
      void initState() {
        super.initState();
        print('_MyAppState initState');
        ///路由注册,原生通过MethodChannel通道来启动对应的flutter页面
        FlutterBoost.singleton.registerPageBuilders({
          'first': (pageName, params, _) => FirstRouteWidget(),
          'second': (pageName, params, _) => SecondRouteWidget(),
          'tab': (pageName, params, _) => TabRouteWidget(),
          'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params),
    
          ///可以在native层通过 getContainerParams 来传递参数
          'flutterPage': (pageName, params, _) {
            print("flutterPage params:$params");
    
            return FlutterRouteWidget();
          },
        });
      }
    
      @override
      Widget build(BuildContext context) {
        print('_MyAppState build');
        return MaterialApp(
            title: 'Flutter Boost example',
            builder: FlutterBoost.init(postPush: _onRoutePushed),
            home: Container());
      }
      ///flutter 路由push 监听,每启动一个新的flutter页面 就回调该方法
      void _onRoutePushed(
          String pageName, String uniqueId, Map params, Route route, Future _) {
        print('pageName'+pageName+"
    ");
      }
    

    先跟随FlutterBoost.singleton进去看看,其构造函数如下

    FlutterBoost(){
        Logger.log('FlutterBoost 构造函数');
        ContainerCoordinator(_boostChannel);
      }
    

    跟随着ContainerCoordinator去看看,ContainerCoordinator和BoostChannel如何一起来维护通信通道,ContainerCoordinator构造函数如下

    ContainerCoordinator(BoostChannel channel) {
        assert(_instance == null);
    
        _instance = this;
    
        channel.addEventListener("lifecycle",
            (String name, Map arguments) => _onChannelEvent(arguments));
    
        channel.addMethodHandler((MethodCall call) => _onMethodCall(call));
      }
    

    增加native生命周期监听,增加方法监听,再去看看_onChannelEvent和_onMethodCall方法就应该清楚ContainerCoordinator其实就是翻译员,将与原生通信的协议进行解释翻译,根据传过来的事件名,方法名 逐一进行需要的框架处理,BoostChannel其实是声明通道,将接收和发送功能进行封装,接收natvie传来的信息,将从flutter的信息发送到native,当然也做了一部分的框架业务处理,将event和method事件进行区分
    分发,个人觉得将该功能直接丢至ContainerCoordinator处理可能更好点,应该是出于为了区分event和method特意在BoostChannel进行处理

    接下来看看ContainerCoordinator对于native传过来的通信数据处理,代码如下,就分为之前说的event和method两类,代码注释也写了

     /// 对native 整个应用的 生命周期 进行抽象出的几个行为事件,让flutter做相应的处理
      /// android端 基本上除了有回退事件的处理,剩余的生命周期 仅仅是做了监听没做任何处理
      /// 分别是回退处理 android才有
      /// foreground  本应用是处于前台
      /// background  本应用是处于后台
      /// scheduleFrame 触发一帧的绘制,但ios和android 都没找到发送该事件的代码,老版本遗留代码?
      Future<dynamic> _onChannelEvent(dynamic event) {
         ...
      }
    
      /// 对native view生命周期(在android 就是activity)进行抽象出的 几个行为事件,
      /// 让flutter做相应的框架处理
      Future<dynamic> _onMethodCall(MethodCall call) {
      ...
      }
    
    

    这里就不讲Method的处理逻辑,后面会结合容器部分重点讲

    接下来再回到main.dart文件,跟随FlutterBoost.init方法进去看一下,就是初始化BoostContainerManager,不再深入,后面会结合起来一起讲BoostContainerManager

    channle

    这模块代码比较少,先从这模块开始讲起

    我们知道原生和flutter之间的通信就是通过MethodChannel这个类实现的(原生和flutter的类名一样),前面有讲flutter的boost_channel.dart的作用,native的BoostChannel其实也一样,将接收和发送功能进行封装,接收flutter传来的信息,将从native的信息发送到flutter

    原生部分

    前面原生初始化 讲到插件的注册是通过反射实现的,GeneratedPluginRegistrant.java当中的registerWith方法我们接下去看一下,注册的时候做了哪些事,路径lutter_boost/android/src/main/java/com/idlefish/flutterboost/BoostChannel.java

    public static void registerWith(PluginRegistry.Registrar registrar) {
            sInstance = new BoostChannel(registrar);
            //通道注册后,处理flutter的method 调用处理
            for(ActionAfterRegistered a : sActions) {
                a.onChannelRegistered(sInstance);
            }
            //状态监听 回调
            if(FlutterBoost.sInstance != null) {
                final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
                if (stateListener != null) {
                    stateListener.onChannelRegistered(registrar, sInstance);
                }
            }
    
            sActions.clear();
        }
    

    看到了吧,BoostChannel的实例化是在插件注册的时候进行的,继续深入,如下代码

      private BoostChannel(PluginRegistry.Registrar registrar){
            mMethodChannel = new MethodChannel(registrar.messenger(), "flutter_boost");
    
            mMethodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
                @Override
                public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
    
                    if (methodCall.method.equals("__event__")) {
                        String name = methodCall.argument("name");
                        Map args = methodCall.argument("arguments");
    
                        Object[] listeners = null;
                        synchronized (mEventListeners) {
                            Set<EventListener> set = mEventListeners.get(name);
                            if (set != null) {
                                listeners = set.toArray();
                            }
                        }
    
                        if(listeners != null) {
                            for(Object o:listeners) {
                                ((EventListener)o).onEvent(name,args);
                            }
                        }
                    }else{
                        Object[] handlers;
                        synchronized (mMethodCallHandlers) {
                            handlers = mMethodCallHandlers.toArray();
                        }
    
                        for(Object o:handlers) {
                            ((MethodChannel.MethodCallHandler)o).onMethodCall(methodCall,result);
                        }
                    }
                }
            });
        }
    

    对于通道上的数据分为两类event和method,都是和flutter一一对应的,前面flutter初始化中,也讲过,在原生这边event 没有框架上的业务处理,但提供了回调,根据自己的业务是否需要增加监听

    method的处理,去查看BoostMethodHandler,FlutterBoost.java作为内部类存在,如下

    class BoostMethodHandler implements MethodChannel.MethodCallHandler {
    
            @Override
            public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {
                switch (methodCall.method) {
                    case "pageOnStart":
                  ...
                    break;
                    case "openPage":
                   ...
                    break;
                    case "closePage":
                   ...
                    break;
                    case "onShownContainerChanged":
                   ...
                    break;
                    default:
                    {
                        result.notImplemented();
                    }
                }
            }
        }
    

    看这些switch的case处理,大概也猜出来是干嘛的了,是flutter通知native的页面行为事件,例openPage,closePage等等 ,在初始化中,讲了挺多flutter的chanell,所以这里就不在讲了,但是讲讲两边的设计
    两边都有个channel类,主要都是用来接收和发送消息的
    flutter专门有一个类ContainerCoordinator.dart,中文翻译过来就是集装箱协调员,用于通信事件的统一处理,就是将从原生接收到的信息进行处理,但是在原生那边并没有类似的类,而是将这个工作放在FlutterBoost.java这个内部类中,个人觉得为了保持统一可以专门抽象出个类,将该功能放置该类中,放在FlutterBoost.java不能保持高度统一且不雅观吧

    讲到这里,其实通道的设计大家应该理解得差不多了(解决开头提出的问题)

    native和flutter的channel的通道是如何设计的?

    容器

    原生部分

    闲鱼的栈管理方案,是将栈的管理都放置原生,所以在原生必须暴露栈的管理,让项目接入方能在原有栈的解决方案上 融合进闲鱼的栈管理方案,所以页面的打开就是入口处,从该入口处去查看容器的设计,先从demo中的PageRouter.java看起,如下代码

    public class PageRouter {
    
        public static final String NATIVE_PAGE_URL = "sample://nativePage";
        public static final String FLUTTER_PAGE_URL = "sample://flutterPage";
        public static final String FLUTTER_FRAGMENT_PAGE_URL = "sample://flutterFragmentPage";
    
        public static boolean openPageByUrl(Context context, String url,Map params) {
            return openPageByUrl(context, url,params, 0);
        }
    
        public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {
            try {
                if (url.startsWith(FLUTTER_PAGE_URL)) {
                    context.startActivity(new Intent(context, FlutterPageActivity.class));
                    return true;
                } else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) {
                    context.startActivity(new Intent(context, FlutterFragmentPageActivity.class));
                    return true;
                } else if (url.startsWith(NATIVE_PAGE_URL)) {
                    context.startActivity(new Intent(context, NativePageActivity.class));
                    return true;
                } else {
                    return false;
                }
            } catch (Throwable t) {
                return false;
            }
        }
    }
    

    如果大家用过阿里的Aroute路由框架,就会觉得很亲切,将每个View配置一个路由,还是前端的思想借鉴过来,一个统一的界面打开处,根据路由路径,判断是原生view还是FlutterView,分别打开不同的Activity

    接入方,在这里可以根据自身的原生栈管理再进行抽象封装就ok了

    接下来看看哪里调用了openPageByUrl(注意是下面那个)方法,发现正是我们一开始框架初始化的时候在调用,如下,文件路径flutter_boost/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/MyApplication.java

     @Override
                public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
                    PageRouter.openPageByUrl(context, url, urlParams, requestCode);
                }
    
    

    再查看是哪里调用了该方法,一直追踪到FlutterBoost.java,关键代码如下

    case "openPage":
                    {
                        try {
                            Map<String,Object> params = methodCall.argument("urlParams");
                            Map<String,Object> exts = methodCall.argument("exts");
                            String url = methodCall.argument("url");
    
                            mManager.openContainer(url, params, exts, new FlutterViewContainerManager.OnResult() {
                                @Override
                                public void onResult(Map<String, Object> rlt) {
                                    if (result != null) {
                                        result.success(rlt);
                                    }
                                }
                            });
                        }catch (Throwable t){
                            result.error("open page error",t.getMessage(),t);
                        }
                    }
    

    Flutter 通过channel通道 通知原生 要打开一个新的页面,然后原生将自身的生命周期通过通道告知flutter,flutter再进行相应的页面处理,虽然短短一句话,但其中的逻辑及代码量还是很多的...

    前面已经讲了通道部分,这里再贴点关键代码,原生将自身的生命周期通过通道告知flutter,关键代码如下

    private class MethodChannelProxy {
            private int mState = STATE_UNKNOW;
    
            private void create() {
              ...
            }
            private void appear() {
                ...
            }
            private void disappear() {
               ...
                }
            }
            private void destroy() {
                ..
            }
            public void invokeChannel(String method, String url, Map params, String uniqueId) {
                ...
            }
            public void invokeChannelUnsafe(String method, String url, Map params, String uniqueId) {
               ..
            }
        }
        public static String genUniqueId(Object obj) {
            return System.currentTimeMillis() + "-" + obj.hashCode();
        }
    }
    

    ok,现在已经找到了MethodChannelProxy类,那我们就继续回找(注意我这里讲解都是从冰山一角再慢慢往上查,最终再将冰山一起探索完毕)MethodChannelProxy是作为ContainerRecord.java的内部类存在。接下来我们来看ContainerRecord类,其实现了IContainerRecord接口,再继续深究找到IOperateSyncer接口,代码如下

    public interface IOperateSyncer {
    
        void onCreate();
    
        void onAppear();
    
        void onDisappear();
    
        void onDestroy();
    
        void onBackPressed();
    
        void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
    
        void onNewIntent(Intent intent);
    
        void onActivityResult(int requestCode, int resultCode, Intent data);
    
        void onContainerResult(int requestCode, int resultCode, Map<String,Object> result);
    
        void onUserLeaveHint();
    
        void onTrimMemory(int level);
    
        void onLowMemory();
    }
    

    该接口是通过对原生的生命周期再结合flutter的生命周期特色及android自身的特性(有回退物理键)抽象出来的,继续回到接下来我们来看ContainerRecord类,发现MethodChannelProxy类其实就是做个代理功能,看名字也清楚,在接口方法被调用的时候,通知flutter

    接下来看看IContainerRecord的方法被调用处,追踪到BoostFlutterActivity.java和BoostFlutterFragment.java,这里我们只看Activity,Fragment基本差不多。我们看到BoostFlutterActivity在走onCreate生命周期方法时,创建了mSyncer,关键代码如下

       @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            configureWindowForTransparency();
    
    mSyncer = FlutterBoost.singleton().containerManager().generateSyncer(this);
    
    
            mFlutterEngine = createFlutterEngine();
            mFlutterView = createFlutterView(mFlutterEngine);
    
            setContentView(mFlutterView);
    
            mSyncer.onCreate();
    
            configureStatusBarForFullscreenFlutterExperience();
        }
    

    FlutterBoost.singleton().containerManager().generateSyncer() 继续深入,追踪到FlutterViewContainerManager类,关键代码如下

    @Override
        public IOperateSyncer generateSyncer(IFlutterViewContainer container) {
            Utils.assertCallOnMainThread();
            //创建容器记录实例
            ContainerRecord record = new ContainerRecord(this, container);
            if (mRecordMap.put(container, record) != null) {
                Debuger.exception("container:" + container.getContainerUrl() + " already exists!");
            }
            mRefs.add(new ContainerRef(record.uniqueId(),container));
            //讲接口引用返回
            return record;
        }
    
    

    ContainerRecord实例在这里创建,同时将接口引用返给Activity,这样就和原生View的生命周期关联起来了

    注意到这里我们已经追踪到FlutterViewContainerManager.java类,已经可以从上帝视角去看了。

    刚刚从容器打开出入一直追踪到FlutterViewContainerManager.java类,该类看名字就清楚就是容器的管理者,容器创建、打开、关闭、销毁、弹出、移除等等工作都是在这儿,这里最关键的generateSyncer方法刚刚追踪的时候已经讲过。这里再重点讲讲该类的setContainerResult方法,如下

    void setContainerResult(IContainerRecord record,int requestCode, int resultCode, Map<String,Object> result) {
    
            IFlutterViewContainer target = findContainerById(record.uniqueId());
            if(target == null) {
                Debuger.exception("setContainerResult error, url="+record.getContainer().getContainerUrl());
            }
    
            if (result == null) {
                result = new HashMap<>();
            }
    
            result.put("_requestCode__",requestCode);
            result.put("_resultCode__",resultCode);
    
            final OnResult onResult = mOnResults.remove(record.uniqueId());
            if(onResult != null) {
                onResult.onResult(result);
            }
        }
    

    单独拎出来讲,主要是本人好奇 目标页 向 起始面 如何传输数据的,
    在纯ntive就是靠着onActivityResult回调拿到目标页传回的数据,该方法就是处理目标页传回来后的处理

    在混合栈中 就分为3种情况

    1.native-flutter
    2.flutter-native
    3.flutter-flutter

    native和ntive就不用说了,都用不到该框架


    第一种情况:native-flutter
    demo自身当中并没有相关的演示代码,于是我按照原生是如何接受传回来的数据去进行更改,改了两处如下,类路径flutterProject/flutter_boost/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/MainActivity.java

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    data.getSerializableExtra(IFlutterViewContainer.RESULT_KEY);
    Debuger.log("MainActivityResult"+data.getSerializableExtra(IFlutterViewContainer.RESULT_KEY).toString());
    }
    还有一处,类路径flutterProject/flutter_boost/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/PageRouter.java
    如下

    public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {
            try {
                if (url.startsWith(FLUTTER_PAGE_URL)) {
                    //接受目标页的回传必须通过startActivityForResult进行打开
                    ((Activity)context).startActivityForResult(new Intent(context, FlutterPageActivity.class),requestCode);
                    return true;
                } else if (url.startsWith(FLUTTER_FRAGMENT_PAGE_URL)) {
                    context.startActivity(new Intent(context, FlutterFragmentPageActivity.class));
                    return true;
                } else if (url.startsWith(NATIVE_PAGE_URL)) {
                    context.startActivity(new Intent(context, NativePageActivity.class));
                    return true;
                } else {
    //                context.startActivity(new Intent(context, FlutterTwoPageActivity.class));
                    return false;
                }
            } catch (Throwable t) {
                return false;
            }
        }
    

    还有记得修改调起的Flutter页面是'second',因为demo中只有它才有传回数据,实现原理这里我简单描述,不详细讲了,就是flutter在关闭页面的时候,传回数据,如下

    class SecondRouteWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Second Route"),
          ),
          body: Center(
            child: RaisedButton(
              onPressed: () {
                // Navigate back to first route when tapped.
    
                BoostContainerSettings settings =
                    BoostContainer.of(context).settings;
                FlutterBoost.singleton.close(settings.uniqueId,
                    result: {"result": "data from second"});
              },
              child: Text('Go back with result!'),
            ),
          ),
        );
      }
    }
    

    跟踪关闭代码逻辑,通过之前建立的通信通道,传过去相关方法,即closePage,原生接收到之后的逻辑代码处理如下(类文件路径flutterProject/flutter_boost/android/src/main/java/com/idlefish/flutterboost/FlutterBoost.java):

     case "closePage":
                    {
                        try {
                            String uniqueId = methodCall.argument("uniqueId");
                            Map<String,Object> resultData = methodCall.argument("result");
                            Map<String,Object> exts = methodCall.argument("exts");
    
                            mManager.closeContainer(uniqueId, resultData,exts);
                            result.success(true);
                        }catch (Throwable t){
                            result.error("close page error",t.getMessage(),t);
                        }
                    }
    

    追踪closeContainer,一直追踪到BoostFlutterActivity.java的finishContainer方法,如下

    @Override
        public void finishContainer(Map<String,Object> result) {
            if(result != null) {
                FlutterBoost.setBoostResult(this,new HashMap<>(result));
                finish();
            }else{
                finish();
            }
        }
    

    再跟踪下去,逻辑很明朗了就不详细讲了


    第二种情况:flutter-native
    闲鱼的混合栈方案里,每个flutter都有自己的独立原生宿主View,所以回调也得依赖原生

    原生我们知道生命周期里就有回调方法,即onActivityResult方法,但是Flutter并没有该方法,闲鱼的混合框架里也并没有专门把这个生命周期给抽出来,本人更倾向于把这个给抽出来,这样框架也比较清晰。不过现在很多原生业务都已经很少用这种方式进行页面传值,因为业务复杂起来,用这种方式反而更麻烦,所以原生就出现了很多eventBus类似的通信框架,所以设计混合栈框架的时候,就直接忽略,而直接用自带的flutter api来实现该功能,怎么实现的?继续看

    先看下invokeMethod这个方法,原生和flutter都会有个回调函数,flutter页面拿到目标页的数据传回就是采用该方法,接下来咱们去看flutter页面打开native页面开始看起,类路径flutterProject/flutter_boost/example/lib/simple_page_widgets.dart,关键代码如下:

    InkWell(
                child: Container(
                    padding: const EdgeInsets.all(8.0),
                    margin: const EdgeInsets.all(8.0),
                    color: Colors.yellow,
                    child: Text(
                      'open native page',
                      style: TextStyle(fontSize: 22.0, color: Colors.black),
                    )),
    
                ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
                ///例如:sample://nativePage?aaa=bbb
                onTap: () =>
                    FlutterBoost.singleton.open("sample://nativePage", urlParams: {
                      "query": {"aaa": "bbb"}
                    }).then((Map value) {
                        print(
                            "call me when page is finished. did recieve second route result $value");
                      }),
              )
    

    FlutterBoost.singleton.open 跟踪下去,发现最终调用的就是invokeMethod,
    如下

      Future<Map<dynamic,dynamic>> open(String url,{Map<dynamic,dynamic> urlParams,Map<dynamic,dynamic> exts}){
    
        Map<dynamic, dynamic> properties = new Map<dynamic, dynamic>();
        properties["url"] = url;
        properties["urlParams"] = urlParams;
        properties["exts"] = exts;
        return channel.invokeMethod<Map<dynamic,dynamic>>(
            'openPage', properties);
      }
    

    然后返回的Future,异步的回调函数,拿到原生页面的回传数据。这里的逻辑很简单,重点是原生那边怎么保存该回调,然后在关闭容器的时候进行调用 回调函数以此将数据传给Flutter

    接下来看原生对于openPage的处理,之前在讲通道的时候提过,类路径
    flutterProject/flutter_boost/android/src/main/java/com/idlefish/flutterboost/FlutterBoost.java,代码如下

      case "openPage":
                    {
                        try {
                            Map<String,Object> params = methodCall.argument("urlParams");
                            Map<String,Object> exts = methodCall.argument("exts");
                            String url = methodCall.argument("url");
                            mManager.openContainer(url, params, exts, new FlutterViewContainerManager.OnResult() {
                                @Override
                                public void onResult(Map<String, Object> rlt) {
                                    if (result != null) {
                                        result.success(rlt);
                                    }
                                }
                            });
                        }catch (Throwable t){
                            result.error("open page error",t.getMessage(),t);
                        }
                    }
                    break;
    

    重点看 mManager.openContainer方法,传入了一个回调函数,最后调用
    result.success(rlt);,数据就传回flutter页面,接下来我们跟踪去看看如何去保存该回调并 最终调用回调

    继续跟踪openContainer方法,代码如下:

    void openContainer(String url, Map<String, Object> urlParams, Map<String, Object> exts,OnResult onResult) {
            Context context = FlutterBoost.singleton().currentActivity();
            if(context == null) {
                context = FlutterBoost.singleton().platform().getApplication();
            }
    
            if(urlParams == null) {
                urlParams = new HashMap<>();
            }
    
            int requestCode = 0;
            final Object v = urlParams.remove("requestCode");
            if(v != null) {
                requestCode = Integer.valueOf(String.valueOf(v));
            }
    
            final String uniqueId = ContainerRecord.genUniqueId(url);
            urlParams.put(IContainerRecord.UNIQ_KEY,uniqueId);
            if(onResult != null) {
                mOnResults.put(uniqueId,onResult);
            }
    
            FlutterBoost.singleton().platform().openContainer(context,url,urlParams,requestCode,exts);
        }
    

    注意 这里有个mOnResults的Map类型参数,就是保存回调函数,通过每个Flutter页面容器的uniqueId做key(只有Flutter页面会建立容器),但前提是该起始容器打开的时候必须传入Key,不然就无法回调,因为找不到该回调了。这就会出现一个问题,就是我们第一个打开的Flutter页面并不是通过onePage打开的,而是直接通过Context.startActivity方法打开,那么就不会保存该回调,也就无法将目标页的数据传回起始页了,已经反馈给闲鱼官方了,本人想过几种方式,为了这个简单的功能,就破坏整体框架得不偿失,等闲鱼官方更优雅的解决方式吧

    继续这个mOnResults这个参数,验证我们的猜想,看看哪里在使用,刚刚只是写入回调函数,就找到setContainerResult这个方法,就回到刚刚说要重点讲的方法那了,关键代码如下:

    void setContainerResult(IContainerRecord record,int requestCode, int resultCode, Map<String,Object> result) {
    ....
            final OnResult onResult = mOnResults.remove(record.uniqueId());
            Debuger.log("setContainerResult uniqueId "+record.uniqueId());
            if(onResult != null) {
                Debuger.log("onResult has result");
                onResult.onResult(result);
            }
        }
    

    看看哪里有调用这个方法,发现有两处,一处就是原生的生命周期onDestroy的时候,代码如下:

        @Override
        public void onDestroy() {
            ...
            mManager.setContainerResult(this,-1,-1,null);
            ...
        }
    
    

    这个是当前页面销毁的时候,但并没有数据传回,明显不是

    ok,继续看另外追踪后的一处关键代码,
    代码如下:

    @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        ...
            mSyncer.onContainerResult(requestCode,resultCode,result);
        }
    

    ok,Flutter起始页拿到native传回的数据


    第三种情况:flutter-flutter
    这里不细讲了,因为就是第一种和第二种的逻辑区分,无非目标页不太一样,传值就是第一种情况的逻辑,拿值就是第二种情况的逻辑

    原生关于容器部分,再讲下IFlutterViewContainer.java和IContainerRecord.java 这两个类,因为容器部分主要就是围绕 这两个抽象出来的接口进行一系列的框架实现,这里面做了相当多的抽象,IContainerRecord.java比较好理解,对原生View 生命周期的部分方法抽象,例:onCreate方法,通知flutter页面可以做一些初始化工作(这里面就涉及到flutter容器部分了),还有引擎部分的部分方法抽象等

    IFlutterViewContainer.java这个类主要是用于业务代码使用的,你可以看它的实现类都是在demo当中,然后抽象出的方法都是传参,传路由路径,容器关闭时的参数回传等等

    好了原生容器的讲解就到此,应该还是遗漏了不少细节的地方,本人觉得好理解就直接过去了

    Flutter部分

    讲这一部分之前,我们得先了解个flutter的一个widget 叫做Overlay!

    了解这玩意,就能弄清楚混合栈是如何做flutter页面的栈,这个组件最大的特点就是提供了动态的在Flutter的渲染树上插入布局的特性。那岂不是很适合Toast这样的场景? 是的去Google下,发现的全是用Overlay来做Toast功能

    基于Overlay的特性,就可以用全屏非透明的Overlay,每增加一个flutter页面就增加一个包含自定义的Widget的OverlayEntry,然后覆盖在上一个OverlayEntry上,用户反正看到的只是覆盖在最顶层的OverlayEntry,如果还不能理解可以看看这篇文章

    ok,背景交代完毕,现在要去看闲鱼如何设计的这个容器及页面栈,我们就从打开第一个flutter页面作为入口开始看起。类路径:flutterProject/flutter_boost/example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/PageRouter.java,第一个打开的Flutter页面是FlutterPageActivity.java,前面在讲通道设计的时候,讲到过原生生命周期和Flutter生命周期的绑定,提到过一个抽象出来的接口IOperateSyncer.java,先从onCreate方法开始看起,经过生命周期绑定调用,生成原生容器,提取定义好的通道参数,然后经过通道通信,最后追踪到flutter的didInitPageContainer的方法处理
    (类路径flutterProject/flutter_boost/lib/container/container_coordinator.dart)

    继续跟踪,中间会经过生命周期的监听调用,最终调用_createContainerSettings方法
    (类路径flutterProject/flutter_boost/lib/container/container_coordinator.dart),代码如下

    BoostContainerSettings _createContainerSettings(
          String name, Map params, String pageId) {
        Widget page;
    
        final BoostContainerSettings routeSettings = BoostContainerSettings(
            uniqueId: pageId,
            name: name,
            params: params,
            builder: (BuildContext ctx) {
              //Try to build a page using keyed builder.
              if (_pageBuilders[name] != null) {
                page = _pageBuilders[name](name, params, pageId);
              }
    
              //Build a page using default builder.
              if (page == null && _defaultPageBuilder != null) {
                page = _defaultPageBuilder(name, params, pageId);
              }
    
              assert(page != null);
              Logger.log('build widget:$page for page:$name($pageId)');
    
              return page;
            });
    
        return routeSettings;
      }
    

    根据方法,再过一遍代码,就是flutter页面容器的参数配置,同时找到一开始注册好的page页面

    接下来跟踪原生的appear方法,同样的一阵信号传输...,最终进入flutter的ContainerCoordinator.dart类中的didShowPageContainer方法,继续跟踪,追踪到flutter_boost/lib/container/container_coordinator.dart的showContainer方法

    注意的是 flutter容器初始化的过程中做了很多兼容工作,兼容ios兼容android,毕竟两个平台的生命周期是有所差别,但最终要抽象成一样的生命周期,所以要做不少的兼容工作,例如连续2次(didInitPageContainer和didShowPageContainer)进行初始化flutter容器参数

    继续看showContainer方法,代码如下

     void showContainer(BoostContainerSettings settings) {
        if (settings.uniqueId == _onstage.settings.uniqueId) {
          _onShownContainerChanged(null, settings.uniqueId);
          return;
        }
    
        final int index = _offstage.indexWhere((BoostContainer container) =>
            container.settings.uniqueId == settings.uniqueId);
            //页面的重新显示
        if (index > -1) {
          _offstage.add(_onstage);
          _onstage = _offstage.removeAt(index);
    
          setState(() {});
    
          for (BoostContainerObserver observer in FlutterBoost
              .singleton.observersHolder
              .observersOf<BoostContainerObserver>()) {
            observer(ContainerOperation.Onstage, _onstage.settings);
          }
          Logger.log('ContainerObserver#2 didOnstage');
        } else {
        //push flutter栈
          pushContainer(settings);
        }
      }
    

    这里的逻辑很简单,重点看下pushContainer方法,代码如下

     void pushContainer(BoostContainerSettings settings) {
        assert(settings.uniqueId != _onstage.settings.uniqueId);
        assert(_offstage.every((BoostContainer container) =>
            container.settings.uniqueId != settings.uniqueId));
        //将当前页面的add
        _offstage.add(_onstage);
        //需要push的页面容器创建
        _onstage = BoostContainer.obtain(widget.initNavigator, settings);
    
        setState(() {});
        //观察者回调
        for (BoostContainerObserver observer in FlutterBoost
            .singleton.observersHolder
            .observersOf<BoostContainerObserver>()) {
          observer(ContainerOperation.Push, _onstage.settings);
        }
        Logger.log('ContainerObserver#2 didPush');
      }
    
    

    flutter的容器的创建,调用setState方法,跟随进去,发现一个东西,一般flutter页面开发都用不着,就是SchedulerBinding,这里有个文章介绍,这里我简单讲解下,我们可以想想flutter的启动流程中,肯定是有个调度节点,例如:Widget什么时候处理build,什么时候处理动画计算等,就是调度。我们如果要写框架,肯定是要对flutter的调度 得清楚,这样才能写出闲鱼这样的混合栈方案,代码如下

    @override
      void setState(VoidCallback fn) {
        Logger.log('BoostContainerManager setState');
        if (SchedulerBinding.instance.schedulerPhase ==
            SchedulerPhase.persistentCallbacks) {
            //主要在下一帧之前,做一些清理工作或者准备工作
          SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
            Logger.log('BoostContainerManager persistentCallbacks');
            _refreshOverlayEntries();
          });
        } else {
          Logger.log('BoostContainerManager '+SchedulerBinding.instance.schedulerPhase.toString());
          _refreshOverlayEntries();
        }
    
        fn();
        //return super.setState(fn);
      }
    

    如果当前调度的是SchedulerPhase.persistentCallbacks,那么就加一个回调,在persistent之后进行调用_refreshOverlayEntries方法,
    SchedulerPhase.persistentCallbacks 是处理build/layout/paint工作

    可以这么理解,SchedulerPhase.persistentCallbacks就是在搭建舞台,舞台搭建好了,那么表演者就可以上台表演了 即调用_refreshOverlayEntries方法

    继续查看_refreshOverlayEntries方法,代码如下

     void _refreshOverlayEntries() {
        final OverlayState overlayState = _overlayKey.currentState;
    
        if (overlayState == null) {
          return;
        }
    
        if (_leastEntries != null && _leastEntries.isNotEmpty) {
          for (_ContainerOverlayEntry entry in _leastEntries) {
            entry.remove();
          }
        }
    
        final List<BoostContainer> containers = <BoostContainer>[];
        containers.addAll(_offstage);
    
        assert(_onstage != null, 'Should have a least one BoostContainer');
        containers.add(_onstage);
        //一层层的entry覆盖上去
        _leastEntries = containers
            .map<_ContainerOverlayEntry>(
                (BoostContainer container) => _ContainerOverlayEntry(container))
            .toList(growable: false);
    
        overlayState.insertAll(_leastEntries);
    
        SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
          final String now = _onstage.settings.uniqueId;
          if (_lastShownContainer != now) {
            final String old = _lastShownContainer;
            _lastShownContainer = now;
            _onShownContainerChanged(old, now);
          }
          //将焦点切换至当前的BoostContainerState
          updateFocuse();
        });
      }
    

    调用OverlayState的insertAll方法,将_leastEntries 覆盖上去,push flutter页面的讲解就到这人,pop其实也一样,将当前的页面栈弹出,当然也有特殊的业务处理,例如非当前的栈弹出,而是某个flutter页弹出,这里就不细讲,逻辑还是比较清晰好理解

    其实本人在看完flutter的源码之后,对于BoostContainer.dart比较有疑问,其实是对其背后的对于Navigator和Overlay有疑问,BoostContainer要继承的是Navigator,这明明是个导航控制器,其实刚刚给出的文章里面讲得非常通俗易懂了。我自己疑问的原因主要是认为一个flutter app应该就只有一个Navigator,其实主要是flutter业务开发做多了而进去的误区。闲鱼的混合栈中的flutter页面栈管理就跟平常的flutter页面栈很不一样。其实最好的理解方式,自己写一个最简单的类似的flutter页面管理,然后再看那篇文章,就豁然开朗了。

    容器讲解就到此了,解决疑问中的第一个问题

    第一个:容器是怎么设计的?

    适配层

    适配层 只有原生才需要做相应的工作,看之前,想想如果要做适配层,要做哪些适配?
    做过flutter业务开发,肯定在软键盘上面花过不少心思去做相应的界面适配工作~
    的确,看原生代码里就有个XInputConnectionAdaptor.java的类,其实要看适配层,要花不少精力的,要弄清楚flutter的启动流程,然后重写FlutterView即XFlutterView,其实跟官方提供的FlutterView改动并不是很多


    遗留问题:
    因为 存在第一个打开的Flutter页面无法将数据传回起始页问题,
    后来又去翻了下通道的相关代码,发现有这么一个flutter向原生的pageOnStart方法,类路径flutterProject/flutter_boost/android/src/main/java/com/idlefish/flutterboost/FlutterBoost.java,代码如下

     case "pageOnStart":
                    {
                        Map<String, Object> pageInfo = new HashMap<>();
    
                        try {
                            IContainerRecord record = mManager.getCurrentTopRecord();
    
                            if (record == null) {
                                record = mManager.getLastGenerateRecord();
                            }
    
                            if(record != null) {
                                pageInfo.put("name", record.getContainer().getContainerUrl());
                                pageInfo.put("params", record.getContainer().getContainerUrlParams());
                                pageInfo.put("uniqueId", record.uniqueId());  
                            }
    
                            result.success(pageInfo);
                        } catch (Throwable t) {
                            result.error("no flutter page found!",t.getMessage(),t);
                        }
                    }
                    break;
    

    看了下代码,应该就是第一个flutter页面的打开逻辑,但是在flutter的demo中并没发现,可能是以前版本留下的

  • 相关阅读:
    微信小程序 阻止冒泡事件
    vant/weapp goodsaction 显示样式不正常问题
    微信小程序图表工具wxcharts
    webstorm 不识别 rpx 格式化出错
    小程序自定义 tabbar 以vant weapp 调试工具不显示,但是在真机显示
    小程序自定义 tabbar 以vant weapp为例
    TypeScript之环境搭建
    模块化打包工具webpack
    【纪中受难记】——Day2.感觉冤的慌
    计算机精英协会考核题 —— 第三题:斐波那契数
  • 原文地址:https://www.cnblogs.com/lichao1991/p/11719737.html
Copyright © 2011-2022 走看看