Flutter混合开发框架(一)——Flutter Boost分析

要解决的问题

实现Native和Flutter的混合开发,降低native工程接入Flutter的成本。

背景

Flutter 2.0之前官方没有提供Native+Flutter的混合开发解决方案,开发者有两种实现方式。
1、单Engine模式
也就是共享Engine的实现方式,当Native启动Activity或者Controller甚至是Fragment时,都将相同的Engine与对应的控制器进行绑定。
2、多Engine模式
每开启一个页面都启动一个Flutter Engine,是用多Engine配合native的形式进行数据共享及通信。

方案对比

多Engine的实现方案要比共享Engine方案简单,但也存在两个致命问题:
1、每个Engine都会占用大量内存(Android 19M左右,iOS在14M左右)
2、Engine的启动需要一定时间,可能会造成页面的短暂性黑屏

对于共享Engine方案,同样也存在问题,在Native-Flutter-Native等多页面切换时,有时会由于状态丢失,造成页面的白屏或者黑屏

结论

最终我们还是选择使用Flutter Boost,但Engine的实现策略来进行Flutter的混合开发

Flutter Boost实现原理

从0开始写一个共享Flutter Engine的解决方案(Android)。

明确核心问题

1、多页面绑定相同Engine
2、生命周期管理
3、不同页面返回时通知Engine进行内容切换
4、建立Native-Flutter通信通道(非必要)

实现方案

1)创建承载Flutter的容器父类,Activity和Fragment

BoostFlutterActivity 和 FlutterFragment

2)为Activity/Fragment添加Flutter初始化逻辑

可以简化认为是两步:
1、创建FlutterView,将Native的SurfaceView给FlutterView进行渲染
2、初始化Flutter Engine,当有Engine时直接使用,没有时创建

Boost与Flutter原代码逻辑类似,使用FlutterActivityAndFragmentDelegate代理去处理全部逻辑。

protected void onCreate(@Nullable Bundle savedInstanceState) {
    …
    delegate = new FlutterActivityAndFragmentDelegate(this);
    delegate.onAttach(this);

    configureWindowForTransparency();
    setContentView(createFlutterView());}
        

其中,delegate.onAttach(this)是启动Engine,createFlutterView是创建FlutterNativeView

3)Delegate的初始化实现
void onAttach(@NonNull Context context) {
        ensureAlive();
        if (FlutterBoost.instance().platform().whenEngineStart() == FlutterBoost.ConfigBuilder.FLUTTER_ACTIVITY_CREATED) {
            FlutterBoost.instance().doInitialFlutter();
        }
        // When "retain instance" is true, the FlutterEngine will survive configuration
        // changes. Therefore, we create a new one only if one does not already exist.
        if (flutterEngine == null) {
            setupFlutterEngine();
        }

        // Regardless of whether or not a FlutterEngine already existed, the PlatformPlugin
        // is bound to a specific Activity. Therefore, it needs to be created and configured
        // every time this Fragment attaches to a new Activity.
        // TODO(mattcarroll): the PlatformPlugin needs to be reimagined because it implicitly takes
        //                    control of the entire window. This is unacceptable for non-fullscreen
        //                    use-cases.
        platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);


        host.configureFlutterEngine(flutterEngine);

        host.getActivity().getWindow().setFormat(PixelFormat.TRANSLUCENT);
    }

如果engine是空则直接调用setupFlutterEngine();

private void setupFlutterEngine() {
        Log.d(TAG, "Setting up FlutterEngine.");


        // Second, defer to subclasses for a custom FlutterEngine.
        flutterEngine = host.provideFlutterEngine(host.getContext());
        if (flutterEngine != null) {
            isFlutterEngineFromHost = true;
            return;
        }

        // Our host did not provide a custom FlutterEngine. Create a FlutterEngine to back our
        // FlutterView.
        Log.d(TAG, "No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
                + " this NewFlutterFragment.");
        isFlutterEngineFromHost = false;
    }

最终调用的是host的provideFlutterEngine,最终就是调用的Flutter Boost初始化时的createEngine

private FlutterEngine createEngine() {
        if (mEngine == null) {

            FlutterMain.startInitialization(mPlatform.getApplication());

            FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]);
            FlutterMain.ensureInitializationComplete(
                    mPlatform.getApplication().getApplicationContext(), flutterShellArgs.toArray());

            mEngine = new FlutterEngine(mPlatform.getApplication().getApplicationContext());
        }
        return mEngine;

    }

暂且不管缓存逻辑,到此为止Flutter Engine已经创建出来了,下一步是获取FlutterView。
Activity的createFlutterView

    @NonNull
    protected View createFlutterView() {
        return delegate.onCreateView(
                null /* inflater */,
                null /* container */,
                null /* savedInstanceState */);
    }

