实现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的混合开发
从0开始写一个共享Flutter Engine的解决方案(Android)。
1、多页面绑定相同Engine
2、生命周期管理
3、不同页面返回时通知Engine进行内容切换
4、建立Native-Flutter通信通道(非必要)
BoostFlutterActivity 和 FlutterFragment
可以简化认为是两步:
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
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实现。
完成此功能我们需要处理以下几个问题:
1、缓存Engine对象
2、Native LifeCycle与Flutter Lifecycle联动(路由)
3、Engine和View的重新绑定
经过上面分析,我们可以直接聚焦到FlutterActivityAndFragmnetDelegate
的setupFlutterEngine方法,关键语句就是:
flutterEngine = host.provideFlutterEngine(host.getContext());
最终返回的就是FlutterBoost中缓存的mEngine对象,通过之前的分析FlutterBoost的mEngine对象,是在FlutterBoost初始化时创建的。因此就实现了Engine的缓存。
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();
操作。
简单思考一下,我们做绑定主要在两个位置,一个是新建页面的时候,需要将引擎与新页面的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的绑定就完成了。