Flutter Android 端 FlutterEngine Java 相關流程原始碼分析

工匠若水發表於2021-08-15

這是我參與8月更文挑戰的第4天,活動詳情檢視: 8月更文挑戰

Flutter 系列文章連載~

背景

我們在 Flutter Android 端的 Java 層程式碼中經常看到 FlutterEngine、FlutterEngineGroup、FlutterEngineCache 等相關類的使用,你是不是也經常搞不清他們的關係和作用?本文就是對他們的一個解剖分析,由於 Flutter 2 版本對這塊做了大調整,所以我們的分析以 2.2.3 版本為例分析。

FlutterEngine 相關分析

FlutterEngine 是一個獨立的 Flutter 執行環境容器,通過它可以在 Android 應用程式中執行 Dart 程式碼。FlutterEngine 中的 Dart 程式碼可以在後臺執行,也可以使用附帶的 FlutterRenderer 和 Dart 程式碼將 Dart 端 UI 效果渲染到螢幕上,渲染可以開始和停止,從而允許 FlutterEngine 從 UI 互動轉移到僅進行資料處理,然後又返回到 UI 互動的能力。

使用 FlutterEngine 執行 Dart 或 Flutter 程式碼需要先通過 FlutterEngine 獲取 DartExecutor 引用,然後呼叫 DartExecutor 的executeDartEntrypoint(DartExecutor.DartEntrypoint)執行 Dart 程式碼即可,同一個 FlutterEngine 例項中獲取的 DartExecutor 的executeDartEntrypoint(DartExecutor.DartEntrypoint)方法只能被呼叫一次,切記。

想要把 Flutter 內容渲染到螢幕上,需要呼叫 FlutterEngine 的getRenderer()方法獲取一個 FlutterRenderer 引用,然後讓 FlutterRenderer 例項 attach 上一個 RenderSurface(譬如預設提供的 FlutterView,也即其內部的 FlutterSurfaceView、FlutterTextureView、FlutterImageView 之一,參見前面系列文章)。

App 每個程式中建立第一個 FlutterEngine 例項的時候會載入 Flutter 引擎的原生庫並啟動 Dart VM(VM 存活生命週期跟隨程式),隨後同程式中其他的 FlutterEngines 將在同一個 VM 例項上執行,但在執行 DartExecutor 時將擁有自己的 Dart Isolate。每個 Isolate 都是一個獨立的 Dart 環境,除非通過 Isolate 埠,否則無法相互通訊。[參見官方文件]

所以,對於一個多程式且多 FlutterEngine 的 app 來說,其 FlutterEngine 與 DartExecutor、Dart VM、Isolate 的關係大致如下圖: 在這裡插入圖片描述 下面是 FlutterEngine 核心原始碼片段:

public class FlutterEngine {
  //Flutter C/C++與平臺java層介面定義互動。
  @NonNull private final FlutterJNI flutterJNI;
  //用來把Flutter Dart UI渲染到螢幕上,renderer會attach到RenderSurface上。
  @NonNull private final FlutterRenderer renderer;
  //Dart執行器。
  @NonNull private final DartExecutor dartExecutor;
  //用來管理安卓元件和Flutter plugins外掛。
  @NonNull private final FlutterEngineConnectionRegistry pluginRegistry;
  //localization的安卓端實現外掛。
  @NonNull private final LocalizationPlugin localizationPlugin;

  //一堆系統通道。
  @NonNull private final AccessibilityChannel accessibilityChannel;
  @NonNull private final DeferredComponentChannel deferredComponentChannel;
  @NonNull private final KeyEventChannel keyEventChannel;
  @NonNull private final LifecycleChannel lifecycleChannel;
  @NonNull private final LocalizationChannel localizationChannel;
  @NonNull private final MouseCursorChannel mouseCursorChannel;
  @NonNull private final NavigationChannel navigationChannel;
  @NonNull private final RestorationChannel restorationChannel;
  @NonNull private final PlatformChannel platformChannel;
  @NonNull private final SettingsChannel settingsChannel;
  @NonNull private final SystemChannel systemChannel;
  @NonNull private final TextInputChannel textInputChannel;

