­

flutter单引擎方案

假设有两个模块,FlutterA,FlutterB,我们利用io.flutter.embedding.android.FlutterFragment下面的接入方式来接入flutter的话,下图,展示的是FlutterA模块,拉起一个独立的FLutterB模块,此时,会依照顺序发生下面的生命周期函数。

这里需要注意的点有:

  1. FlutterA页面在拉起FlutterB页面之后,没有执行onDestoryView方法,也就是说View还在。
  2. FLutterA页面拉起FlutterB之后,一直到FlutterB完全可见之后,才执行了onStop方法。
  3. FlutterB回退到FlutterA之后,最终走到了onDesctoryVIew

FLutterA完全拉起FlutterB之后,为什么内存中只有一个引擎了。

首先,我们知道FLutterA一定会走到其生命周期函数onStop,进而会触发FlutterActivityAndFragmentDelegate的onStop方法。

//FlutterActivityAndFragmentDelegate  void onStop() {          Log.v("FlutterActivityAndFragmentDelegate", "onStop()");          this.ensureAlive();          this.flutterEngine.getLifecycleChannel().appIsPaused();          this.flutterView.detachFromFlutterEngine();  }

我们注意到,这里调用了flutterView的detachFromFlutterEngine方法。

public void detachFromFlutterEngine() {          Log.d("FlutterView", "Detaching from a FlutterEngine: " + this.flutterEngine);          if (!this.isAttachedToFlutterEngine()) {              Log.d("FlutterView", "Not attached to an engine. Doing nothing.");          } else {              Iterator var1 = this.flutterEngineAttachmentListeners.iterator();                while(var1.hasNext()) {                  FlutterView.FlutterEngineAttachmentListener listener = (FlutterView.FlutterEngineAttachmentListener)var1.next();                  listener.onFlutterEngineDetachedFromFlutterView();              }                this.flutterEngine.getPlatformViewsController().detachAccessibiltyBridge();              this.accessibilityBridge.release();              this.accessibilityBridge = null;              this.textInputPlugin.getInputMethodManager().restartInput(this);              this.textInputPlugin.destroy();              FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();              this.didRenderFirstFrame = false;              flutterRenderer.removeOnFirstFrameRenderedListener(this.onFirstFrameRenderedListener);              flutterRenderer.detachFromRenderSurface();              this.flutterEngine = null;          }  }

我们最终看到,调用了this.flutterEngine = null;,引擎被释放了,所以,只要是这种模式,你无论开多少个Flutter模块,最后都只会有一个引擎。

FlutterB回退到FlutterA,FlutterA的状态为什么可以继续保存

我们注意到生命FlutterA会来时会执行周期函数onStart,它又会走到FlutterActivityAndFragmentDelegate的onStart。

void onStart() {          Log.v("FlutterActivityAndFragmentDelegate", "onStart()");          this.ensureAlive();          (new Handler()).post(new Runnable() {              public void run() {                  Log.v("FlutterActivityAndFragmentDelegate", "Attaching FlutterEngine to FlutterView.");                  FlutterActivityAndFragmentDelegate.this.flutterView.attachToFlutterEngine(FlutterActivityAndFragmentDelegate.this.flutterEngine);                  FlutterActivityAndFragmentDelegate.this.doInitialFlutterViewRun();              }          });   }

我们注意到这里又一个attachToFlutterEngine的方法,那么,它绑定的是FlutterActivityAndFragmentDelegate.this.flutterEngine,这个引擎是我们实现的一个EngineProvider每次需要的时候new出来的。

public class TipEngineProvider {        public static FlutterEngine obtain() {          return new FlutterEngine(IGameApplication.getIGameApplicationContext());      }  }

那既然是new出来的一个引擎,我们不禁要问了,为什么回退回来,FlutterA的页面状态还可以保存,比如,列表滑动到一定的位置,打开FlutterB,在回退回来,FlutterA还保存在列表滑动到的最后位置,没变化。

带着这个问题,我们只能去看看,onStart中绑定引擎,及初始化引起的过程中,做了一些什么?

首先看看Flutter引擎的初始化
public FlutterEngine(@NonNull Context context) {          this.flutterJNI.addEngineLifecycleListener(this.engineLifecycleListener);          this.attachToJni();          this.dartExecutor = new DartExecutor(this.flutterJNI, context.getAssets());          this.dartExecutor.onAttachedToJNI();          this.renderer = new FlutterRenderer(this.flutterJNI);      }

以上代码有省略,只保留至关重要的部分。这里我们看到,在FlutterEngine的初始化中,flutterJNI绑定了Native,就在此时拿到了nativePlatformViewId,这里我们先埋下一个伏笔,继续走,然后,初始化了DartExecutor,DartExecutor绑定到了flutterJNI,然后初始化了FlutterRenderer。好,这里先看到这一步,总结一下,FlutterEngine融合了很多关键的成员。

