The original link: cloud.tencent.com/developer/a…

If two modules, FlutterA FlutterB, we use IO. Flutter. Embedding. Android. FlutterFragment access below to access the flutter, below, shows the FlutterA module, A separate FLutterB module is pulled, at which point the following lifecycle functions occur in sequence.

Points to note here are:

  1. FlutterAThe page does not execute the onDestoryView method after pulling up the FlutterB page, which means the View is still there.
  2. After the FLutterA page pulls up FlutterB, the onStop method is executed until FlutterB is fully visible.
  3. FlutterB falls back to FlutterA and finally goes to onDesctoryVIew

Why is there only one engine in memory after FLutterA fully pulled FlutterB?

First of all, we know FLutterA onStop () function will go to the life cycle, which can trigger FlutterActivityAndFragmentDelegate onStop methods.

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

Notice that the detachFromFlutterEngine method of flutterView is called.

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; }}Copy the code

We finally see that this.flutterEngine = null is called; The engine is released, so no matter how many Flutter modules you open in this mode, you will only end up with one engine.

FlutterB reverts to FlutterA. Why can FlutterA be saved

We note that life FlutterA will come will perform periodic function onStart, it will come to 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();
            }
        });
 }Copy the code

We noticed another attachToFlutterEngine method here, so, it is binding FlutterActivityAndFragmentDelegate. Enclosing flutterEngine, The engine is a EngineProvider that we implement that comes out new every time we need it.

public class TipEngineProvider {

    public static FlutterEngine obtain() {
        returnnew FlutterEngine(IGameApplication.getIGameApplicationContext()); }}Copy the code

Since it is a new engine, we can’t help asking why the page state of FlutterA can still be saved. For example, when the list slides to a certain position and FlutterB is opened, FlutterA is still saved in the last position of the list, unchanged.

So with that in mind, we just have to look at what’s going on in onStart with the binding engine and initialization.

First look at the initialization of the Flutter engine
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);
    }Copy the code

The above code has been omitted, leaving only the most important parts. Here we can see that in the initialization of flutterJNI, flutterJNI binds Native, and then we get the nativePlatformViewId. Here we first lay a hint and go on, and then initialize DartExecutor. DartExecutor binds to flutterJNI and initializes the FlutterRenderer. Okay, so here’s the step, and to sum up, FlutterEngine incorporates many key members.

In FlutterSurfaceView, this class inherits from the SurfaceView and implements the RenderSurface interface by calling the FlutterSurfaceEr surfaceCreated method, Finally, the surfaceCreated of flutterJNI is called, and the association between the surfaceCreated method and nativePlatformViewId is generated by calling the nativeSurfaceCreated method. The following is the source code.

//FlutterSurfaceView connectSurfaceToRenderer private voidconnectSurfaceToRenderer() {
        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."); }}Copy the code
Public void surfaceCreated(@nonnull Surface) {public void surfaceCreated(@nonnull Surface) { this.flutterJNI.onSurfaceCreated(surface); }Copy the code
//FlutterJNI onSurfaceCreated @uithRead public void onSurfaceCreated(@nonnull Surface Surface) { this.ensureRunningOnMainThread(); this.ensureAttachedToNative(); this.nativeSurfaceCreated(this.nativePlatformViewId, surface); }Copy the code

In other words, the underlying flutter eventually draws data onto the native surface via the nativePlatformViewId.

We notice that the connectSurfaceToRenderer method is called twice, first when the surface is first created, and then when it’s removed, look at the code

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(); }}Copy the code
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(); }}Copy the code

Obviously, when FlutterB returned to FlutterA, the FlutterA page was still there, so the Surface was still there. Therefore, only attachToRenderer could be called to associate the nativePlatformViewId with the Surface. So, who called attachToRenderer and when?

The answer is FlutterView’s attachToFlutterEngine method, which is called when the periodic function onStart is called. So, the question arises, is FlutterA back unchanged when the surface is unchanged? Remember, we also have a nativePlatformViewId.

There is a lot going on when we re-bind a FlutterEngine to a FlutterView, and we focus on how we re-get the nativePlatformViewId. As we know above, it is in the initialization of FlutterEngine that flutterJNI binds Native and then gets the nativePlatformViewId.

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

This is a native method

// 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 {
    return0; }}Copy the code

According to the log I typed, the nativePlatformViewId I got after FlutterB returned FlutterA was the same long as the previous one. So here’s the situation.

NativePlatformViewId and Surface have not changed, and the content rendered by FlutterA’s page has not changed naturally.

At this point, we know that The FlutterEngine does not save data itself, but only manages many members of the native flutter communication, acting as a central controller. Once it is no longer available, the Surface is disconnected from the nativePlatformViewId. The Surface will not refresh, but once it is installed on FlutterView and the surface is re-associated with the nativePlatformViewId, the surface can refresh again.