  // Platform Views.
  @NonNull private final PlatformViewsController platformViewsController;
  // Engine Lifecycle.
  @NonNull private final Set<EngineLifecycleListener> engineLifecycleListeners = new HashSet<>();
  //......

  //全引數的建構函式,各種構造最終都走進這裡
  public FlutterEngine(
      @NonNull Context context,
      @Nullable FlutterLoader flutterLoader,
      @NonNull FlutterJNI flutterJNI,
      @NonNull PlatformViewsController platformViewsController,
      @Nullable String[] dartVmArgs,
      boolean automaticallyRegisterPlugins,
      boolean waitForRestorationData) {
    //......
    //建立一個DartExecutor並將flutterJNI和安卓平臺的assetManager例項傳遞進去。
    this.dartExecutor = new DartExecutor(flutterJNI, assetManager);
    this.dartExecutor.onAttachedToJNI();
    //......
    //各種channel例項化,下面獨立分析。
    accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI);
    deferredComponentChannel = new DeferredComponentChannel(dartExecutor);
    keyEventChannel = new KeyEventChannel(dartExecutor);
    lifecycleChannel = new LifecycleChannel(dartExecutor);
    localizationChannel = new LocalizationChannel(dartExecutor);
    mouseCursorChannel = new MouseCursorChannel(dartExecutor);
    navigationChannel = new NavigationChannel(dartExecutor);
    platformChannel = new PlatformChannel(dartExecutor);
    restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData);
    settingsChannel = new SettingsChannel(dartExecutor);
    systemChannel = new SystemChannel(dartExecutor);
    textInputChannel = new TextInputChannel(dartExecutor);
    //......
    //外掛例項化。
    this.localizationPlugin = new LocalizationPlugin(context, localizationChannel);

    this.flutterJNI = flutterJNI;
    if (flutterLoader == null) {
      flutterLoader = FlutterInjector.instance().flutterLoader();
    }
    //......

    this.pluginRegistry =
        new FlutterEngineConnectionRegistry(context.getApplicationContext(), this, flutterLoader);
	//預設就是自動註冊plugins的,可以通過清單檔案配置變更等。
    if (automaticallyRegisterPlugins && flutterLoader.automaticallyRegisterPlugins()) {
      registerPlugins();
    }
  }
  //......

  //註冊flutter專案根目錄下pubspec.yaml中依賴的所有flutter plugins。
  //Flutter tool會生成一個GeneratedPluginRegistrant的類。
  private void registerPlugins() {
    try {
      Class<?> generatedPluginRegistrant = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
      Method registrationMethod = generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class);
      registrationMethod.invoke(null, this);
    } catch (Exception e) {
      Log.w(TAG, "Tried to automatically register plugins with FlutterEngine ("
              + this + ") but could not find and invoke the GeneratedPluginRegistrant.");
    }
  }
  //......省略一堆屬性成員的get方法
}
複製程式碼

上面程式碼片段其實挺直觀,主要就是負責各種外掛、Channel、渲染環境、執行環境的例項化準備工作。我們重點看一下上面的registerPlugins()方法,他內部反射呼叫了io.flutter.plugins.GeneratedPluginRegistrant類的registerWith(this)方法把當前 FlutterEngine 例項傳遞進去。