依旧会交给Delegate去处理相关内容,FlutterNativeView需要Native创建一个SurfaceView传递进来。

@SuppressLint("ResourceType")
    @NonNull
    View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.v(TAG, "Creating FlutterView.");
        flutterEngine.getActivityControlSurface().attachToActivity(
                host.getActivity(),
                host.getLifecycle()
        );


        mSyncer = FlutterBoost.instance().containerManager().generateSyncer(this);

        ensureAlive();
        flutterView = new XFlutterView(host.getActivity(), FlutterBoost.instance().platform().renderMode(), host.getTransparencyMode());


        flutterSplashView = new FlutterSplashView(host.getContext());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            flutterSplashView.setId(View.generateViewId());
        } else {
            // TODO(mattcarroll): Find a better solution to this ID. This is a random, static ID.
            // It might conflict with other Views, and it means that only a single FlutterSplashView
            // can exist in a View hierarchy at one time.
            flutterSplashView.setId(486947586);
        }
        flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());
        mSyncer.onCreate();
        return flutterSplashView;
    }

显然关键性代码就是:

flutterView = new XFlutterView(host.getActivity(), FlutterBoost.instance().platform().renderMode(), host.getTransparencyMode());

需要解决的问题很明确,要创建flutterView,根据初始化方法,最后调用到的是

private void init() {
    Log.v(TAG, "Initializing FlutterView");

    switch (renderMode) {
      case surface:
        Log.v(TAG, "Internally using a FlutterSurfaceView.");
        FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(getContext(), transparencyMode == FlutterView.TransparencyMode.transparent);
        renderSurface = flutterSurfaceView;
        addView(flutterSurfaceView);
        break;
      case texture:
        Log.v(TAG, "Internally using a FlutterTextureView.");
        FlutterTextureView flutterTextureView = new FlutterTextureView(getContext());
        renderSurface = flutterTextureView;
        addView(flutterTextureView);
        break;
    }

    // FlutterView needs to be focusable so that the InputMethodManager can interact with it.
    setFocusable(true);
    setFocusableInTouchMode(true);
  }

至此,FlutterView也创建完成了,Boost只是在FlutterView上套了一个FrameLayout。整体的初始化流程就完成了,当然还有事件管理器的初始化以及插件管理器的初始化,可以仿照FlutterActivity或者FlutterFragment实现。


4)共享引擎

完成此功能我们需要处理以下几个问题:
1、缓存Engine对象
2、Native LifeCycle与Flutter Lifecycle联动(路由)
3、Engine和View的重新绑定

4.1 缓存Engine

经过上面分析,我们可以直接聚焦到FlutterActivityAndFragmnetDelegate的setupFlutterEngine方法,关键语句就是:
flutterEngine = host.provideFlutterEngine(host.getContext());
最终返回的就是FlutterBoost中缓存的mEngine对象,通过之前的分析FlutterBoost的mEngine对象,是在FlutterBoost初始化时创建的。因此就实现了Engine的缓存。

4.2 Native LifeCycle和Flutter LifeCycle的联动

Engine主要通过Delegate管理的,所以生命周期的管理也应该在Delegate中实现,由Fragment或者Activity调用对应的生命周期函数。

    @Override
    protected void onStart() {
        super.onStart();
        lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
        delegate.onStart();
    }@Override
    protected void onDestroy() {
        super.onDestroy();
        delegate.onDestroyView();
        delegate.onDetach();
//        lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
    }

Activity生命周期直接委托到Delegate中处理。我们的目的最终还是要通知Engine的,所以可想而知,delegate会调用到FlutterEngine。
但是像如页面跳转,开启关闭等的生命周期,只是当前页面的关闭或者死亡,对于Flutter Engine来说也只是某个页面的生命周期而已。这个就不同于单引擎了,这里应该使用代理的形式,重新分配生命周期,关注FlutterView的生命周期处理。

页面返回回来时,我们需要将引擎重新绑定回当前的Activity,处理在onResume中

void onResume() {
        mSyncer.onAppear();

        Log.v(TAG, "onResume()");
        ensureAlive();
        flutterEngine.getLifecycleChannel().appIsResumed();

        flutterEngine.getActivityControlSurface().attachToActivity(
                host.getActivity(),
                host.getLifecycle()
        );
    }

