未验证 提交 971a851f 编写于 作者: X xster 提交者: GitHub

Deduplicate plugin registration logic and make error logs visible - take 2 (#25395)

上级 cbeb02e1
......@@ -7,6 +7,7 @@ package io.flutter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager;
import io.flutter.embedding.engine.loader.FlutterLoader;
......@@ -65,13 +66,17 @@ public final class FlutterInjector {
}
private FlutterInjector(
@NonNull FlutterLoader flutterLoader, DeferredComponentManager deferredComponentManager) {
@NonNull FlutterLoader flutterLoader,
@Nullable DeferredComponentManager deferredComponentManager,
@NonNull FlutterJNI.Factory flutterJniFactory) {
this.flutterLoader = flutterLoader;
this.deferredComponentManager = deferredComponentManager;
this.flutterJniFactory = flutterJniFactory;
}
private FlutterLoader flutterLoader;
private DeferredComponentManager deferredComponentManager;
private FlutterJNI.Factory flutterJniFactory;
/** Returns the {@link FlutterLoader} instance to use for the Flutter Android engine embedding. */
@NonNull
......@@ -88,6 +93,11 @@ public final class FlutterInjector {
return deferredComponentManager;
}
@NonNull
public FlutterJNI.Factory getFlutterJNIFactory() {
return flutterJniFactory;
}
/**
* Builder used to supply a custom FlutterInjector instance to {@link
* FlutterInjector#setInstance(FlutterInjector)}.
......@@ -97,6 +107,7 @@ public final class FlutterInjector {
public static final class Builder {
private FlutterLoader flutterLoader;
private DeferredComponentManager deferredComponentManager;
private FlutterJNI.Factory flutterJniFactory;
/**
* Sets a {@link FlutterLoader} override.
*
......@@ -113,9 +124,18 @@ public final class FlutterInjector {
return this;
}
public Builder setFlutterJNIFactory(@NonNull FlutterJNI.Factory factory) {
this.flutterJniFactory = factory;
return this;
}
private void fillDefaults() {
if (flutterJniFactory == null) {
flutterJniFactory = new FlutterJNI.Factory();
}
if (flutterLoader == null) {
flutterLoader = new FlutterLoader();
flutterLoader = new FlutterLoader(flutterJniFactory.provideFlutterJNI());
}
// DeferredComponentManager's intended default is null.
}
......@@ -127,7 +147,7 @@ public final class FlutterInjector {
public FlutterInjector build() {
fillDefaults();
return new FlutterInjector(flutterLoader, deferredComponentManager);
return new FlutterInjector(flutterLoader, deferredComponentManager, flutterJniFactory);
}
}
}
......@@ -919,12 +919,21 @@ public class FlutterActivity extends Activity
* <p>This method is called after {@link #provideFlutterEngine(Context)}.
*
* <p>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.
* method unless the FlutterEngine for this activity was externally created. To avoid the
* automatic plugin registration for implicitly created FlutterEngines, 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) {
if (delegate.isFlutterEngineFromHost()) {
// If the FlutterEngine was explicitly built and injected into this FlutterActivity, the
// builder should explicitly decide whether to automatically register plugins via the
// FlutterEngine's construction parameter or via the AndroidManifest metadata.
return;
}
GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
}
......
......@@ -910,6 +910,14 @@ public class FlutterFragment extends Fragment
return getArguments().getString(ARG_CACHED_ENGINE_ID, null);
}
/**
* Returns true a {@code FlutterEngine} was explicitly created and injected into the {@code
* FlutterFragment} rather than one that was created implicitly in the {@code FlutterFragment}.
*/
/* package */ boolean isFlutterEngineInjected() {
return delegate.isFlutterEngineFromHost();
}
/**
* Returns false if the {@link FlutterEngine} within this {@code FlutterFragment} should outlive
* the {@code FlutterFragment}, itself.
......
......@@ -580,12 +580,21 @@ public class FlutterFragmentActivity extends FragmentActivity
* <p>This method is called after {@link #provideFlutterEngine(Context)}.
*
* <p>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.
* method unless the FlutterEngine for this activity was externally created. To avoid the
* automatic plugin registration for implicitly created FlutterEngines, 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) {
if (flutterFragment.isFlutterEngineInjected()) {
// If the FlutterEngine was explicitly built and injected into this FlutterActivity, the
// builder should explicitly decide whether to automatically register plugins via the
// FlutterEngine's construction parameter or via the AndroidManifest metadata.
return;
}
GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
}
......
......@@ -20,6 +20,7 @@ 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;
......@@ -36,7 +37,6 @@ 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.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
......@@ -157,7 +157,7 @@ public class FlutterEngine {
* <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, new FlutterJNI(), dartVmArgs, true);
this(context, /* flutterLoader */ null, /* flutterJNI */ null, dartVmArgs, true);
}
/**
......@@ -173,7 +173,7 @@ public class FlutterEngine {
this(
context,
/* flutterLoader */ null,
new FlutterJNI(),
/* flutterJNI */ null,
dartVmArgs,
automaticallyRegisterPlugins);
}
......@@ -204,7 +204,7 @@ public class FlutterEngine {
this(
context,
/* flutterLoader */ null,
new FlutterJNI(),
/* flutterJNI */ null,
new PlatformViewsController(),
dartVmArgs,
automaticallyRegisterPlugins,
......@@ -282,6 +282,14 @@ public class FlutterEngine {
} 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();
......@@ -307,9 +315,8 @@ public class FlutterEngine {
this.localizationPlugin = new LocalizationPlugin(context, localizationChannel);
this.flutterJNI = flutterJNI;
if (flutterLoader == null) {
flutterLoader = FlutterInjector.instance().flutterLoader();
flutterLoader = injector.flutterLoader();
}
if (!flutterJNI.isAttached()) {
......@@ -320,7 +327,7 @@ public class FlutterEngine {
flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
flutterJNI.setPlatformViewsController(platformViewsController);
flutterJNI.setLocalizationPlugin(localizationPlugin);
flutterJNI.setDeferredComponentManager(FlutterInjector.instance().deferredComponentManager());
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
......@@ -342,7 +349,7 @@ public class FlutterEngine {
// Only automatically register plugins if both constructor parameter and
// loaded AndroidManifest config turn this feature on.
if (automaticallyRegisterPlugins && flutterLoader.automaticallyRegisterPlugins()) {
registerPlugins();
GeneratedPluginRegister.registerGeneratedPlugins(this);
}
}
......@@ -391,36 +398,6 @@ public class FlutterEngine {
newFlutterJNI); // FlutterJNI.
}
/**
* Registers all plugins that an app lists in its pubspec.yaml.
*
* <p>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.
*
* <p>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.
......
......@@ -282,6 +282,7 @@ public class FlutterJNI {
@NonNull private final Looper mainLooper; // cached to avoid synchronization on repeat access.
// Prefer using the FlutterJNI.Factory so it's easier to test.
public FlutterJNI() {
// We cache the main looper so that we can ensure calls are made on the main thread
// without consistently paying the synchronization cost of getMainLooper().
......@@ -1258,4 +1259,15 @@ public class FlutterJNI {
public interface AsyncWaitForVsyncDelegate {
void asyncWaitForVsync(final long cookie);
}
/**
* A factory for creating {@code FlutterJNI} instances. Useful for FlutterJNI injections during
* tests.
*/
public static class Factory {
/** @return a {@link FlutterJNI} instance. */
public FlutterJNI provideFlutterJNI() {
return new FlutterJNI();
}
}
}
......@@ -17,6 +17,7 @@ import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.BuildConfig;
import io.flutter.FlutterInjector;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.util.PathUtils;
......@@ -70,7 +71,7 @@ public class FlutterLoader {
/** Creates a {@code FlutterLoader} that uses a default constructed {@link FlutterJNI}. */
public FlutterLoader() {
this(new FlutterJNI());
this(FlutterInjector.instance().getFlutterJNIFactory().provideFlutterJNI());
}
/**
......
......@@ -33,11 +33,12 @@ public class GeneratedPluginRegister {
generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class);
registrationMethod.invoke(null, flutterEngine);
} catch (Exception e) {
Log.w(
Log.e(
TAG,
"Tried to automatically register plugins with FlutterEngine ("
+ flutterEngine
+ ") but could not find and invoke the GeneratedPluginRegistrant.");
+ ") but could not find or invoke the GeneratedPluginRegistrant.");
Log.e(TAG, "Received exception while registering", e);
}
}
}
......@@ -21,6 +21,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import io.flutter.FlutterInjector;
import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
......@@ -48,11 +49,18 @@ public class FlutterActivityTest {
@Before
public void setUp() {
GeneratedPluginRegistrant.clearRegisteredEngines();
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
when(mockFlutterJNI.isAttached()).thenReturn(true);
FlutterJNI.Factory mockFlutterJNIFactory = mock(FlutterJNI.Factory.class);
when(mockFlutterJNIFactory.provideFlutterJNI()).thenReturn(mockFlutterJNI);
FlutterInjector.setInstance(
new FlutterInjector.Builder().setFlutterJNIFactory(mockFlutterJNIFactory).build());
}
@After
public void tearDown() {
GeneratedPluginRegistrant.clearRegisteredEngines();
FlutterInjector.reset();
}
@Test
......@@ -211,12 +219,14 @@ public class FlutterActivityTest {
@Test
public void itRegistersPluginsAtConfigurationTime() {
FlutterActivity activity =
Robolectric.buildActivity(FlutterActivityWithProvidedEngine.class).get();
activity.onCreate(null);
Intent intent = FlutterActivity.createDefaultIntent(RuntimeEnvironment.application);
ActivityController<FlutterActivity> activityController =
Robolectric.buildActivity(FlutterActivity.class, intent);
FlutterActivity activity = activityController.get();
assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());
activity.configureFlutterEngine(activity.getFlutterEngine());
// This calls onAttach on FlutterActivityAndFragmentDelegate and subsequently
// configureFlutterEngine which registers the plugins.
activity.onCreate(null);
List<FlutterEngine> registeredEngines = GeneratedPluginRegistrant.getRegisteredEngines();
assertEquals(1, registeredEngines.size());
......@@ -282,6 +292,23 @@ public class FlutterActivityTest {
"Expected FakeFlutterPlugin onCreateCalled to be true", fakeFlutterPlugin.onCreateCalled);
}
@Test
public void itDoesNotRegisterPluginsTwiceWhenUsingACachedEngine() {
Intent intent =
new Intent(RuntimeEnvironment.application, FlutterActivityWithProvidedEngine.class);
ActivityController<FlutterActivityWithProvidedEngine> activityController =
Robolectric.buildActivity(FlutterActivityWithProvidedEngine.class, intent);
activityController.create();
FlutterActivityWithProvidedEngine flutterActivity = activityController.get();
flutterActivity.configureFlutterEngine(flutterActivity.getFlutterEngine());
List<FlutterEngine> registeredEngines = GeneratedPluginRegistrant.getRegisteredEngines();
// This might cause the plugins to be registered twice, once by the FlutterEngine constructor,
// and once by the default FlutterActivity.configureFlutterEngine implementation.
// Test that it doesn't happen.
assertEquals(1, registeredEngines.size());
}
static class FlutterActivityWithProvidedEngine extends FlutterActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
......@@ -293,10 +320,11 @@ public class FlutterActivityTest {
@Override
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
FlutterJNI flutterJNI = mock(FlutterJNI.class);
FlutterLoader flutterLoader = mock(FlutterLoader.class);
when(flutterJNI.isAttached()).thenReturn(true);
when(flutterLoader.automaticallyRegisterPlugins()).thenReturn(true);
return new FlutterEngine(
context, mock(FlutterLoader.class), flutterJNI, new String[] {}, false);
return new FlutterEngine(context, flutterLoader, flutterJNI, new String[] {}, true);
}
}
......
......@@ -16,6 +16,7 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.FlutterInjector;
import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterJNI;
......@@ -35,12 +36,20 @@ import org.robolectric.annotation.Config;
public class FlutterFragmentActivityTest {
@Before
public void setUp() {
FlutterInjector.reset();
GeneratedPluginRegistrant.clearRegisteredEngines();
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
when(mockFlutterJNI.isAttached()).thenReturn(true);
FlutterJNI.Factory mockFlutterJNIFactory = mock(FlutterJNI.Factory.class);
when(mockFlutterJNIFactory.provideFlutterJNI()).thenReturn(mockFlutterJNI);
FlutterInjector.setInstance(
new FlutterInjector.Builder().setFlutterJNIFactory(mockFlutterJNIFactory).build());
}
@After
public void tearDown() {
GeneratedPluginRegistrant.clearRegisteredEngines();
FlutterInjector.reset();
}
@Test
......@@ -76,7 +85,7 @@ public class FlutterFragmentActivityTest {
@Test
public void itRegistersPluginsAtConfigurationTime() {
FlutterFragmentActivity activity =
Robolectric.buildActivity(FlutterFragmentActivityWithProvidedEngine.class).get();
Robolectric.buildActivity(FlutterFragmentActivity.class).get();
assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());
// Calling onCreate on the FlutterFragmentActivity will create a FlutterFragment and
......@@ -89,6 +98,20 @@ public class FlutterFragmentActivityTest {
assertEquals(activity.getFlutterEngine(), registeredEngines.get(0));
}
@Test
public void itDoesNotRegisterPluginsTwiceWhenUsingACachedEngine() {
FlutterFragmentActivity activity =
Robolectric.buildActivity(FlutterFragmentActivityWithProvidedEngine.class).get();
activity.onCreate(null);
activity.configureFlutterEngine(activity.getFlutterEngine());
List<FlutterEngine> registeredEngines = GeneratedPluginRegistrant.getRegisteredEngines();
// This might cause the plugins to be registered twice, once by the FlutterEngine constructor,
// and once by the default FlutterFragmentActivity.configureFlutterEngine implementation.
// Test that it doesn't happen.
assertEquals(1, registeredEngines.size());
}
@Test
public void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1()
throws PackageManager.NameNotFoundException {
......@@ -157,10 +180,11 @@ public class FlutterFragmentActivityTest {
@Override
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
FlutterJNI flutterJNI = mock(FlutterJNI.class);
FlutterLoader flutterLoader = mock(FlutterLoader.class);
when(flutterJNI.isAttached()).thenReturn(true);
when(flutterLoader.automaticallyRegisterPlugins()).thenReturn(true);
return new FlutterEngine(
context, mock(FlutterLoader.class), flutterJNI, new String[] {}, false);
return new FlutterEngine(context, flutterLoader, flutterJNI, new String[] {}, true);
}
}
......
......@@ -35,6 +35,7 @@ import org.mockito.stubbing.Answer;
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)
......@@ -63,6 +64,9 @@ public class FlutterEngineTest {
@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
......@@ -78,6 +82,36 @@ public class FlutterEngineTest {
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<FlutterEngine> 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"));
assertEquals(
GeneratedPluginRegistrant.pluginRegistrationException,
ShadowLog.getLogsForTag("GeneratedPluginsRegister").get(1).throwable.getCause());
GeneratedPluginRegistrant.pluginRegistrationException = null;
}
@Test
public void itDoesNotAutomaticallyRegistersPluginsWhenFlutterLoaderDisablesIt() {
assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty());
......@@ -105,18 +139,6 @@ public class FlutterEngineTest {
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.
......@@ -220,6 +242,7 @@ public class FlutterEngineTest {
verify(mockFlutterLoader, times(1)).startInitialization(any());
verify(mockFlutterLoader, times(1)).ensureInitializationComplete(any(), any());
FlutterInjector.reset();
}
@Test
......
......@@ -13,6 +13,7 @@ import java.util.List;
@VisibleForTesting
public class GeneratedPluginRegistrant {
private static final List<FlutterEngine> registeredEngines = new ArrayList<>();
public static RuntimeException pluginRegistrationException;
/**
* The one and only method currently generated by the tool.
......@@ -21,6 +22,9 @@ public class GeneratedPluginRegistrant {
* all registered engines instead.
*/
public static void registerWith(FlutterEngine engine) {
if (pluginRegistrationException != null) {
throw pluginRegistrationException;
}
registeredEngines.add(engine);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册