在FlutterSurfaceView中,这个类继承至SurfaceView,实现了RenderSurface接口,在通过调用FlutterRenderer的surfaceCreated方法,最终会调用到flutterJNI的surfaceCreated,最终通过调用nativeSurfaceCreated方法和nativePlatformViewId产生了关联,以下是源码部分。

//FlutterSurfaceView 中的connectSurfaceToRenderer方法  private void connectSurfaceToRenderer() {          if (this.flutterRenderer != null && this.getHolder() != null) {              this.flutterRenderer.surfaceCreated(this.getHolder().getSurface());          } else {              throw new IllegalStateException("connectSurfaceToRenderer() should only be called when flutterRenderer and getHolder() are non-null.");          }  }
//FlutterRenderer 中的surfaceCreated方法  public void surfaceCreated(@NonNull Surface surface) {          this.flutterJNI.onSurfaceCreated(surface);  }
//FlutterJNI中的onSurfaceCreated方法  @UiThread      public void onSurfaceCreated(@NonNull Surface surface) {          this.ensureRunningOnMainThread();          this.ensureAttachedToNative();          this.nativeSurfaceCreated(this.nativePlatformViewId, surface);      }

换句话说,最终flutter底层通过nativePlatformViewId将数据绘制到了原生的surface上。

我们注意到,connectSurfaceToRenderer方法有两次调用的时机,第一次是在surface初次create的时候,还有一次是主动去掉,看代码

private FlutterSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, boolean renderTransparently) {          super(context, attrs);          this.isSurfaceAvailableForRendering = false;          this.isAttachedToFlutterRenderer = false;          this.onFirstFrameRenderedListeners = new HashSet();          this.surfaceCallback = new Callback() {              public void surfaceCreated(@NonNull SurfaceHolder holder) {                  Log.v("FlutterSurfaceView", "SurfaceHolder.Callback.surfaceCreated()");                  FlutterSurfaceView.this.isSurfaceAvailableForRendering = true;                  if (FlutterSurfaceView.this.isAttachedToFlutterRenderer) {                      FlutterSurfaceView.this.connectSurfaceToRenderer();                  }    }
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {          Log.v("FlutterSurfaceView", "Attaching to FlutterRenderer.");          if (this.flutterRenderer != null) {              Log.v("FlutterSurfaceView", "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one.");              this.flutterRenderer.detachFromRenderSurface();          }          this.flutterRenderer = flutterRenderer;          this.isAttachedToFlutterRenderer = true;          if (this.isSurfaceAvailableForRendering) {              Log.v("FlutterSurfaceView", "Surface is available for rendering. Connecting FlutterRenderer to Android surface.");              this.connectSurfaceToRenderer();          }        }

很显然,从FlutterB回到FlutterA的时候,因为FlutterA的页面还在,所以,surface还在,所以,只可能调用attachToRenderer,才能够将nativePlatformViewId与surface进行关联。那么,attachToRenderer是谁在什么时候调用的呢?

答案就是FlutterView的attachToFlutterEngine方法,它就是在什么周期函数onStart时调用的,那么,问题来了,surface没有变化,时决定FlutterA回来不变的唯一原因吗?别忘记了,我们还有一个nativePlatformViewId呢。

当我们重新给一个FlutterEngine给FlutterView绑定时,发生了很多的过程,我们把焦点放到nativePlatformViewId时如何重新拿到的。上面我们已经知道是在FlutterEngine的初始化中,flutterJNI绑定了Native,就在此时拿到了nativePlatformViewId。

 @UiThread      public void attachToNative(boolean isBackgroundView) {          this.ensureRunningOnMainThread();          this.ensureNotAttachedToNative();          this.nativePlatformViewId = this.nativeAttach(this, isBackgroundView);      }

这是一个native的方法

// Called By Java    static jlong AttachJNI(JNIEnv* env,                         jclass clazz,                         jobject flutterJNI,                         jboolean is_background_view) {    fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI);    auto shell_holder = std::make_unique<AndroidShellHolder>(        FlutterMain::Get().GetSettings(), java_object, is_background_view);    if (shell_holder->IsValid()) {      return reinterpret_cast<jlong>(shell_holder.release());    } else {      return 0;    }  }

根据我打的日志来观察,nativePlatformViewId在FlutterB返回FlutterA之后,此时拿到的这个nativePlatformViewId与之前的那个是一个long。因此,现在的情况是。

nativePlatformViewId和surface都没有变化,还是然来的那个,自然而然FlutterA的页面渲染出来的内容没有发生变化。

至此,我们知道FlutterEngine,其实本身没有保存数据,它只是管理着原生与flutter通讯的众多成员,起到了一个中心控制者的作用,一旦它不再了,surface与nativePlatformViewId就断开了,surface将不会在刷新,一旦它被安装到了FlutterView上,surface与nativePlatformViewId重新建立起了关联,surface又可以刷新了