Flutter 混合棧複用原理

本文將以 Android 視角,來扒一扒 Flutter 混合棧的前世今生。其實也就是從 1.0 正式發佈到現在 1.9 版本的一些變更。

本文將會從以下幾個方面來分析:

  • 什麼是 Flutter 混合棧
  • 爲什麼會產生問題
  • 處理混合棧的相關框架
  • 官方的處理方案

一、什麼是混合棧

一個新的技術的興起,必然是一步一步向前的,Flutter 在成熟的 Android、iOS 的大環境下的生存,必要與 native 融合。

一些成熟的 APP ,如果想使用 Flutter 技術,必然不會完全使用 Flutter 重寫,那樣的成本太高,所以 Native + Flutter 的項目就出現了,Native 頁面與 Flutter 頁面共存,甚至交替呈現在用戶手機上。

那什麼是混合棧呢?

相信你已經有了答案,下面我們用 Android 的視角來重新審視一下混合棧。

我們使用 flutter boost 框架提供的 demo 來看一下混合棧的效果(debug包)

二、爲什麼會產生問題

知道了混合棧問題,接下來我們要考慮爲什麼會出現這樣的問題。

目標是什麼

現狀是一個 Activity 中存在多個 Flutter Page,我們想要的是 Flutter Page 與 Android Activity 可以一一對應,這樣便可以簡單的將兩個頁面棧合併成一個棧,並且在性能上不會產生影響,這就是我們處理混合棧的目標。

如上圖混合棧所示,Android 中以 FlutterView 承載 Flutter 的展示,默認情況下不同 FlutterView 創建了不同的 engine。如果一個 Flutter Page 對應一個 Activity, 這就導致了資源的多次重複創建和內存的不共享。

如果極端情況下,Native => Flutter => Native => … => Native => Flutter 會是什麼情況呢?後果不堪設想,當然,可以從業務上避免這樣的問題,但是作爲框架的制定者,必須要考慮到這樣的問題。

接下來我們看如何解決問題。需要從原理入手,需要閱讀源碼

Flutter 架構圖

從最經典的 Flutter 架構圖入手

從圖中可以看到 3 層架構,每層提供了不同的能力

  • Framework:這一層提供了Flutter 的常用控件,也提供了用於繪製的一些準備工作,詳見Flutter 從加載到顯示
  • Engine:這一層提供了Flutter的2D圖形渲染庫Skia和用於垃圾收集的面嚮對象語言的 Dart VM,並將它們託管在Shell中。這裏也提供了 Platform Channels 等與 Native 交互的 API。可以參考這篇文章The Engine architecture
  • Embedder:這一層提供了不同平臺的嵌入 API 如 Andorid、iOS。使得 Flutter 可以運行在不同的嵌入式平臺。

在 Android 中的 Flutter

我們看一下創建 Flutter 項目中自動生成的 Android 工程,內部使用的是 io.flutter.app 包內的 FlutterActivity,暫且不討論 io.flutter.embedding.android 包相關內容,後面會分析。

在 Android 中使用 Flutter 是這樣的

如上圖所示,圖中羅列了一些類,這裏講解一下

  • FlutterView:Android 中用於展示 Flutter 頁面的控件,一個 FlutterView 中可以展示多個 Flutter Widget,官方的註釋是:"An Android View containing a Flutter app"。內部包含了 FlutterNativeView。
  • FlutterNativeView:每個 FlutterView 中包含一個FlutterNativeView,該類的主要作用是 Android 與 Flutter 之間的通信,保持生命週期與 Activity 及 FlutterView 同步。內部包含了 DartExecutor。
  • DartExecutor:根據名稱我們可以瞭解,這個類就是 Dart VM 相關處理 Java 與 C/C++ 的調用。官方的註釋是:"Configures, bootstraps, and starts executing Dart code"

其實 Flutter 的運行機制就是這樣的,Flutter 由 FlutterView 呈現。每一個 FlutterView,會對應的創建一個 FlutterNativeView,創建一個 Dart VM。而不同 FlutterView 內部的 Dart 代碼內存無法共享。

源碼閱讀

建議讀者閱讀一下代碼

io.flutter.app.FlutterActivit
Io.flutter.app.FlutterActivityDelegate
io.flutter.view.FlutterMain
io.flutter.view.FlutterView
io.flutter.view.FlutterNativeView
io.flutter.embedding.engine.dart.DartExecutor
io.flutter.embedding.engine.FlutterJNI