通过attachToActivy重新进行了绑定。之后就是当页面退出时的处理。


    void onPause() {
        Log.v(TAG, "onPause()");

        ensureAlive();
        mSyncer.onDisappear();
        flutterEngine.getLifecycleChannel().appIsInactive();
    }


    void onStop() {
        Log.v(TAG, "onStop()");
        ensureAlive();
    }
    
    void onDestroyView() {
        Log.v(TAG, "onDestroyView()");
        mSyncer.onDestroy();

        ensureAlive();

        flutterView.release();
    }


    void onDetach() {
        Log.v(TAG, "onDetach()");
        ensureAlive();


        // Null out the platformPlugin to avoid a possible retain cycle between the plugin, this Fragment,
        // and this Fragment's Activity.
        if (platformPlugin != null) {
            platformPlugin.destroy();
            platformPlugin = null;
        }

        Utils.fixInputMethodManagerLeak(host.getActivity());

    }

可以对比Flutter的FlutterActivity,在onStop时并没有进行FlutteEngine的停止处理,在onDetach的时候也没有进行Engine的释放,保证Engine的活跃状态。
但是在onDestoryView时,进行了flutterView.release();操作。

4.3 Flutter Engine和View的重新绑定

简单思考一下,我们做绑定主要在两个位置,一个是新建页面的时候,需要将引擎与新页面的View进行绑定,第二个是在返回当前页面时,需要将引擎和返回的页面View进行绑定。

绑定也有两种方式,
一种是每次都新建一个SurfaceView,不同页面对应不同的SurfaceView

此方案需要FlutterEngine可以绑定和解绑对应的FlutterView

另外一种是,同一个SurfaceView多个Activity共享

此方案需要SurfaceView可以作为共享资源,多个Activity或者Fragment中使用。

经过思考,选择方案一会更好处理一些,因为方案二虽然可以使用一个surfaceview在不同的载体页进行加载,但是加载过程的转场动画以及数据的处理,都会有一些问题,需要做到SurfaceView的耕种状态要相对Activity独立,但是又要与整个APP的生命周期进行联动。

介绍一下具体实现:

场景1、开启新的页面是,调用的是delegate.createView,与Flutter源代码一致,CreateView时我们需要创建一个FlutterSurfaceView进行承载

private void init() {
    Log.v(TAG, "Initializing FlutterView");

    switch (renderMode) {
      case surface:
        Log.v(TAG, "Internally using a FlutterSurfaceView.");
        FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(getContext(), transparencyMode == FlutterView.TransparencyMode.transparent);
        renderSurface = flutterSurfaceView;
        addView(flutterSurfaceView);
        break;
      case texture:
        Log.v(TAG, "Internally using a FlutterTextureView.");
        FlutterTextureView flutterTextureView = new FlutterTextureView(getContext());
        renderSurface = flutterTextureView;
        addView(flutterTextureView);
        break;
    }

    // FlutterView needs to be focusable so that the InputMethodManager can interact with it.
    setFocusable(true);
    setFocusableInTouchMode(true);
  }

FlutterSurfaceView继承自SurfaceView,内部会处理flutterRenderer的相应处理逻辑。

然后将View与引擎绑定,FlutterView的内容展示主要靠FlutterRender,我们只需要在页面展示出来在之后进行调用FlutterView.attachRender即可,看一下Boost的处理。

Boost跟我们分析的一致,并且引入了Syncer和Recorder两个概念,进行图形的处理。

delegate:

@SuppressLint("ResourceType")
    @NonNull
    View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        ……
        mSyncer.onCreate();
        ……
    }
    
    void onResume() {
        mSyncer.onAppear();

        Log.v(TAG, "onResume()");
        ensureAlive();
        flutterEngine.getLifecycleChannel().appIsResumed();

        flutterEngine.getActivityControlSurface().attachToActivity(
                host.getActivity(),
                host.getLifecycle()
        );
    }

Syncer:

    @Override
    public void onAppear() {
        Utils.assertCallOnMainThread();

        if (mState != STATE_CREATED && mState != STATE_DISAPPEAR) {
            Debuger.exception("state error");
        }

        mState = STATE_APPEAR;

        mManager.pushRecord(this);

        mProxy.appear();

        mContainer.getBoostFlutterView().onAttach();

    }

此处主要做了两件事:
1、mProxy.appear()

        private void appear() {
            invokeChannelUnsafe("didShowPageContainer",
                    mContainer.getContainerUrl(),
                    mContainer.getContainerUrlParams(),
                    mUniqueId
            );
            //Debuger.log("didShowPageContainer");

            mState = STATE_APPEAR;
        }

通过methodchannel,根据对应的containerUrl,调用起对应的Flutter页面。
2、mContainer.getBoostFlutterView().onAttach()
也就是刚才提到的,FlutterView的attachRender的处理,只是这里相较于原生Flutter,需要处理一下Engine内容。

    public void onAttach() {
        Debuger.log("BoostFlutterView onAttach");

        flutterView.attachToFlutterEngine(mFlutterEngine);

    }

简化看一下XFlutterView的attachToFlutterEngine方法。

