diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 943b04ca4c71a..9b7ee297e1094 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -435,6 +435,7 @@ action("robolectric_tests") { "test/io/flutter/plugin/common/StandardMessageCodecTest.java", "test/io/flutter/plugin/editing/TextInputPluginTest.java", "test/io/flutter/plugin/platform/SingleViewPresentationTest.java", + "test/io/flutter/plugins/GeneratedPluginRegistrant.java", "test/io/flutter/util/PreconditionsTest.java", ] diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 6e5ada1d8d77c..8a7a8475e71f8 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -33,6 +33,9 @@ import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.view.FlutterMain; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_BACKGROUND_MODE; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_DART_ENTRYPOINT; @@ -873,14 +876,18 @@ public PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNul } /** - * Hook for subclasses to easily configure a {@code FlutterEngine}, e.g., register - * plugins. - *
- * This method is called after {@link #provideFlutterEngine(Context)}. + * Hook for subclasses to easily configure a {@code FlutterEngine}. + * + *
This method is called after {@link #provideFlutterEngine(Context)}. + * + *
All plugins listed in the app's pubspec are registered in the base implementation of this + * method. To avoid automatic plugin registration, override this method without invoking super(). + * To keep automatic plugin registration and further configure the flutterEngine, override this + * method, invoke super(), and then configure the flutterEngine as desired. */ @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { - // No-op. Hook for subclasses. + registerPlugins(flutterEngine); } /** @@ -950,4 +957,28 @@ public void onFlutterUiNoLongerDisplayed() { // no-op } + /** + * Registers all plugins that an app lists in its pubspec.yaml. + *
+ * The Flutter tool generates a class called GeneratedPluginRegistrant, which includes the code + * necessary to register every plugin in the pubspec.yaml with a given {@code FlutterEngine}. + * The GeneratedPluginRegistrant must be generated per app, because each app uses different sets + * of plugins. Therefore, the Android embedding cannot place a compile-time dependency on this + * generated class. This method uses reflection to attempt to locate the generated file and then + * use it at runtime. + *
+ * This method fizzles if the GeneratedPluginRegistrant cannot be found or invoked. This situation + * should never occur, but if any eventuality comes up that prevents an app from using this + * behavior, that app can still write code that explicitly registers plugins. + */ + private static void registerPlugins(@NonNull FlutterEngine flutterEngine) { + try { + Class> generatedPluginRegistrant = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant"); + Method registrationMethod = generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class); + registrationMethod.invoke(null, flutterEngine); + } catch (Exception e) { + Log.w(TAG, "Tried to automatically register plugins with FlutterEngine (" + + flutterEngine + ") but could not find and invoke the GeneratedPluginRegistrant."); + } + } } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 17bd3c46d5632..5b36dfaafe735 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -27,6 +27,8 @@ import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; +import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.plugin.platform.PlatformPlugin; import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; @@ -223,7 +225,7 @@ void onAttach(@NonNull Context context) { // FlutterView. Log.v(TAG, "No preferred FlutterEngine was provided. Creating a new FlutterEngine for" + " this FlutterFragment."); - flutterEngine = new FlutterEngine(host.getContext(), host.getFlutterShellArgs().toArray()); + flutterEngine = new FlutterEngine(host.getContext(), host.getFlutterShellArgs().toArray(), /*automaticallyRegisterPlugins=*/false); isFlutterEngineFromHost = false; } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index cd8956a172961..1d142a0bf9b2c 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -126,8 +126,7 @@ public void onPreEngineRestart() { * {@link RenderSurface} is registered. See * {@link #getRenderer()} and {@link FlutterRenderer#startRenderingToSurface(RenderSurface)}. *
- * A new {@code FlutterEngine} does not come with any Flutter plugins attached. To attach plugins, - * see {@link #getPlugins()}. + * A new {@code FlutterEngine} automatically attaches all plugins. See {@link #getPlugins()}. *
* A new {@code FlutterEngine} does come with all default system channels attached. *
@@ -152,6 +151,19 @@ public FlutterEngine(@NonNull Context context, @Nullable String[] dartVmArgs) { this(context, FlutterLoader.getInstance(), new FlutterJNI(), dartVmArgs, true); } + /** + * Same as {@link #FlutterEngine(Context)} with added support for passing Dart + * VM arguments and avoiding automatic plugin registration. + *
+ * 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.getInstance(), new FlutterJNI(), dartVmArgs, automaticallyRegisterPlugins);
+ }
+
/**
* Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[])} but with no Dart
* VM flags.
diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java
index 64528bfd766cf..e4cfbfdda70d0 100644
--- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java
@@ -6,6 +6,10 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import io.flutter.plugins.GeneratedPluginRegistrant;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
@@ -30,6 +34,16 @@
@Config(manifest=Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterActivityTest {
+ @Before
+ public void setUp() {
+ GeneratedPluginRegistrant.clearRegisteredEngines();
+ }
+
+ @After
+ public void tearDown() {
+ GeneratedPluginRegistrant.clearRegisteredEngines();
+ }
+
@Test
public void itCreatesDefaultIntentWithExpectedDefaults() {
Intent intent = FlutterActivity.createDefaultIntent(RuntimeEnvironment.application);
@@ -122,6 +136,19 @@ public void itCreatesCachedEngineIntentThatDestroysTheEngine() {
assertTrue(flutterActivity.shouldDestroyEngineWithHost());
}
+ @Test
+ public void itRegistersPluginsAtConfigurationTime() {
+ FlutterActivity activity = Robolectric.buildActivity(FlutterActivityWithProvidedEngine.class).get();
+ activity.onCreate(null);
+
+ assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());
+ activity.configureFlutterEngine(activity.getFlutterEngine());
+
+ List Used to test engine logic which interacts with the generated class.
+ */
+@VisibleForTesting
+public class GeneratedPluginRegistrant {
+ private static final List Normally it registers all plugins in an app with the given {@code engine}. This fake tracks
+ * all registered engines instead.
+ */
+ public static void registerWith(FlutterEngine engine) {
+ registeredEngines.add(engine);
+ }
+
+ /**
+ * Clears the mutable static state regrettably stored in this class.
+ *
+ * {@link #registerWith} is a static call with no visible side effects. In order to verify when
+ * it's been called we also unfortunately need to store the state statically. This should be
+ * called before and after each test run accessing this class to make sure the state is clear both
+ * before and after the run.
+ */
+ public static void clearRegisteredEngines() {
+ registeredEngines.clear();
+ }
+
+ /**
+ * Returns a list of all the engines registered so far.
+ *
+ * CAUTION: This list is static and must be manually wiped in between test runs. See
+ * {@link #clearRegisteredEngines()}.
+ */
+ public static List