未验证 提交 15f5696c 编写于 作者: X xster 提交者: GitHub

Add a java injector for testing (#20789)

上级 cb9439af
...@@ -694,6 +694,7 @@ FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_poo ...@@ -694,6 +694,7 @@ FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_poo
FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_pool_unittests.cc FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_pool_unittests.cc
FILE: ../../../flutter/shell/platform/android/flutter_main.cc FILE: ../../../flutter/shell/platform/android/flutter_main.cc
FILE: ../../../flutter/shell/platform/android/flutter_main.h FILE: ../../../flutter/shell/platform/android/flutter_main.h
FILE: ../../../flutter/shell/platform/android/io/flutter/FlutterInjector.java
FILE: ../../../flutter/shell/platform/android/io/flutter/Log.java FILE: ../../../flutter/shell/platform/android/io/flutter/Log.java
FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivity.java FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivity.java
FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java
......
...@@ -117,6 +117,7 @@ embedding_sources_jar_filename = "$embedding_artifact_id-sources.jar" ...@@ -117,6 +117,7 @@ embedding_sources_jar_filename = "$embedding_artifact_id-sources.jar"
embedding_source_jar_path = "$root_out_dir/$embedding_sources_jar_filename" embedding_source_jar_path = "$root_out_dir/$embedding_sources_jar_filename"
android_java_sources = [ android_java_sources = [
"io/flutter/FlutterInjector.java",
"io/flutter/Log.java", "io/flutter/Log.java",
"io/flutter/app/FlutterActivity.java", "io/flutter/app/FlutterActivity.java",
"io/flutter/app/FlutterActivityDelegate.java", "io/flutter/app/FlutterActivityDelegate.java",
...@@ -415,6 +416,7 @@ action("robolectric_tests") { ...@@ -415,6 +416,7 @@ action("robolectric_tests") {
jar_path = "$root_out_dir/robolectric_tests.jar" jar_path = "$root_out_dir/robolectric_tests.jar"
sources = [ sources = [
"test/io/flutter/FlutterInjectorTest.java",
"test/io/flutter/FlutterTestSuite.java", "test/io/flutter/FlutterTestSuite.java",
"test/io/flutter/SmokeTest.java", "test/io/flutter/SmokeTest.java",
"test/io/flutter/embedding/android/AndroidKeyProcessorTest.java", "test/io/flutter/embedding/android/AndroidKeyProcessorTest.java",
...@@ -434,6 +436,7 @@ action("robolectric_tests") { ...@@ -434,6 +436,7 @@ action("robolectric_tests") {
"test/io/flutter/embedding/engine/RenderingComponentTest.java", "test/io/flutter/embedding/engine/RenderingComponentTest.java",
"test/io/flutter/embedding/engine/dart/DartExecutorTest.java", "test/io/flutter/embedding/engine/dart/DartExecutorTest.java",
"test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java", "test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java",
"test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java",
"test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java", "test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java",
"test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java", "test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java",
"test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java", "test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java",
......
// 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;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import io.flutter.embedding.engine.loader.FlutterLoader;
/**
* This class is a simple dependency injector for the relatively thin Android part of the Flutter
* engine.
*
* <p>This simple solution is used facilitate testability without bringing in heavier
* app-development centric dependency injection frameworks such as Guice or Dagger2 or spreading
* construction injection everywhere.
*/
public final class FlutterInjector {
private static FlutterInjector instance;
private static boolean accessed;
/**
* Use {@link FlutterInjector.Builder} to specify members to be injected via the static {@code
* FlutterInjector}.
*
* <p>This can only be called at the beginning of the program before the {@link #instance()} is
* accessed.
*/
public static void setInstance(@NonNull FlutterInjector injector) {
if (accessed) {
throw new IllegalStateException(
"Cannot change the FlutterInjector instance once it's been "
+ "read. If you're trying to dependency inject, be sure to do so at the beginning of "
+ "the program");
}
instance = injector;
}
/**
* Retrieve the static instance of the {@code FlutterInjector} to use in your program.
*
* <p>Once you access it, you can no longer change the values injected.
*
* <p>If no override is provided for the injector, reasonable defaults are provided.
*/
public static FlutterInjector instance() {
accessed = true;
if (instance == null) {
instance = new Builder().build();
}
return instance;
}
// This whole class is here to enable testing so to test the thing that lets you test, some degree
// of hack is needed.
@VisibleForTesting
public static void reset() {
accessed = false;
instance = null;
}
private FlutterInjector(boolean shouldLoadNative, @NonNull FlutterLoader flutterLoader) {
this.shouldLoadNative = shouldLoadNative;
this.flutterLoader = flutterLoader;
}
private boolean shouldLoadNative;
private FlutterLoader flutterLoader;
/**
* Returns whether the Flutter Android engine embedding should load the native C++ engine.
*
* <p>Useful for testing since JVM tests via Robolectric can't load native libraries.
*/
public boolean shouldLoadNative() {
return shouldLoadNative;
}
/** Returns the {@link FlutterLoader} instance to use for the Flutter Android engine embedding. */
@NonNull
public FlutterLoader flutterLoader() {
return flutterLoader;
}
/**
* Builder used to supply a custom FlutterInjector instance to {@link
* FlutterInjector#setInstance(FlutterInjector)}.
*
* <p>Non-overriden values have reasonable defaults.
*/
public static final class Builder {
private boolean shouldLoadNative = true;
/**
* Sets whether the Flutter Android engine embedding should load the native C++ engine.
*
* <p>Useful for testing since JVM tests via Robolectric can't load native libraries.
*
* <p>Defaults to true.
*/
public Builder setShouldLoadNative(boolean shouldLoadNative) {
this.shouldLoadNative = shouldLoadNative;
return this;
}
private FlutterLoader flutterLoader;
/**
* Sets a {@link FlutterLoader} override.
*
* <p>A reasonable default will be used if unspecified.
*/
public Builder setFlutterLoader(@NonNull FlutterLoader flutterLoader) {
this.flutterLoader = flutterLoader;
return this;
}
private void fillDefaults() {
if (flutterLoader == null) {
flutterLoader = new FlutterLoader();
}
}
/**
* Builds a {@link FlutterInjector} from the builder. Unspecified properties will have
* reasonable defaults.
*/
public FlutterInjector build() {
fillDefaults();
System.out.println("should load native is " + shouldLoadNative);
return new FlutterInjector(shouldLoadNative, flutterLoader);
}
}
}
...@@ -35,7 +35,8 @@ import io.flutter.view.FlutterView; ...@@ -35,7 +35,8 @@ import io.flutter.view.FlutterView;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* Class that performs the actual work of tying Android {@link Activity} instances to Flutter. * Deprecated class that performs the actual work of tying Android {@link Activity} instances to
* Flutter.
* *
* <p>This exists as a dedicated class (as opposed to being integrated directly into {@link * <p>This exists as a dedicated class (as opposed to being integrated directly into {@link
* FlutterActivity}) to facilitate applications that don't wish to subclass {@code FlutterActivity}. * FlutterActivity}) to facilitate applications that don't wish to subclass {@code FlutterActivity}.
...@@ -48,6 +49,10 @@ import java.util.ArrayList; ...@@ -48,6 +49,10 @@ import java.util.ArrayList;
* FlutterActivityEvents} from your activity to an instance of this class. Optionally, you can make * FlutterActivityEvents} from your activity to an instance of this class. Optionally, you can make
* your activity implement {@link PluginRegistry} and/or {@link * your activity implement {@link PluginRegistry} and/or {@link
* io.flutter.view.FlutterView.Provider} and forward those methods to this class as well. * io.flutter.view.FlutterView.Provider} and forward those methods to this class as well.
*
* <p>Deprecation: {@link io.flutter.embedding.android.FlutterActivity} is the new API that now
* replaces this class and {@link io.flutter.app.FlutterActivity}. See
* https://flutter.dev/go/android-project-migration for more migration details.
*/ */
public final class FlutterActivityDelegate public final class FlutterActivityDelegate
implements FlutterActivityEvents, FlutterView.Provider, PluginRegistry { implements FlutterActivityEvents, FlutterView.Provider, PluginRegistry {
......
...@@ -7,7 +7,7 @@ package io.flutter.app; ...@@ -7,7 +7,7 @@ package io.flutter.app;
import android.app.Activity; import android.app.Activity;
import android.app.Application; import android.app.Application;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import io.flutter.view.FlutterMain; import io.flutter.FlutterInjector;
/** /**
* Flutter implementation of {@link android.app.Application}, managing application-level global * Flutter implementation of {@link android.app.Application}, managing application-level global
...@@ -21,7 +21,7 @@ public class FlutterApplication extends Application { ...@@ -21,7 +21,7 @@ public class FlutterApplication extends Application {
@CallSuper @CallSuper
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
FlutterMain.startInitialization(this); FlutterInjector.instance().flutterLoader().startInitialization(this);
} }
private Activity mCurrentActivity = null; private Activity mCurrentActivity = null;
......
...@@ -7,6 +7,7 @@ package io.flutter.embedding.engine; ...@@ -7,6 +7,7 @@ package io.flutter.embedding.engine;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import io.flutter.FlutterInjector;
import io.flutter.Log; import io.flutter.Log;
import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.loader.FlutterLoader;
...@@ -131,7 +132,8 @@ public class FlutterEngine { ...@@ -131,7 +132,8 @@ public class FlutterEngine {
* <p>In order to pass Dart VM initialization arguments (see {@link * <p>In order to pass Dart VM initialization arguments (see {@link
* io.flutter.embedding.engine.FlutterShellArgs}) when creating the VM, manually set the * io.flutter.embedding.engine.FlutterShellArgs}) when creating the VM, manually set the
* initialization arguments by calling {@link FlutterLoader#startInitialization(Context)} and * initialization arguments by calling {@link FlutterLoader#startInitialization(Context)} and
* {@link FlutterLoader#ensureInitializationComplete(Context, String[])}. * {@link FlutterLoader#ensureInitializationComplete(Context, String[])} before constructing the
* engine.
*/ */
public FlutterEngine(@NonNull Context context) { public FlutterEngine(@NonNull Context context) {
this(context, null); this(context, null);
...@@ -143,7 +145,7 @@ public class FlutterEngine { ...@@ -143,7 +145,7 @@ public class FlutterEngine {
* <p>If the Dart VM has already started, the given arguments will have no effect. * <p>If the Dart VM has already started, the given arguments will have no effect.
*/ */
public FlutterEngine(@NonNull Context context, @Nullable String[] dartVmArgs) { public FlutterEngine(@NonNull Context context, @Nullable String[] dartVmArgs) {
this(context, FlutterLoader.getInstance(), new FlutterJNI(), dartVmArgs, true); this(context, /* flutterLoader */ null, new FlutterJNI(), dartVmArgs, true);
} }
/** /**
...@@ -158,7 +160,7 @@ public class FlutterEngine { ...@@ -158,7 +160,7 @@ public class FlutterEngine {
boolean automaticallyRegisterPlugins) { boolean automaticallyRegisterPlugins) {
this( this(
context, context,
FlutterLoader.getInstance(), /* flutterLoader */ null,
new FlutterJNI(), new FlutterJNI(),
dartVmArgs, dartVmArgs,
automaticallyRegisterPlugins); automaticallyRegisterPlugins);
...@@ -189,7 +191,7 @@ public class FlutterEngine { ...@@ -189,7 +191,7 @@ public class FlutterEngine {
boolean waitForRestorationData) { boolean waitForRestorationData) {
this( this(
context, context,
FlutterLoader.getInstance(), /* flutterLoader */ null,
new FlutterJNI(), new FlutterJNI(),
new PlatformViewsController(), new PlatformViewsController(),
dartVmArgs, dartVmArgs,
...@@ -206,7 +208,7 @@ public class FlutterEngine { ...@@ -206,7 +208,7 @@ public class FlutterEngine {
*/ */
public FlutterEngine( public FlutterEngine(
@NonNull Context context, @NonNull Context context,
@NonNull FlutterLoader flutterLoader, @Nullable FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI) { @NonNull FlutterJNI flutterJNI) {
this(context, flutterLoader, flutterJNI, null, true); this(context, flutterLoader, flutterJNI, null, true);
} }
...@@ -219,7 +221,7 @@ public class FlutterEngine { ...@@ -219,7 +221,7 @@ public class FlutterEngine {
*/ */
public FlutterEngine( public FlutterEngine(
@NonNull Context context, @NonNull Context context,
@NonNull FlutterLoader flutterLoader, @Nullable FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI, @NonNull FlutterJNI flutterJNI,
@Nullable String[] dartVmArgs, @Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins) { boolean automaticallyRegisterPlugins) {
...@@ -238,7 +240,7 @@ public class FlutterEngine { ...@@ -238,7 +240,7 @@ public class FlutterEngine {
*/ */
public FlutterEngine( public FlutterEngine(
@NonNull Context context, @NonNull Context context,
@NonNull FlutterLoader flutterLoader, @Nullable FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI, @NonNull FlutterJNI flutterJNI,
@NonNull PlatformViewsController platformViewsController, @NonNull PlatformViewsController platformViewsController,
@Nullable String[] dartVmArgs, @Nullable String[] dartVmArgs,
...@@ -256,7 +258,7 @@ public class FlutterEngine { ...@@ -256,7 +258,7 @@ public class FlutterEngine {
/** Fully configurable {@code FlutterEngine} constructor. */ /** Fully configurable {@code FlutterEngine} constructor. */
public FlutterEngine( public FlutterEngine(
@NonNull Context context, @NonNull Context context,
@NonNull FlutterLoader flutterLoader, @Nullable FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI, @NonNull FlutterJNI flutterJNI,
@NonNull PlatformViewsController platformViewsController, @NonNull PlatformViewsController platformViewsController,
@Nullable String[] dartVmArgs, @Nullable String[] dartVmArgs,
...@@ -280,6 +282,9 @@ public class FlutterEngine { ...@@ -280,6 +282,9 @@ public class FlutterEngine {
this.localizationPlugin = new LocalizationPlugin(context, localizationChannel); this.localizationPlugin = new LocalizationPlugin(context, localizationChannel);
this.flutterJNI = flutterJNI; this.flutterJNI = flutterJNI;
if (flutterLoader == null) {
flutterLoader = FlutterInjector.instance().flutterLoader();
}
flutterLoader.startInitialization(context.getApplicationContext()); flutterLoader.startInitialization(context.getApplicationContext());
flutterLoader.ensureInitializationComplete(context, dartVmArgs); flutterLoader.ensureInitializationComplete(context, dartVmArgs);
......
...@@ -15,8 +15,8 @@ import java.util.*; ...@@ -15,8 +15,8 @@ import java.util.*;
* <p>The term "shell" refers to the native code that adapts Flutter to different platforms. * <p>The term "shell" refers to the native code that adapts Flutter to different platforms.
* Flutter's Android Java code initializes a native "shell" and passes these arguments to that * Flutter's Android Java code initializes a native "shell" and passes these arguments to that
* native shell when it is initialized. See {@link * native shell when it is initialized. See {@link
* io.flutter.view.FlutterMain#ensureInitializationComplete(Context, String[])} for more * io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context, String[])}
* information. * for more information.
*/ */
@SuppressWarnings({"WeakerAccess", "unused"}) @SuppressWarnings({"WeakerAccess", "unused"})
public class FlutterShellArgs { public class FlutterShellArgs {
......
...@@ -8,12 +8,13 @@ import android.content.res.AssetManager; ...@@ -8,12 +8,13 @@ import android.content.res.AssetManager;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import io.flutter.FlutterInjector;
import io.flutter.Log; import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.StringCodec; import io.flutter.plugin.common.StringCodec;
import io.flutter.view.FlutterCallbackInformation; import io.flutter.view.FlutterCallbackInformation;
import io.flutter.view.FlutterMain;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
...@@ -250,9 +251,19 @@ public class DartExecutor implements BinaryMessenger { ...@@ -250,9 +251,19 @@ public class DartExecutor implements BinaryMessenger {
* that entrypoint and other assets required for Dart execution. * that entrypoint and other assets required for Dart execution.
*/ */
public static class DartEntrypoint { public static class DartEntrypoint {
/**
* Create a DartEntrypoint pointing to the default Flutter assets location with a default Dart
* entrypoint.
*/
@NonNull @NonNull
public static DartEntrypoint createDefault() { public static DartEntrypoint createDefault() {
return new DartEntrypoint(FlutterMain.findAppBundlePath(), "main"); FlutterLoader flutterLoader = FlutterInjector.instance().flutterLoader();
if (!flutterLoader.initialized()) {
throw new AssertionError(
"DartEntrypoints can only be created once a FlutterEngine is created.");
}
return new DartEntrypoint(flutterLoader.findAppBundlePath(), "main");
} }
/** The path within the AssetManager where the app will look for assets. */ /** The path within the AssetManager where the app will look for assets. */
......
...@@ -15,6 +15,7 @@ import android.view.WindowManager; ...@@ -15,6 +15,7 @@ import android.view.WindowManager;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import io.flutter.BuildConfig; import io.flutter.BuildConfig;
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.util.PathUtils; import io.flutter.util.PathUtils;
import io.flutter.view.VsyncWaiter; import io.flutter.view.VsyncWaiter;
...@@ -47,7 +48,10 @@ public class FlutterLoader { ...@@ -47,7 +48,10 @@ public class FlutterLoader {
* <p>The returned instance loads Flutter native libraries in the standard way. A singleton object * <p>The returned instance loads Flutter native libraries in the standard way. A singleton object
* is used instead of static methods to facilitate testing without actually running native library * is used instead of static methods to facilitate testing without actually running native library
* linking. * linking.
*
* @deprecated Use the {@link io.flutter.FlutterInjector} instead.
*/ */
@Deprecated
@NonNull @NonNull
public static FlutterLoader getInstance() { public static FlutterLoader getInstance() {
if (instance == null) { if (instance == null) {
...@@ -56,13 +60,6 @@ public class FlutterLoader { ...@@ -56,13 +60,6 @@ public class FlutterLoader {
return instance; return instance;
} }
@NonNull
public static FlutterLoader getInstanceForTest(FlutterApplicationInfo flutterApplicationInfo) {
FlutterLoader loader = new FlutterLoader();
loader.flutterApplicationInfo = flutterApplicationInfo;
return loader;
}
private boolean initialized = false; private boolean initialized = false;
@Nullable private Settings settings; @Nullable private Settings settings;
private long initStartTimestampMillis; private long initStartTimestampMillis;
...@@ -128,7 +125,9 @@ public class FlutterLoader { ...@@ -128,7 +125,9 @@ public class FlutterLoader {
public InitResult call() { public InitResult call() {
ResourceExtractor resourceExtractor = initResources(appContext); ResourceExtractor resourceExtractor = initResources(appContext);
System.loadLibrary("flutter"); if (FlutterInjector.instance().shouldLoadNative()) {
System.loadLibrary("flutter");
}
// Prefetch the default font manager as soon as possible on a background thread. // Prefetch the default font manager as soon as possible on a background thread.
// It helps to reduce time cost of engine setup that blocks the platform thread. // It helps to reduce time cost of engine setup that blocks the platform thread.
...@@ -231,13 +230,15 @@ public class FlutterLoader { ...@@ -231,13 +230,15 @@ public class FlutterLoader {
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis; long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
FlutterJNI.nativeInit( if (FlutterInjector.instance().shouldLoadNative()) {
applicationContext, FlutterJNI.nativeInit(
shellArgs.toArray(new String[0]), applicationContext,
kernelPath, shellArgs.toArray(new String[0]),
result.appStoragePath, kernelPath,
result.engineCachesPath, result.appStoragePath,
initTimeMillis); result.engineCachesPath,
initTimeMillis);
}
initialized = true; initialized = true;
} catch (Exception e) { } catch (Exception e) {
...@@ -293,6 +294,11 @@ public class FlutterLoader { ...@@ -293,6 +294,11 @@ public class FlutterLoader {
}); });
} }
/** Returns whether the FlutterLoader has finished loading the native library. */
public boolean initialized() {
return initialized;
}
/** Extract assets out of the APK that need to be cached as uncompressed files on disk. */ /** Extract assets out of the APK that need to be cached as uncompressed files on disk. */
private ResourceExtractor initResources(@NonNull Context applicationContext) { private ResourceExtractor initResources(@NonNull Context applicationContext) {
ResourceExtractor resourceExtractor = null; ResourceExtractor resourceExtractor = null;
......
...@@ -7,6 +7,7 @@ package io.flutter.embedding.engine.plugins.shim; ...@@ -7,6 +7,7 @@ package io.flutter.embedding.engine.plugins.shim;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import io.flutter.FlutterInjector;
import io.flutter.Log; import io.flutter.Log;
import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityAware;
...@@ -14,7 +15,6 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; ...@@ -14,7 +15,6 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.platform.PlatformViewRegistry; import io.flutter.plugin.platform.PlatformViewRegistry;
import io.flutter.view.FlutterMain;
import io.flutter.view.FlutterView; import io.flutter.view.FlutterView;
import io.flutter.view.TextureRegistry; import io.flutter.view.TextureRegistry;
import java.util.HashSet; import java.util.HashSet;
...@@ -85,12 +85,12 @@ class ShimRegistrar implements PluginRegistry.Registrar, FlutterPlugin, Activity ...@@ -85,12 +85,12 @@ class ShimRegistrar implements PluginRegistry.Registrar, FlutterPlugin, Activity
@Override @Override
public String lookupKeyForAsset(String asset) { public String lookupKeyForAsset(String asset) {
return FlutterMain.getLookupKeyForAsset(asset); return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset);
} }
@Override @Override
public String lookupKeyForAsset(String asset, String packageName) { public String lookupKeyForAsset(String asset, String packageName) {
return FlutterMain.getLookupKeyForAsset(asset, packageName); return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset, packageName);
} }
@Override @Override
......
...@@ -8,7 +8,7 @@ import android.content.Context; ...@@ -8,7 +8,7 @@ import android.content.Context;
import android.os.Handler; import android.os.Handler;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.loader.FlutterLoader;
/** /**
...@@ -42,10 +42,7 @@ public class FlutterMain { ...@@ -42,10 +42,7 @@ public class FlutterMain {
* @param applicationContext The Android application context. * @param applicationContext The Android application context.
*/ */
public static void startInitialization(@NonNull Context applicationContext) { public static void startInitialization(@NonNull Context applicationContext) {
if (isRunningInRobolectricTest) { FlutterInjector.instance().flutterLoader().startInitialization(applicationContext);
return;
}
FlutterLoader.getInstance().startInitialization(applicationContext);
} }
/** /**
...@@ -61,12 +58,9 @@ public class FlutterMain { ...@@ -61,12 +58,9 @@ public class FlutterMain {
*/ */
public static void startInitialization( public static void startInitialization(
@NonNull Context applicationContext, @NonNull Settings settings) { @NonNull Context applicationContext, @NonNull Settings settings) {
if (isRunningInRobolectricTest) {
return;
}
FlutterLoader.Settings newSettings = new FlutterLoader.Settings(); FlutterLoader.Settings newSettings = new FlutterLoader.Settings();
newSettings.setLogTag(settings.getLogTag()); newSettings.setLogTag(settings.getLogTag());
FlutterLoader.getInstance().startInitialization(applicationContext, newSettings); FlutterInjector.instance().flutterLoader().startInitialization(applicationContext, newSettings);
} }
/** /**
...@@ -79,10 +73,9 @@ public class FlutterMain { ...@@ -79,10 +73,9 @@ public class FlutterMain {
*/ */
public static void ensureInitializationComplete( public static void ensureInitializationComplete(
@NonNull Context applicationContext, @Nullable String[] args) { @NonNull Context applicationContext, @Nullable String[] args) {
if (isRunningInRobolectricTest) { FlutterInjector.instance()
return; .flutterLoader()
} .ensureInitializationComplete(applicationContext, args);
FlutterLoader.getInstance().ensureInitializationComplete(applicationContext, args);
} }
/** /**
...@@ -94,22 +87,20 @@ public class FlutterMain { ...@@ -94,22 +87,20 @@ public class FlutterMain {
@Nullable String[] args, @Nullable String[] args,
@NonNull Handler callbackHandler, @NonNull Handler callbackHandler,
@NonNull Runnable callback) { @NonNull Runnable callback) {
if (isRunningInRobolectricTest) { FlutterInjector.instance()
return; .flutterLoader()
}
FlutterLoader.getInstance()
.ensureInitializationCompleteAsync(applicationContext, args, callbackHandler, callback); .ensureInitializationCompleteAsync(applicationContext, args, callbackHandler, callback);
} }
@NonNull @NonNull
public static String findAppBundlePath() { public static String findAppBundlePath() {
return FlutterLoader.getInstance().findAppBundlePath(); return FlutterInjector.instance().flutterLoader().findAppBundlePath();
} }
@Deprecated @Deprecated
@Nullable @Nullable
public static String findAppBundlePath(@NonNull Context applicationContext) { public static String findAppBundlePath(@NonNull Context applicationContext) {
return FlutterLoader.getInstance().findAppBundlePath(); return FlutterInjector.instance().flutterLoader().findAppBundlePath();
} }
/** /**
...@@ -121,7 +112,7 @@ public class FlutterMain { ...@@ -121,7 +112,7 @@ public class FlutterMain {
*/ */
@NonNull @NonNull
public static String getLookupKeyForAsset(@NonNull String asset) { public static String getLookupKeyForAsset(@NonNull String asset) {
return FlutterLoader.getInstance().getLookupKeyForAsset(asset); return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset);
} }
/** /**
...@@ -135,23 +126,6 @@ public class FlutterMain { ...@@ -135,23 +126,6 @@ public class FlutterMain {
*/ */
@NonNull @NonNull
public static String getLookupKeyForAsset(@NonNull String asset, @NonNull String packageName) { public static String getLookupKeyForAsset(@NonNull String asset, @NonNull String packageName) {
return FlutterLoader.getInstance().getLookupKeyForAsset(asset, packageName); return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset, packageName);
}
private static boolean isRunningInRobolectricTest = false;
/*
* Indicates whether we are currently running in a Robolectric Test.
*
* <p> Flutter cannot be initialized inside a Robolectric environment since it cannot load
* native libraries.
*
* @deprecated Use the new embedding (io.flutter.embedding) instead which provides better
* modularity for testing.
*/
@Deprecated
@VisibleForTesting
public static void setIsRunningInRobolectricTest(boolean isRunningInRobolectricTest) {
FlutterMain.isRunningInRobolectricTest = isRunningInRobolectricTest;
} }
} }
// 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import io.flutter.embedding.engine.loader.FlutterLoader;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterInjectorTest {
@Mock FlutterLoader mockFlutterLoader;
@Before
public void setUp() {
// Since the intent is to have a convenient static class to use for production.
FlutterInjector.reset();
MockitoAnnotations.initMocks(this);
}
@Test
public void itHasSomeReasonableDefaults() {
// Implicitly builds when first accessed.
FlutterInjector injector = FlutterInjector.instance();
assertNotNull(injector.flutterLoader());
assertTrue(injector.shouldLoadNative());
}
@Test
public void canPartiallyOverride() {
FlutterInjector.setInstance(
new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
FlutterInjector injector = FlutterInjector.instance();
assertEquals(injector.flutterLoader(), mockFlutterLoader);
assertTrue(injector.shouldLoadNative());
}
@Test()
public void cannotBeChangedOnceRead() {
FlutterInjector.instance();
assertThrows(
IllegalStateException.class,
() -> {
FlutterInjector.setInstance(
new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
});
}
}
...@@ -16,6 +16,8 @@ import io.flutter.embedding.engine.FlutterEnginePluginRegistryTest; ...@@ -16,6 +16,8 @@ import io.flutter.embedding.engine.FlutterEnginePluginRegistryTest;
import io.flutter.embedding.engine.FlutterJNITest; import io.flutter.embedding.engine.FlutterJNITest;
import io.flutter.embedding.engine.LocalizationPluginTest; import io.flutter.embedding.engine.LocalizationPluginTest;
import io.flutter.embedding.engine.RenderingComponentTest; import io.flutter.embedding.engine.RenderingComponentTest;
import io.flutter.embedding.engine.loader.ApplicationInfoLoaderTest;
import io.flutter.embedding.engine.loader.FlutterLoaderTest;
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistryTest; import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistryTest;
import io.flutter.embedding.engine.renderer.FlutterRendererTest; import io.flutter.embedding.engine.renderer.FlutterRendererTest;
import io.flutter.embedding.engine.systemchannels.KeyEventChannelTest; import io.flutter.embedding.engine.systemchannels.KeyEventChannelTest;
...@@ -44,6 +46,7 @@ import test.io.flutter.embedding.engine.dart.DartExecutorTest; ...@@ -44,6 +46,7 @@ import test.io.flutter.embedding.engine.dart.DartExecutorTest;
@SuiteClasses({ @SuiteClasses({
AccessibilityBridgeTest.class, AccessibilityBridgeTest.class,
AndroidKeyProcessorTest.class, AndroidKeyProcessorTest.class,
ApplicationInfoLoaderTest.class,
DartExecutorTest.class, DartExecutorTest.class,
FlutterActivityAndFragmentDelegateTest.class, FlutterActivityAndFragmentDelegateTest.class,
FlutterActivityTest.class, FlutterActivityTest.class,
...@@ -53,8 +56,11 @@ import test.io.flutter.embedding.engine.dart.DartExecutorTest; ...@@ -53,8 +56,11 @@ import test.io.flutter.embedding.engine.dart.DartExecutorTest;
FlutterEngineTest.class, FlutterEngineTest.class,
FlutterFragmentActivityTest.class, FlutterFragmentActivityTest.class,
FlutterFragmentTest.class, FlutterFragmentTest.class,
FlutterInjectorTest.class,
FlutterJNITest.class, FlutterJNITest.class,
FlutterLaunchTests.class, FlutterLaunchTests.class,
FlutterLoaderTest.class,
FlutterShellArgsTest.class,
FlutterRendererTest.class, FlutterRendererTest.class,
FlutterShellArgsTest.class, FlutterShellArgsTest.class,
FlutterViewTest.class, FlutterViewTest.class,
......
...@@ -3,6 +3,7 @@ package test.io.flutter.embedding.engine; ...@@ -3,6 +3,7 @@ package test.io.flutter.embedding.engine;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
...@@ -10,6 +11,7 @@ import static org.mockito.Mockito.verify; ...@@ -10,6 +11,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.loader.FlutterLoader;
...@@ -137,4 +139,18 @@ public class FlutterEngineTest { ...@@ -137,4 +139,18 @@ public class FlutterEngineTest {
verify(context, atLeast(1)).getApplicationContext(); verify(context, atLeast(1)).getApplicationContext();
} }
@Test
public void itCanUseFlutterLoaderInjectionViaFlutterInjector() {
FlutterInjector.reset();
FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
FlutterInjector.setInstance(
new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
Context mockContext = mock(Context.class);
new FlutterEngine(mockContext, null, flutterJNI);
verify(mockFlutterLoader, times(1)).startInitialization(any());
verify(mockFlutterLoader, times(1)).ensureInitializationComplete(any(), any());
}
} }
// 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 test.io.flutter.embedding.engine; package test.io.flutter.embedding.engine;
import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.loader.FlutterApplicationInfo;
import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.FlutterPlugin;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
...@@ -22,47 +24,19 @@ import org.robolectric.annotation.Config; ...@@ -22,47 +24,19 @@ import org.robolectric.annotation.Config;
@Config(manifest = Config.NONE) @Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class PluginComponentTest { public class PluginComponentTest {
@Before
public void setUp() {
FlutterInjector.reset();
}
@Test @Test
public void pluginsCanAccessFlutterAssetPaths() { public void pluginsCanAccessFlutterAssetPaths() {
// Setup test. // Setup test.
FlutterInjector.setInstance(new FlutterInjector.Builder().setShouldLoadNative(false).build());
FlutterJNI flutterJNI = mock(FlutterJNI.class); FlutterJNI flutterJNI = mock(FlutterJNI.class);
when(flutterJNI.isAttached()).thenReturn(true); when(flutterJNI.isAttached()).thenReturn(true);
FlutterApplicationInfo emptyInfo =
new FlutterApplicationInfo(null, null, null, null, null, null, false, false); FlutterLoader flutterLoader = new FlutterLoader();
// FlutterLoader is the object to which the PluginRegistry defers for obtaining
// the path to a Flutter asset. Ideally in this component test we would use a
// real FlutterLoader and directly verify the relationship between FlutterAssets
// and FlutterLoader. However, a real FlutterLoader cannot be used in a JVM test
// because it would attempt to load native libraries. Therefore, we create a fake
// FlutterLoader, but then we defer the corresponding asset lookup methods to the
// real FlutterLoader singleton. This test ends up verifying that when FlutterAssets
// is queried for an asset path, it returns the real expected path based on real
// FlutterLoader behavior.
FlutterLoader flutterLoader = mock(FlutterLoader.class);
when(flutterLoader.getLookupKeyForAsset(any(String.class)))
.thenAnswer(
new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
// Defer to a real FlutterLoader to return the asset path.
String fileNameOrSubpath = (String) invocation.getArguments()[0];
return FlutterLoader.getInstanceForTest(emptyInfo)
.getLookupKeyForAsset(fileNameOrSubpath);
}
});
when(flutterLoader.getLookupKeyForAsset(any(String.class), any(String.class)))
.thenAnswer(
new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
// Defer to a real FlutterLoader to return the asset path.
String fileNameOrSubpath = (String) invocation.getArguments()[0];
String packageName = (String) invocation.getArguments()[1];
return FlutterLoader.getInstanceForTest(emptyInfo)
.getLookupKeyForAsset(fileNameOrSubpath, packageName);
}
});
// Execute behavior under test. // Execute behavior under test.
FlutterEngine flutterEngine = FlutterEngine flutterEngine =
...@@ -82,6 +56,7 @@ public class PluginComponentTest { ...@@ -82,6 +56,7 @@ public class PluginComponentTest {
assertEquals( assertEquals(
"flutter_assets/packages/fakepackage/some/path/fake_asset.jpg", "flutter_assets/packages/fakepackage/some/path/fake_asset.jpg",
plugin.getAssetPathBasedOnSubpathAndPackage()); plugin.getAssetPathBasedOnSubpathAndPackage());
FlutterInjector.reset();
} }
private static class PluginThatAccessesAssets implements FlutterPlugin { private static class PluginThatAccessesAssets implements FlutterPlugin {
......
package test.io.flutter.embedding.engine.dart; package test.io.flutter.embedding.engine.dart;
import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertNotNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
...@@ -9,17 +11,31 @@ import static org.mockito.Mockito.verify; ...@@ -9,17 +11,31 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;
import io.flutter.embedding.engine.loader.FlutterLoader;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
@Config(manifest = Config.NONE) @Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public class DartExecutorTest { public class DartExecutorTest {
@Mock FlutterLoader mockFlutterLoader;
@Before
public void setUp() {
FlutterInjector.reset();
MockitoAnnotations.initMocks(this);
}
@Test @Test
public void itSendsBinaryMessages() { public void itSendsBinaryMessages() {
// Setup test. // Setup test.
...@@ -49,4 +65,24 @@ public class DartExecutorTest { ...@@ -49,4 +65,24 @@ public class DartExecutorTest {
dartExecutor.notifyLowMemoryWarning(); dartExecutor.notifyLowMemoryWarning();
verify(mockFlutterJNI, times(1)).notifyLowMemoryWarning(); verify(mockFlutterJNI, times(1)).notifyLowMemoryWarning();
} }
@Test
public void itThrowsWhenCreatingADefaultDartEntrypointWithAnUninitializedFlutterLoader() {
assertThrows(
AssertionError.class,
() -> {
DartEntrypoint.createDefault();
});
}
@Test
public void itHasReasonableDefaultsWhenFlutterLoaderIsInitialized() {
when(mockFlutterLoader.initialized()).thenReturn(true);
when(mockFlutterLoader.findAppBundlePath()).thenReturn("my/custom/path");
FlutterInjector.setInstance(
new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
DartEntrypoint entrypoint = DartEntrypoint.createDefault();
assertEquals(entrypoint.pathToBundle, "my/custom/path");
assertEquals(entrypoint.dartEntrypointFunctionName, "main");
}
} }
// 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.loader; package io.flutter.embedding.engine.loader;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
......
// 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.loader;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
import io.flutter.FlutterInjector;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterLoaderTest {
@Before
public void setUp() {
FlutterInjector.reset();
}
@Test
public void itReportsUninitializedAfterCreating() {
FlutterLoader flutterLoader = new FlutterLoader();
assertFalse(flutterLoader.initialized());
}
@Test
public void itReportsInitializedAfterInitializing() {
FlutterInjector.setInstance(new FlutterInjector.Builder().setShouldLoadNative(false).build());
FlutterLoader flutterLoader = new FlutterLoader();
assertFalse(flutterLoader.initialized());
flutterLoader.startInitialization(RuntimeEnvironment.application);
flutterLoader.ensureInitializationComplete(RuntimeEnvironment.application, null);
assertTrue(flutterLoader.initialized());
FlutterInjector.reset();
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册