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又可以刷新了