本文將以 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);
}
大致的流程就是這樣的。
最後
如果你看到了這裏,覺得文章寫得不錯就給個讚唄!歡迎大家評論討論!如果你覺得那裏值得改進的,請給我留言。一定會認真查詢,修正不足,定期免費分享技術乾貨。喜歡的小夥伴可以關注一下哦。謝謝!