diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 389d3af06afdb..3d7f197dcfeeb 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -20,7 +20,6 @@ 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; @@ -37,6 +36,7 @@ import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.plugin.localization.LocalizationPlugin; import io.flutter.plugin.platform.PlatformViewsController; +import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; @@ -342,7 +342,7 @@ public FlutterEngine( // Only automatically register plugins if both constructor parameter and // loaded AndroidManifest config turn this feature on. if (automaticallyRegisterPlugins && flutterLoader.automaticallyRegisterPlugins()) { - GeneratedPluginRegister.registerGeneratedPlugins(this); + registerPlugins(); } } @@ -391,6 +391,36 @@ private boolean isAttachedToJni() { newFlutterJNI); // FlutterJNI. } + /** + * 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 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."); + } + } + /** * 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. diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java b/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java index fab7eb8c7ee65..5bb0ff5a6f37b 100644 --- a/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java +++ b/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java @@ -33,15 +33,11 @@ public static void registerGeneratedPlugins(@NonNull FlutterEngine flutterEngine generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class); registrationMethod.invoke(null, flutterEngine); } catch (Exception e) { - Log.e( + Log.w( TAG, "Tried to automatically register plugins with FlutterEngine (" + flutterEngine + ") but could not find and invoke the GeneratedPluginRegistrant."); - Log.e( - TAG, - // getCause here because the first layer of the exception would be from reflect. - "Received exception while registering: " + e.getCause()); } } } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java index 1af79de943259..bcfabf863dafe 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java @@ -35,7 +35,6 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowLog; @Config(manifest = Config.NONE) @RunWith(RobolectricTestRunner.class) @@ -64,9 +63,6 @@ public Object answer(InvocationOnMock invocation) throws Throwable { @After public void tearDown() { GeneratedPluginRegistrant.clearRegisteredEngines(); - // Make sure to not forget to remove the mock exception in the generated plugin registration - // mock, or everything subsequent will break. - GeneratedPluginRegistrant.pluginRegistrationException = null; } @Test @@ -82,38 +78,6 @@ public void itAutomaticallyRegistersPluginsByDefault() { assertEquals(flutterEngine, registeredEngines.get(0)); } - // Helps show the root cause of MissingPluginException type errors like - // https://github.com/flutter/flutter/issues/78625. - @Test - public void itCatchesAndDisplaysRegistrationExceptions() { - assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty()); - GeneratedPluginRegistrant.pluginRegistrationException = - new RuntimeException("I'm a bug in the plugin"); - FlutterLoader mockFlutterLoader = mock(FlutterLoader.class); - when(mockFlutterLoader.automaticallyRegisterPlugins()).thenReturn(true); - FlutterEngine flutterEngine = - new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, flutterJNI); - - List registeredEngines = GeneratedPluginRegistrant.getRegisteredEngines(); - // When it crashes, it doesn't end up registering anything. - assertEquals(0, registeredEngines.size()); - - // Check the logs actually says registration failed, so a subsequent MissingPluginException - // isn't mysterious. - assertTrue( - ShadowLog.getLogsForTag("GeneratedPluginsRegister") - .get(0) - .msg - .contains("Tried to automatically register plugins")); - assertTrue( - ShadowLog.getLogsForTag("GeneratedPluginsRegister") - .get(1) - .msg - .contains("I'm a bug in the plugin")); - - GeneratedPluginRegistrant.pluginRegistrationException = null; - } - @Test public void itDoesNotAutomaticallyRegistersPluginsWhenFlutterLoaderDisablesIt() { assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty()); @@ -141,6 +105,18 @@ public void itDoesNotAutomaticallyRegistersPluginsWhenFlutterEngineDisablesIt() assertTrue(registeredEngines.isEmpty()); } + @Test + public void itCanBeConfiguredToNotAutomaticallyRegisterPlugins() { + new FlutterEngine( + RuntimeEnvironment.application, + mock(FlutterLoader.class), + flutterJNI, + /*dartVmArgs=*/ new String[] {}, + /*automaticallyRegisterPlugins=*/ false); + + assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty()); + } + @Test public void itNotifiesPlatformViewsControllerWhenDevHotRestart() { // Setup test. diff --git a/shell/platform/android/test/io/flutter/plugins/GeneratedPluginRegistrant.java b/shell/platform/android/test/io/flutter/plugins/GeneratedPluginRegistrant.java index c2528725b89b3..9b93bd6399464 100644 --- a/shell/platform/android/test/io/flutter/plugins/GeneratedPluginRegistrant.java +++ b/shell/platform/android/test/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -13,7 +13,6 @@ @VisibleForTesting public class GeneratedPluginRegistrant { private static final List registeredEngines = new ArrayList<>(); - public static RuntimeException pluginRegistrationException; /** * The one and only method currently generated by the tool. @@ -22,9 +21,6 @@ public class GeneratedPluginRegistrant { * all registered engines instead. */ public static void registerWith(FlutterEngine engine) { - if (pluginRegistrationException != null) { - throw pluginRegistrationException; - } registeredEngines.add(engine); }