你可能會問,這個io.flutter.plugins.GeneratedPluginRegistrant類是哪裡來的呢?其實在 Flutter 專案根目錄下pubspec.yaml檔案的依賴中如果有 Flutter Plugin 則會在執行flutter pub get等 Flutter tools 命令時自動生成一個名為 GeneratedPluginRegistrant 的類,其中包含依賴的 Flutter Plugin 相關 add 程式碼。我們以一個 demo 為例來進行說明,如下圖示在pubspec.yaml中追加了 webview_flutter 依賴,本質是一個 Flutter Plugin,執行 pub get 後的效果如下: 在這裡插入圖片描述 可以看到,在構造例項化 FlutterEngine 時會呼叫其registerPlugins()方法,registerPlugins()方法會反射呼叫自動生成的io.flutter.plugins.GeneratedPluginRegistrant類的registerWith(this)方法把當前 FlutterEngine 例項傳遞進去。而io.flutter.plugins.GeneratedPluginRegistrant類的registerWith(this)方法中主要就是將我們在pubspec.yaml檔案中的 Flutter Plugin 依賴追加到 Plugins 集合中。

我們先看下flutterEngine.getPlugins().add(xxx)方法:

class FlutterEngineConnectionRegistry
    implements PluginRegistry,
        ActivityControlSurface,
        ServiceControlSurface,
        BroadcastReceiverControlSurface,
        ContentProviderControlSurface {
  //......
  @Override
  public void add(@NonNull FlutterPlugin plugin) {
    //......
    plugins.put(plugin.getClass(), plugin);
    plugin.onAttachedToEngine(pluginBinding);

    // For ActivityAware plugins, add the plugin to our set of ActivityAware
    // plugins, and if this engine is currently attached to an Activity,
    // notify the ActivityAware plugin that it is now attached to an Activity.
    if (plugin instanceof ActivityAware) {
      ActivityAware activityAware = (ActivityAware) plugin;
      activityAwarePlugins.put(plugin.getClass(), activityAware);

      if (isAttachedToActivity()) {
        activityAware.onAttachedToActivity(activityPluginBinding);
      }
    }

    // For ServiceAware plugins, add the plugin to our set of ServiceAware
    // plugins, and if this engine is currently attached to a Service,
    // notify the ServiceAware plugin that it is now attached to a Service.
    if (plugin instanceof ServiceAware) {
      ServiceAware serviceAware = (ServiceAware) plugin;
      serviceAwarePlugins.put(plugin.getClass(), serviceAware);

      if (isAttachedToService()) {
        serviceAware.onAttachedToService(servicePluginBinding);
      }
    }

    // For BroadcastReceiverAware plugins, add the plugin to our set of BroadcastReceiverAware
    // plugins, and if this engine is currently attached to a BroadcastReceiver,
    // notify the BroadcastReceiverAware plugin that it is now attached to a BroadcastReceiver.
    if (plugin instanceof BroadcastReceiverAware) {
      BroadcastReceiverAware broadcastReceiverAware = (BroadcastReceiverAware) plugin;
      broadcastReceiverAwarePlugins.put(plugin.getClass(), broadcastReceiverAware);

      if (isAttachedToBroadcastReceiver()) {
        broadcastReceiverAware.onAttachedToBroadcastReceiver(broadcastReceiverPluginBinding);
      }
    }

    // For ContentProviderAware plugins, add the plugin to our set of ContentProviderAware
    // plugins, and if this engine is currently attached to a ContentProvider,
    // notify the ContentProviderAware plugin that it is now attached to a ContentProvider.
    if (plugin instanceof ContentProviderAware) {
      ContentProviderAware contentProviderAware = (ContentProviderAware) plugin;
      contentProviderAwarePlugins.put(plugin.getClass(), contentProviderAware);

      if (isAttachedToContentProvider()) {
        contentProviderAware.onAttachedToContentProvider(contentProviderPluginBinding);
      }
    }
  }
  //......
}
複製程式碼