我們能做什麼

通過上面的介紹,應該依然瞭解 Flutter 在 Android 上的運行機制,如果閱讀了源碼應該有更深的印象。在這樣的運行機制中,我們能做什麼呢?其實 Flutter Boost 框架給了我們解決的思路,但是這裏我還是希望讀者能自己來想想,如果是你自己來實現,該怎麼做呢?

三、混合棧處理框架

來看看社區爲我們提供的方案吧。

網上的文章很多,在文章最後提供一些鏈接,有興趣的讀者可以都看一下。這裏僅以 Flutter Boost 爲例,來分析一下。

Flutter Boost 的處理方案可以分成兩個版本,使用了兩種方案,可以作爲混合棧方案的兩種代表思路。

Flutter Boost 0.0.4+版本

alibaba/flutter_boost 0.0.420
github.com/alibaba/flu…

FlutterView 複用方案

框架中從 FlutterActivityDelegate#onCreate 方法入手,重寫創建 FlutterView 的流程,複用 FlutterView 來實現。

我們先來看一下這個版本的接入方式,在 Application 中初始化

    FlutterBoostPlugin.init(new IPlatform() {
        ...
          
        /**
         * 獲取應用入口的Activity,這個Activity在應用交互期間應該是一直在棧底的
         * 提供給框架內部,後續創建FlutterView用
         */
        @Override
        public Activity getMainActivity() {
            if (MainActivity.sRef != null) {
                return MainActivity.sRef.get();
            }
            return null;
        }
        ...
    });

我們來看一下 FlutterActivityDelegate#onCreate 是如何處理的。

# FlutterActivityDelegate.java    
		@Override
    public void onCreate(Bundle savedInstanceState) {
				...
				// 獲取 flutterView
        flutterView = viewFactory.createFlutterView(activity);
        // 爲空則創建,否則直接使用
        if (flutterView == null) {
        		// 創建 FlutterView 的同時,創建 FlutterNative、DartExecutor
            FlutterNativeView nativeView = viewFactory.createFlutterNativeView();
            flutterView = new FlutterView(activity, null, nativeView);
            flutterView.setLayoutParams(matchParent);
            activity.setContentView(flutterView);
            launchView = createLaunchView();
            if (launchView != null) {
                addLaunchView();
            }
        }
      ...
    }

createFlutterView 的實現是在 FlutterActivity 中的。

# FlutterActivity.java
    @Override
    public FlutterView createFlutterView(Context context) {
        return null;
    }

而 Flutter Boost 框架重寫了 createFlutterView 方法

# BoostFlutterActivity.java
		public FlutterView createFlutterView(Context context) {
        return FlutterBoostPlugin.viewProvider().createFlutterView(this);
    }

真正返回的是這裏構造的

# FlutterViewProvider.java
		@Override
    public BoostFlutterView createFlutterView(IFlutterViewContainer container) {
    		// 在 Application 中提供的 mPlatform,將緩存的 Activity 提供給這裏
        Activity activity = mPlatform.getMainActivity();

        if(activity == null) {
            Debuger.log("create Flutter View not with MainActivity");
            activity = container.getActivity();
        }
        // 如果爲 null 則創建然後緩存,有值則直接使用
        if (mFlutterView == null) {
        		// BoostFlutterView 繼承自 FlutterView
            mFlutterView = new BoostFlutterView(activity, null, createFlutterNativeView(container));
        }
        return mFlutterView;
    }

這樣就複用了 FlutterView。

複用 FlutterView 需要在 Activity 切換的時候進行 view 的 attach、detach,該版本使用了截圖方案。

Flutter Boost 0.1.5+版本

FlutterEngine 複用

從接入方式來看,在 Application 中初始化,提供了一個回調方法,提供 BoostFlutterEngine 即是 FlutterEngine 實例。

    FlutterBoost.init(new Platform() {
      	...
        @Override
        public IFlutterEngineProvider engineProvider() {
            return new BoostEngineProvider() {
                @Override
                public BoostFlutterEngine createEngine(Context context) {
                    return new BoostFlutterEngine(context, new DartExecutor.DartEntrypoint(
                            context.getResources().getAssets(),
                            FlutterMain.findAppBundlePath(context),
                            "main"), "/");
                }
            };
        }
      	...
    });

