blob: bc2902c0281a99d357bbfb3e0cfdf1bdf99b838c [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.embedding.engine;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.FlutterInjector;
import io.flutter.Log;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;
import io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.embedding.engine.plugins.PluginRegistry;
import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface;
import io.flutter.embedding.engine.plugins.broadcastreceiver.BroadcastReceiverControlSurface;
import io.flutter.embedding.engine.plugins.contentprovider.ContentProviderControlSurface;
import io.flutter.embedding.engine.plugins.service.ServiceControlSurface;
import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.RenderSurface;
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
import io.flutter.embedding.engine.systemchannels.DeferredComponentChannel;
import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
import io.flutter.embedding.engine.systemchannels.NavigationChannel;
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.embedding.engine.systemchannels.RestorationChannel;
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
import io.flutter.embedding.engine.systemchannels.SpellCheckChannel;
import io.flutter.embedding.engine.systemchannels.SystemChannel;
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.plugin.localization.LocalizationPlugin;
import io.flutter.plugin.platform.PlatformViewsController;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A single Flutter execution environment.
*
* <p>The {@code FlutterEngine} is the container through which Dart code can be run in an Android
* application.
*
* <p>Dart code in a {@code FlutterEngine} can execute in the background, or it can be render to the
* screen by using the accompanying {@link FlutterRenderer} and Dart code using the Flutter
* framework on the Dart side. Rendering can be started and stopped, thus allowing a {@code
* FlutterEngine} to move from UI interaction to data-only processing and then back to UI
* interaction.
*
* <p>Multiple {@code FlutterEngine}s may exist, execute Dart code, and render UIs within a single
* Android app. For better memory performance characteristics, construct multiple {@code
* FlutterEngine}s via {@link io.flutter.embedding.engine.FlutterEngineGroup} rather than via {@code
* FlutterEngine}'s constructor directly.
*
* <p>To start running Dart and/or Flutter within this {@code FlutterEngine}, get a reference to
* this engine's {@link DartExecutor} and then use {@link
* DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)}. The {@link
* DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)} method must not be invoked twice
* on the same {@code FlutterEngine}.
*
* <p>To start rendering Flutter content to the screen, use {@link #getRenderer()} to obtain a
* {@link FlutterRenderer} and then attach a {@link RenderSurface}. Consider using a {@link
* io.flutter.embedding.android.FlutterView} as a {@link RenderSurface}.
*
* <p>Instatiating the first {@code FlutterEngine} per process will also load the Flutter engine's
* native library and start the Dart VM. Subsequent {@code FlutterEngine}s will run on the same VM
* instance but will have their own Dart <a
* href="https://api.dartlang.org/stable/dart-isolate/Isolate-class.html">Isolate</a> when the
* {@link DartExecutor} is run. Each Isolate is a self-contained Dart environment and cannot
* communicate with each other except via Isolate ports.
*/
public class FlutterEngine {
private static final String TAG = "FlutterEngine";
@NonNull private final FlutterJNI flutterJNI;
@NonNull private final FlutterRenderer renderer;
@NonNull private final DartExecutor dartExecutor;
@NonNull private final FlutterEngineConnectionRegistry pluginRegistry;
@NonNull private final LocalizationPlugin localizationPlugin;
// System channels.
@NonNull private final AccessibilityChannel accessibilityChannel;
@NonNull private final DeferredComponentChannel deferredComponentChannel;
@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 SpellCheckChannel spellCheckChannel;
@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<>();
@NonNull
private final EngineLifecycleListener engineLifecycleListener =
new EngineLifecycleListener() {
@SuppressWarnings("unused")
public void onPreEngineRestart() {
Log.v(TAG, "onPreEngineRestart()");
for (EngineLifecycleListener lifecycleListener : engineLifecycleListeners) {
lifecycleListener.onPreEngineRestart();
}
platformViewsController.onPreEngineRestart();
restorationChannel.clearData();
}
@Override
public void onEngineWillDestroy() {
// This inner implementation doesn't do anything since FlutterEngine sent this
// notification in the first place. It's meant for external listeners.
}
};
/**
* Constructs a new {@code FlutterEngine}.
*
* <p>A new {@code FlutterEngine} does not execute any Dart code automatically. See {@link
* #getDartExecutor()} and {@link DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)}
* to begin executing Dart code within this {@code FlutterEngine}.
*
* <p>A new {@code FlutterEngine} will not display any UI until a {@link RenderSurface} is
* registered. See {@link #getRenderer()} and {@link
* FlutterRenderer#startRenderingToSurface(Surface, boolean)}.
*
* <p>A new {@code FlutterEngine} automatically attaches all plugins. See {@link #getPlugins()}.
*
* <p>A new {@code FlutterEngine} does come with all default system channels attached.
*
* <p>The first {@code FlutterEngine} instance constructed per process will also load the Flutter
* native library and start a Dart VM.
*
* <p>In order to pass Dart VM initialization arguments (see {@link
* io.flutter.embedding.engine.FlutterShellArgs}) when creating the VM, manually set the
* initialization arguments by calling {@link
* io.flutter.embedding.engine.loader.FlutterLoader#startInitialization(Context)} and {@link
* io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context,
* String[])} before constructing the engine.
*/
public FlutterEngine(@NonNull Context context) {
this(context, null);
}
/**
* Same as {@link #FlutterEngine(Context)} with added support for passing Dart VM arguments.
*
* <p>If the Dart VM has already started, the given arguments will have no effect.
*/
public FlutterEngine(@NonNull Context context, @Nullable String[] dartVmArgs) {
this(context, /* flutterLoader */ null, /* flutterJNI */ null, dartVmArgs, true);
}
/**
* Same as {@link #FlutterEngine(Context)} with added support for passing Dart VM arguments and
* avoiding automatic plugin registration.
*
* <p>If the Dart VM has already started, the given arguments will have no effect.
*/
public FlutterEngine(
@NonNull Context context,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins) {
this(
context,
/* flutterLoader */ null,
/* flutterJNI */ null,
dartVmArgs,
automaticallyRegisterPlugins);
}
/**
* Same as {@link #FlutterEngine(Context, String[], boolean)} with added support for configuring
* whether the engine will receive restoration data.
*
* <p>The {@code waitForRestorationData} flag controls whether the engine delays responding to
* requests from the framework for restoration data until that data has been provided to the
* engine via {@code RestorationChannel.setRestorationData(byte[] data)}. If the flag is false,
* the framework may temporarily initialize itself to default values before the restoration data
* has been made available to the engine. Setting {@code waitForRestorationData} to true avoids
* this extra work by delaying initialization until the data is available.
*
* <p>When {@code waitForRestorationData} is set, {@code
* RestorationChannel.setRestorationData(byte[] data)} must be called at a later point in time. If
* it later turns out that no restoration data is available to restore the framework from, that
* method must still be called with null as an argument to indicate "no data".
*
* <p>If the framework never requests the restoration data, this flag has no effect.
*/
public FlutterEngine(
@NonNull Context context,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins,
boolean waitForRestorationData) {
this(
context,
/* flutterLoader */ null,
/* flutterJNI */ null,
new PlatformViewsController(),
dartVmArgs,
automaticallyRegisterPlugins,
waitForRestorationData);
}
/**
* Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[], boolean)} but with
* no Dart VM flags and automatically registers plugins.
*
* <p>{@code flutterJNI} should be a new instance that has never been attached to an engine
* before.
*/
public FlutterEngine(
@NonNull Context context,
@Nullable FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI) {
this(context, flutterLoader, flutterJNI, null, true);
}
/**
* Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI)}, plus Dart VM flags in
* {@code dartVmArgs}, and control over whether plugins are automatically registered with this
* {@code FlutterEngine} in {@code automaticallyRegisterPlugins}. If plugins are automatically
* registered, then they are registered during the execution of this constructor.
*/
public FlutterEngine(
@NonNull Context context,
@Nullable FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins) {
this(
context,
flutterLoader,
flutterJNI,
new PlatformViewsController(),
dartVmArgs,
automaticallyRegisterPlugins);
}
/**
* Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[], boolean)}, plus the
* ability to provide a custom {@code PlatformViewsController}.
*/
public FlutterEngine(
@NonNull Context context,
@Nullable FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI,
@NonNull PlatformViewsController platformViewsController,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins) {
this(
context,
flutterLoader,
flutterJNI,
platformViewsController,
dartVmArgs,
automaticallyRegisterPlugins,
false);
}
/** Fully configurable {@code FlutterEngine} constructor. */
public FlutterEngine(
@NonNull Context context,
@Nullable FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI,
@NonNull PlatformViewsController platformViewsController,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins,
boolean waitForRestorationData) {
this(
context,
flutterLoader,
flutterJNI,
platformViewsController,
dartVmArgs,
automaticallyRegisterPlugins,
waitForRestorationData,
null);
}
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public FlutterEngine(
@NonNull Context context,
@Nullable FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI,
@NonNull PlatformViewsController platformViewsController,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins,
boolean waitForRestorationData,
@Nullable FlutterEngineGroup group) {
AssetManager assetManager;
try {
assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets();
} catch (NameNotFoundException e) {
assetManager = context.getAssets();
}
FlutterInjector injector = FlutterInjector.instance();
if (flutterJNI == null) {
flutterJNI = injector.getFlutterJNIFactory().provideFlutterJNI();
}
this.flutterJNI = flutterJNI;
this.dartExecutor = new DartExecutor(flutterJNI, assetManager);
this.dartExecutor.onAttachedToJNI();
DeferredComponentManager deferredComponentManager =
FlutterInjector.instance().deferredComponentManager();
accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI);
deferredComponentChannel = new DeferredComponentChannel(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);
spellCheckChannel = new SpellCheckChannel(dartExecutor);
systemChannel = new SystemChannel(dartExecutor);
textInputChannel = new TextInputChannel(dartExecutor);
if (deferredComponentManager != null) {
deferredComponentManager.setDeferredComponentChannel(deferredComponentChannel);
}
this.localizationPlugin = new LocalizationPlugin(context, localizationChannel);
if (flutterLoader == null) {
flutterLoader = injector.flutterLoader();
}
if (!flutterJNI.isAttached()) {
flutterLoader.startInitialization(context.getApplicationContext());
flutterLoader.ensureInitializationComplete(context, dartVmArgs);
}
flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
flutterJNI.setPlatformViewsController(platformViewsController);
flutterJNI.setLocalizationPlugin(localizationPlugin);
flutterJNI.setDeferredComponentManager(injector.deferredComponentManager());
// It should typically be a fresh, unattached JNI. But on a spawned engine, the JNI instance
// is already attached to a native shell. In that case, the Java FlutterEngine is created around
// an existing shell.
if (!flutterJNI.isAttached()) {
attachToJni();
}
// TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if
// possible.
this.renderer = new FlutterRenderer(flutterJNI);
this.platformViewsController = platformViewsController;
this.platformViewsController.onAttachedToJNI();
this.pluginRegistry =
new FlutterEngineConnectionRegistry(
context.getApplicationContext(), this, flutterLoader, group);
localizationPlugin.sendLocalesToFlutter(context.getResources().getConfiguration());
// Only automatically register plugins if both constructor parameter and
// loaded AndroidManifest config turn this feature on.
if (automaticallyRegisterPlugins && flutterLoader.automaticallyRegisterPlugins()) {
GeneratedPluginRegister.registerGeneratedPlugins(this);
}
}
private void attachToJni() {
Log.v(TAG, "Attaching to JNI.");
flutterJNI.attachToNative();
if (!isAttachedToJni()) {
throw new RuntimeException("FlutterEngine failed to attach to its native Object reference.");
}
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean isAttachedToJni() {
return flutterJNI.isAttached();
}
/**
* Create a second {@link io.flutter.embedding.engine.FlutterEngine} based on this current one by
* sharing as much resources together as possible to minimize startup latency and memory cost.
*
* @param context is a Context used to create the {@link
* io.flutter.embedding.engine.FlutterEngine}. Could be the same Context as the current engine
* or a different one. Generally, only an application Context is needed for the {@link
* io.flutter.embedding.engine.FlutterEngine} and its dependencies.
* @param dartEntrypoint specifies the {@link DartEntrypoint} the new engine should run. It
* doesn't need to be the same entrypoint as the current engine but must be built in the same
* AOT or snapshot.
* @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is
* null, it will default to the "/" route.
* @param dartEntrypointArgs Arguments passed as a list of string to Dart's entrypoint function.
* @return a new {@link io.flutter.embedding.engine.FlutterEngine}.
*/
@NonNull
/*package*/ FlutterEngine spawn(
@NonNull Context context,
@NonNull DartEntrypoint dartEntrypoint,
@Nullable String initialRoute,
@Nullable List<String> dartEntrypointArgs,
@Nullable PlatformViewsController platformViewsController,
boolean automaticallyRegisterPlugins,
boolean waitForRestorationData) {
if (!isAttachedToJni()) {
throw new IllegalStateException(
"Spawn can only be called on a fully constructed FlutterEngine");
}
FlutterJNI newFlutterJNI =
flutterJNI.spawn(
dartEntrypoint.dartEntrypointFunctionName,
dartEntrypoint.dartEntrypointLibrary,
initialRoute,
dartEntrypointArgs);
return new FlutterEngine(
context, // Context.
null, // FlutterLoader. A null value passed here causes the constructor to get it from the
// FlutterInjector.
newFlutterJNI, // FlutterJNI.
platformViewsController, // PlatformViewsController.
null, // String[]. The Dart VM has already started, this arguments will have no effect.
automaticallyRegisterPlugins, // boolean.
waitForRestorationData); // boolean
}
/**
* Cleans up all components within this {@code FlutterEngine} and destroys the associated Dart
* Isolate. All state held by the Dart Isolate, such as the Flutter Elements tree, is lost.
*
* <p>This {@code FlutterEngine} instance should be discarded after invoking this method.
*/
public void destroy() {
Log.v(TAG, "Destroying.");
for (EngineLifecycleListener listener : engineLifecycleListeners) {
listener.onEngineWillDestroy();
}
// The order that these things are destroyed is important.
pluginRegistry.destroy();
platformViewsController.onDetachedFromJNI();
dartExecutor.onDetachedFromJNI();
flutterJNI.removeEngineLifecycleListener(engineLifecycleListener);
flutterJNI.setDeferredComponentManager(null);
flutterJNI.detachFromNativeAndReleaseResources();
if (FlutterInjector.instance().deferredComponentManager() != null) {
FlutterInjector.instance().deferredComponentManager().destroy();
deferredComponentChannel.setDeferredComponentManager(null);
}
}
/**
* Adds a {@code listener} to be notified of Flutter engine lifecycle events, e.g., {@code
* onPreEngineStart()}.
*/
public void addEngineLifecycleListener(@NonNull EngineLifecycleListener listener) {
engineLifecycleListeners.add(listener);
}
/**
* Removes a {@code listener} that was previously added with {@link
* #addEngineLifecycleListener(EngineLifecycleListener)}.
*/
public void removeEngineLifecycleListener(@NonNull EngineLifecycleListener listener) {
engineLifecycleListeners.remove(listener);
}
/**
* The Dart execution context associated with this {@code FlutterEngine}.
*
* <p>The {@link DartExecutor} can be used to start executing Dart code from a given entrypoint.
* See {@link DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)}.
*
* <p>Use the {@link DartExecutor} to connect any desired message channels and method channels to
* facilitate communication between Android and Dart/Flutter.
*/
@NonNull
public DartExecutor getDartExecutor() {
return dartExecutor;
}
/**
* The rendering system associated with this {@code FlutterEngine}.
*
* <p>To render a Flutter UI that is produced by this {@code FlutterEngine}'s Dart code, attach a
* {@link RenderSurface} to this {@link FlutterRenderer}.
*/
@NonNull
public FlutterRenderer getRenderer() {
return renderer;
}
/** System channel that sends accessibility requests and events from Flutter to Android. */
@NonNull
public AccessibilityChannel getAccessibilityChannel() {
return accessibilityChannel;
}
/** System channel that sends Android lifecycle events to Flutter. */
@NonNull
public LifecycleChannel getLifecycleChannel() {
return lifecycleChannel;
}
/** System channel that sends locale data from Android to Flutter. */
@NonNull
public LocalizationChannel getLocalizationChannel() {
return localizationChannel;
}
/** System channel that sends Flutter navigation commands from Android to Flutter. */
@NonNull
public NavigationChannel getNavigationChannel() {
return navigationChannel;
}
/**
* System channel that sends platform-oriented requests and information to Flutter, e.g., requests
* to play sounds, requests for haptics, system chrome settings, etc.
*/
@NonNull
public PlatformChannel getPlatformChannel() {
return platformChannel;
}
/**
* System channel to exchange restoration data between framework and engine.
*
* <p>The engine can obtain the current restoration data from the framework via this channel to
* store it on disk and - when the app is relaunched - provide the stored data back to the
* framework to recreate the original state of the app.
*/
@NonNull
public RestorationChannel getRestorationChannel() {
return restorationChannel;
}
/**
* System channel that sends platform/user settings from Android to Flutter, e.g., time format,
* scale factor, etc.
*/
@NonNull
public SettingsChannel getSettingsChannel() {
return settingsChannel;
}
/** System channel that allows manual installation and state querying of deferred components. */
@NonNull
public DeferredComponentChannel getDeferredComponentChannel() {
return deferredComponentChannel;
}
/** System channel that sends memory pressure warnings from Android to Flutter. */
@NonNull
public SystemChannel getSystemChannel() {
return systemChannel;
}
/** System channel that sends and receives text input requests and state. */
@NonNull
public MouseCursorChannel getMouseCursorChannel() {
return mouseCursorChannel;
}
/** System channel that sends and receives text input requests and state. */
@NonNull
public TextInputChannel getTextInputChannel() {
return textInputChannel;
}
/** System channel that sends and receives spell check requests and results. */
@NonNull
public SpellCheckChannel getSpellCheckChannel() {
return spellCheckChannel;
}
/**
* Plugin registry, which registers plugins that want to be applied to this {@code FlutterEngine}.
*/
@NonNull
public PluginRegistry getPlugins() {
return pluginRegistry;
}
/** The LocalizationPlugin this FlutterEngine created. */
@NonNull
public LocalizationPlugin getLocalizationPlugin() {
return localizationPlugin;
}
/**
* {@code PlatformViewsController}, which controls all platform views running within this {@code
* FlutterEngine}.
*/
@NonNull
public PlatformViewsController getPlatformViewsController() {
return platformViewsController;
}
@NonNull
public ActivityControlSurface getActivityControlSurface() {
return pluginRegistry;
}
@NonNull
public ServiceControlSurface getServiceControlSurface() {
return pluginRegistry;
}
@NonNull
public BroadcastReceiverControlSurface getBroadcastReceiverControlSurface() {
return pluginRegistry;
}
@NonNull
public ContentProviderControlSurface getContentProviderControlSurface() {
return pluginRegistry;
}
/** Lifecycle callbacks for Flutter engine lifecycle events. */
public interface EngineLifecycleListener {
/** Lifecycle callback invoked before a hot restart of the Flutter engine. */
void onPreEngineRestart();
/**
* Lifecycle callback invoked before the Flutter engine is destroyed.
*
* <p>For the duration of the call, the Flutter engine is still valid.
*/
void onEngineWillDestroy();
}
}