可以看到,FlutterEngineConnectionRegistry 的 add 方法不需要我們做過多解釋就能看懂,主要就是新增一個 FlutterPlugin 例項,然後呼叫 FlutterPlugin 介面約定的一堆類似生命週期方法,譬如 onAttachedToEngine,然後依據外掛的具體型別(安卓平臺元件型別,Activity、Service、Broadcast、ContentProvider)進行對應的方法呼叫,這樣 Flutter Plugin 外掛開發者就能依據這些時機方法進行自己的平臺邏輯處理。譬如上面 demo 中 webview_flutter Flutter Plugin 原始碼中的實現,如下:

public class WebViewFlutterPlugin implements FlutterPlugin {
  private FlutterCookieManager flutterCookieManager;
  //......
  //FlutterEngineConnectionRegistry的add中觸發呼叫,例項化BinaryMessenger和FlutterCookieManager。
  @Override
  public void onAttachedToEngine(FlutterPluginBinding binding) {
    BinaryMessenger messenger = binding.getBinaryMessenger();
    binding.getPlatformViewRegistry().registerViewFactory("plugins.flutter.io/webview", new WebViewFactory(messenger, /*containerView=*/ null));
    flutterCookieManager = new FlutterCookieManager(messenger);
  }
  
  //FlutterEngineConnectionRegistry的remove中觸發呼叫,移除外掛。
  @Override
  public void onDetachedFromEngine(FlutterPluginBinding binding) {
    if (flutterCookieManager == null) {
      return;
    }
    flutterCookieManager.dispose();
    flutterCookieManager = null;
  }
}
複製程式碼

這下搞懂每次編譯 Flutter Android App 時自動生成的 GeneratedPluginRegistrant 是咋回事了吧,也知道為啥要求 GeneratedPluginRegistrant 類需要在混淆清單中被 keep 住的原因了吧。整體流程大致如下圖: 在這裡插入圖片描述 關於 FlutterEngine 建構函式中的各種例項化 Channel 我們這裡先不展開,後面單獨篇章解析。

FlutterEngineCache 相關分析

FlutterEngineCache 其實很簡單,目的就是一個程式單例模式,其中通過 Map 儲存快取 FlutterEngine 例項,程式碼也沒啥好分析的。

public class FlutterEngineCache {
  private static FlutterEngineCache instance;
  //單例模式
  public static FlutterEngineCache getInstance() {
    if (instance == null) {
      instance = new FlutterEngineCache();
    }
    return instance;
  }
  //基於key快取FlutterEngine例項集合
  private final Map<String, FlutterEngine> cachedEngines = new HashMap<>();

  //判斷是否包含指定id的FlutterEngine例項
  public boolean contains(@NonNull String engineId) {
    return cachedEngines.containsKey(engineId);
  }

  //獲取指定id的FlutterEngine例項
  public FlutterEngine get(@NonNull String engineId) {
    return cachedEngines.get(engineId);
  }

  //快取指定id的FlutterEngine例項
  public void put(@NonNull String engineId, @Nullable FlutterEngine engine) {
    if (engine != null) {
      cachedEngines.put(engineId, engine);
    } else {
      cachedEngines.remove(engineId);
    }
  }

  //刪除指定id的FlutterEngine例項
  public void remove(@NonNull String engineId) {
    put(engineId, null);
  }

  //清空整個cache
  public void clear() {
    cachedEngines.clear();
  }
}
複製程式碼

FlutterActivity 支援和快取的 FlutterEngine 一起使用,可以通過 FlutterActivity.withCachedEngine(String) 構建一個 FlutterActivity Intent,該 Intent 配置為使用現有的快取 FlutterEngine。使用快取的 FlutterEngine 時,該 FlutterEngine 應當已經執行 Dart 程式碼,也就是說 Dart 入口點和初始路由已經定義,所以 CachedEngineIntentBuilder 不提供這些配置特性。推薦使用 FlutterEngineCache,這樣做可以預熱引擎,減少啟動 Flutter 頁面時的白屏或者等待時間,就像官方說的一樣。

FlutterEngineGroup 相關分析