在看 BoostFlutterActivity 實現

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
				...
        mFlutterEngine = createFlutterEngine();
        mFlutterView = createFlutterView(mFlutterEngine);
        setContentView(mFlutterView);
				...
    }
		...
    protected BoostFlutterEngine createFlutterEngine(){
        return FlutterBoost.singleton().engineProvider().provideEngine(this);
    }

真正返回的是這裏構造的

 # com.idlefish.flutterboost.BoostEngineProvider
 		@Override
    public BoostFlutterEngine provideEngine(Context context) {
        Utils.assertCallOnMainThread();

        if (mEngine == null) {
            FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]);
            FlutterMain.ensureInitializationComplete(
                    context.getApplicationContext(), flutterShellArgs.toArray());
						// 這裏調用的方法就是初始化時重寫內容
            mEngine = createEngine(context.getApplicationContext());

            final IStateListener stateListener = FlutterBoost.sInstance.mStateListener;
            if(stateListener != null) {
                stateListener.onEngineCreated(mEngine);
            }
        }
        return mEngine;
    }

該版本的處理方式與 io.flutter.embedding 包中處理方式基本相同,使用了 flutter.jar 中的 FlutterSurfaceView 和 FlutterTextureView 來最終的展示 Flutter 界面。接下來我們看看 Flutter 官方爲我們提供的方式。

官方提供的方式

Experimental: Adding Flutter to Android
github.com/flutter/flu…

通過文檔我們就可以知道,該方式和 Flutter 項目自動生成的 Android 工程不同,使用的大多爲 io.flutter.embedding 包中的內容,並且提供了使用緩存的 FlutterEngine 的方式。使用了 FlutterEngineCache 類進行對 FlutterEngine 的 key-value 緩存。

在 flutter.jar 中可以看到,共存了兩個 FlutterActivity、FlutterView 等。

FlutterActivity

io.flutter.app.FlutterActivity
io.flutter.embedding.FlutterActivity

FlutterView

io.flutter.view.FlutterView
io.flutter.embedding.FlutterView

這裏簡單介紹一下

# io.flutter.embedding.FlutterActivity.java
	@Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    delegate = new FlutterActivityAndFragmentDelegate(this);
    // 創建 Flutter
    // 提供 FlutterEngine 並綁定到 Activity、創建並配置 PlatformPlugin、
    delegate.onAttach(this);
    ...
    // 創建 Flutter 的 View 並綁定到 Activity
    setContentView(createFlutterView());
    ...
  }
	@NonNull
  private View createFlutterView() {
    return delegate.onCreateView(
        null /* inflater */,
        null /* container */,
        null /* savedInstanceState */);
  }

加載到 Activity 上的 View 是如何創建的

# io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.java  
	@NonNull
  View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
		...
		// 創建了一個 FlutterView
    flutterView = new FlutterView(host.getActivity(), host.getRenderMode(), host.getTransparencyMode());
		// 創建了一個 FlutterSplashView,包裹了一個 FlutterView 的View
    flutterSplashView = new FlutterSplashView(host.getContext());
		...
		// 在 FlutterView 展示第一幀之前,先展示提供的 splashScreen
    flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());

    return flutterSplashView;
  }

看一下 FlutterView 是如何實現的

  private void init() {
		// 根據 renderMode 模式來選擇使用 SurfaceView/TextureView,解決了 SurfaceView 對動畫支持差的詬病
    switch (renderMode) {
      case surface:
        FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(getContext(), transparencyMode == TransparencyMode.transparent);
        renderSurface = flutterSurfaceView;
        addView(flutterSurfaceView);
        break;
      case texture:
        FlutterTextureView flutterTextureView = new FlutterTextureView(getContext());
        renderSurface = flutterTextureView;
        addView(flutterTextureView);
        break;
    }
    setFocusable(true);
    setFocusableInTouchMode(true);
  }

大致的流程就是這樣的。

最後

如果你看到了這裏,覺得文章寫得不錯就給個讚唄!歡迎大家評論討論!如果你覺得那裏值得改進的,請給我留言。一定會認真查詢,修正不足,定期免費分享技術乾貨。喜歡的小夥伴可以關注一下哦。謝謝!


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章