public void attachToFlutterEngine(
          @NonNull FlutterEngine flutterEngine
  ) {
    ……
    this.flutterEngine = flutterEngine;

    FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();
    isFlutterUiDisplayed = flutterRenderer.isDisplayingFlutterUi();
    renderSurface.attachToRenderer(flutterRenderer);
    flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
    this.flutterEngine.getPlatformViewsController().attachToView(this);



    ……

    // Push View and Context related information from Android to Flutter.
    sendUserSettingsToFlutter();
    sendLocalesToFlutter(getResources().getConfiguration());
    sendViewportMetricsToFlutter();

    // Notify engine attachment listeners of the attachment.
    for (FlutterView.FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
      listener.onFlutterEngineAttachedToFlutterView(flutterEngine);
    }
    
  }

只看我们现在关心的几行代码,保存引擎对象,将View与Renderer绑定,这里的renderSurface就是FlutterView。

初始化过程已经完成,下面是返回逻辑的绑定处理
由上面分析可知,对应的appear方法调用是在Activity或者Fragment的onResume中调用的,所以重新绑定逻辑可以暂且不管了,主要关心一下,关闭时,如何解决的。
直接定位到Delegate中,

    void onPause() {
        Log.v(TAG, "onPause()");

        ensureAlive();
        mSyncer.onDisappear();
        flutterEngine.getLifecycleChannel().appIsInactive();
    }


    void onStop() {
        Log.v(TAG, "onStop()");
        ensureAlive();


    }

    void onDestroyView() {
        Log.v(TAG, "onDestroyView()");
        mSyncer.onDestroy();

        ensureAlive();

        flutterView.release();
    }

很明显了,依次调用的是mSyncer.onDisappear();mSyncer.onDestroy();flutterView.release();
到这里大家应该会有个问题了,既然Engine用的是同一个,onDestoryView的调用时间要晚于onResume的调用,那么此时再调用Release会不会对刚才的Renderer的处理有影响呢?
分别看一下:

    @Override
    public void onDisappear() {
        Utils.assertCallOnMainThread();

        if (mState != STATE_APPEAR) {
            Debuger.exception("state error");
        }

        mState = STATE_DISAPPEAR;

        mProxy.disappear();
        if(getContainer().getContextActivity().isFinishing()) {
            mProxy.destroy();
        }

        mContainer.getBoostFlutterView().onDetach();

        mManager.popRecord(this);
    }

mProxy.disappear();主要工作还是通过MethodChannel通知Flutter层,进行didDisappearPageContainer操作。
mContainer.getBoostFlutterView().onDetach();最终调用到:

public void detachFromFlutterEngine() {
    Log.d(TAG, "Detaching from a FlutterEngine: " + flutterEngine);
    if (!isAttachedToFlutterEngine()) {
      Log.d(TAG, "Not attached to an engine. Doing nothing.");
      return;
    }

    // Notify engine attachment listeners of the detachment.
    for (FlutterView.FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
      listener.onFlutterEngineDetachedFromFlutterView();
    }

    // Disconnect the FlutterEngine's PlatformViewsController from the AccessibilityBridge.
    flutterEngine.getPlatformViewsController().detachAccessibiltyBridge();
    flutterEngine.getPlatformViewsController().detachFromView();
    // Disconnect and clean up the AccessibilityBridge.
    accessibilityBridge.release();
    accessibilityBridge = null;

    // Inform the Android framework that it should retrieve a new InputConnection
    // now that the engine is detached. The new InputConnection will be null, which
    // signifies that this View does not process input (until a new engine is attached).
    // TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin

    // Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.


    FlutterRenderer flutterRenderer = flutterEngine.getRenderer();
    isFlutterUiDisplayed = false;
    flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
    flutterRenderer.stopRenderingToSurface();
    flutterRenderer.setSemanticsEnabled(false);
    renderSurface.detachFromRenderer();

    flutterEngine = null;

  }

主要方法就是进行Engine和View的强制解绑。在onStop时,就已经进行了Engine的解绑处理,因此不会有相互影响的问题。那destory的时候做了什么呢?
1、与Flutter同步生命周期

    private void destroy() {
            if (mState < STATE_DESTROYED) {
                invokeChannel("willDeallocPageContainer",
                        mContainer.getContainerUrl(),
                        mContainer.getContainerUrlParams(),
                        mUniqueId
                );
                //Debuger.log("willDeallocPageContainer");

                mState = STATE_DESTROYED;
            }
        }

2、推出栈,并且将返回数据返回

        mManager.removeRecord(this);

        mManager.setContainerResult(this,-1,-1,null);
    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);
        }
    }

mSyncer的release:

public void release(){
    if(textInputPlugin!=null){
      textInputPlugin.release();
    }
  }

至此Engine和View的绑定就完成了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值