比較早接觸 Flutter 的小夥伴應該都知道,Flutter 混合開發中,多個 Flutter 頁面(FlutterActivity)模式最被詬病的問題之一就是會生成多個 FlutterEngine 例項且每個 FlutterEngine 例項非常佔用記憶體,所以才有了民間類似鹹魚 Flutter 的 Flutter Boost 方案,採用單 FlutterEngine 方案(分屏等場景無法相容)且整個單程式 App 在同一個 Isolate 下做到記憶體共享。後來由於社群呼聲太高,官方也努力在 Flutter 2.0 釋出了實驗特性的 FlutterEngineGroup 多 FlutterEngine 官方解決方案,即每個頁面是一個 FlutterEngine,或者一個頁面內包含多個 FlutterEngine,每個 FlutterEngine 對應一個 Isolate 且記憶體不共享。官方說從 FlutterEngineGroup 生成的 FlutterEngine 記憶體只增加 180k,因為它對常用資源進行共享(例如 GPU 上下文、字型度量和隔離執行緒的快照等),加快首次渲染的速度、降低延遲並降低記憶體佔用。但是到目前 Flutter 2.2 版本為止,FlutterEngineGroup 依舊處於實驗特性階段,不推薦在正式專案中使用,參見官方 multiple-flutters 文件。下面先看下核心原始碼:

public class FlutterEngineGroup {
  final List<FlutterEngine> activeEngines = new ArrayList<>();
  //......

  //通過FlutterEngineGroup例項建立一個FlutterEngine,第一個FlutterEngine和普通建立沒區別,之後的就有區別了。
  public FlutterEngine createAndRunEngine(
      @NonNull Context context, @Nullable DartEntrypoint dartEntrypoint) {
    FlutterEngine engine = null;
    //......
    if (activeEngines.size() == 0) {
      //來自FlutterEngineGroup建立的第一個FlutterEngine,和普通建立沒區別。
      engine = createEngine(context);
      engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint);
    } else {
      //來自FlutterEngineGroup建立的非第一個FlutterEngine,基於第一個進行spawn操作返回。
      engine = activeEngines.get(0).spawn(context, dartEntrypoint);
    }

    activeEngines.add(engine);
	//......
    return engine;
  }

  FlutterEngine createEngine(Context context) {
    return new FlutterEngine(context);
  }
}
複製程式碼

可以看到,與普通 FlutterEngine 的區別在於 FlutterEngineGroup 的 createAndRunEngine 方法建立會有不同,具體在於,createAndRunEngine 方法建立第一個 FlutterEngine 例項與普通無區別,當建立第二個時會通過第一個 FlutterEngine 例項的spawn(context, dartEntrypoint)方法進行建立,所以我們去看下 FlutterEngine 的 spawn 方法,如下:

FlutterEngine spawn(@NonNull Context context, @NonNull DartEntrypoint dartEntrypoint) {
  //......
  FlutterJNI newFlutterJNI =
      flutterJNI.spawn(
          dartEntrypoint.dartEntrypointFunctionName, dartEntrypoint.dartEntrypointLibrary);
  return new FlutterEngine(
      context, // Context.
      null, // FlutterLoader. A null value passed here causes the constructor to get it from the
      // FlutterInjector.
      newFlutterJNI); // FlutterJNI.
}
複製程式碼

很明顯看到 spawn 方法是 FlutterEngine 的 c/c++ 層實現的,我們不再跟進,可以通過他的註釋知道,這種基於當前 FlutterEngine 建立第二個 FlutterEngine 的方式會通過共享盡可能多的資源以最小化啟動延遲和記憶體成本,這也就是官方的解決方案。具體樣例 demo 參見官方 github 中 samples 的 add_to_app/multiple_flutters

總結

可以看到,FlutterEngine、FlutterEngineCache、FlutterEngineGroup 的本質都是 FlutterEngine,多出的只是空間換時間,共用換空間的機制,其他官方 FlutterEngineGroup 早日穩定正式版。

相關文章