Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
move native loading override into the injector, add assertion for dar…
…t entrypoint construction
  • Loading branch information
xster committed Aug 28, 2020
commit 860994de71c0a2b03040b1ae13697d33b00ae54e
1 change: 1 addition & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ action("robolectric_tests") {
"test/io/flutter/embedding/engine/RenderingComponentTest.java",
"test/io/flutter/embedding/engine/dart/DartExecutorTest.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/renderer/FlutterRendererTest.java",
"test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java",
Expand Down
45 changes: 38 additions & 7 deletions shell/platform/android/io/flutter/FlutterInjector.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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.
*
* This simple solution is used facilitate testability without bringing in heavier app-development
Expand All @@ -20,7 +20,7 @@ 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}.
*
Expand All @@ -37,7 +37,7 @@ public static void setInstance(@NonNull FlutterInjector injector) {
instance = injector;
}

/*
/**
* Retrieve the static instance of the {@code FlutterInjector} to use in your program.
*
* Once you access it, you can no longer change the values injected.
Expand All @@ -60,27 +60,54 @@ public static void reset() {
instance = null;
}

private FlutterInjector(@NonNull FlutterLoader flutterLoader) {
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.
*
* 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)}.
*
* 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.
*
* Useful for testing since JVM tests via Robolectric can't load native libraries.
*
* Defaults to true.
*/
public Builder setShouldLoadNative(boolean shouldLoadNative) {
this.shouldLoadNative = shouldLoadNative;
return this;
}

private FlutterLoader flutterLoader;
/*
/**
* Sets a {@link FlutterLoader} override.
*
* A reasonable default will be used if unspecified.
Expand All @@ -96,10 +123,14 @@ private void fillDefaults() {
}
}

/** Builds a {@link FlutterInjector} from the builder. Unspecified properties will have
* reasonable defaults.
*/
public FlutterInjector build() {
fillDefaults();

return new FlutterInjector(flutterLoader);
System.out.println("should load native is " + shouldLoadNative);
return new FlutterInjector(shouldLoadNative, flutterLoader);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.flutter.FlutterInjector;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.StringCodec;
import io.flutter.view.FlutterCallbackInformation;
Expand Down Expand Up @@ -250,10 +251,19 @@ public void notifyLowMemoryWarning() {
* that entrypoint and other assets required for Dart execution.
*/
public static class DartEntrypoint {
/**
* Create a DartEntrypoint pointing to the default Flutter assets location with a default Dart
* entrypoint.
*/
@NonNull
public static DartEntrypoint createDefault() {
FlutterLoader flutterLoader = FlutterInjector.instance().flutterLoader();

if (!flutterLoader.initialized()) {
throw new AssertionError("DartEntrypoints can only be created once a FlutterEngine is created.");
}
return new DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(), "main");
flutterLoader.findAppBundlePath(), "main");
}

/** The path within the AssetManager where the app will look for assets. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.BuildConfig;
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.util.PathUtils;
import io.flutter.view.VsyncWaiter;
Expand Down Expand Up @@ -88,12 +89,6 @@ public void startInitialization(@NonNull Context applicationContext) {
startInitialization(applicationContext, new Settings());
}

@VisibleForTesting
public void loadNativeLibrary() {
// Let this be mockable.
System.loadLibrary("flutter");
}

/**
* Starts initialization of the native system.
*
Expand Down Expand Up @@ -131,7 +126,9 @@ public void startInitialization(@NonNull Context applicationContext, @NonNull Se
public InitResult call() {
ResourceExtractor resourceExtractor = initResources(appContext);

loadNativeLibrary();
if (FlutterInjector.instance().shouldLoadNative()) {
System.loadLibrary("flutter");
}

// 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.
Expand Down Expand Up @@ -234,13 +231,15 @@ public void ensureInitializationComplete(

long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;

FlutterJNI.nativeInit(
applicationContext,
shellArgs.toArray(new String[0]),
kernelPath,
result.appStoragePath,
result.engineCachesPath,
initTimeMillis);
if (FlutterInjector.instance().shouldLoadNative()) {
FlutterJNI.nativeInit(
applicationContext,
shellArgs.toArray(new String[0]),
kernelPath,
result.appStoragePath,
result.engineCachesPath,
initTimeMillis);
}

initialized = true;
} catch (Exception e) {
Expand Down Expand Up @@ -296,6 +295,13 @@ public void run() {
});
}

/**
* 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. */
private ResourceExtractor initResources(@NonNull Context applicationContext) {
ResourceExtractor resourceExtractor = null;
Expand Down
12 changes: 10 additions & 2 deletions shell/platform/android/test/io/flutter/FlutterInjectorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

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.FlutterInjector;
import io.flutter.embedding.engine.loader.FlutterLoader;
import org.junit.Before;
import org.junit.Test;
Expand All @@ -33,6 +36,7 @@ public void itHasSomeReasonableDefaults() {
// Implicitly builds when first accessed.
FlutterInjector injector = FlutterInjector.instance();
assertNotNull(injector.flutterLoader());
assertTrue(injector.shouldLoadNative());
}

@Test
Expand All @@ -41,12 +45,16 @@ public void canPartiallyOverride() {
new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
FlutterInjector injector = FlutterInjector.instance();
assertEquals(injector.flutterLoader(), mockFlutterLoader);
assertTrue(injector.shouldLoadNative());
}

@Test(expected = IllegalStateException.class)
@Test()
public void cannotBeChangedOnceRead() {
FlutterInjector.instance();
FlutterInjector.setInstance(

assertThrows(IllegalStateException.class, () -> {
FlutterInjector.setInstance(
new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
});
}
}
5 changes: 5 additions & 0 deletions shell/platform/android/test/io/flutter/FlutterTestSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import io.flutter.embedding.engine.FlutterJNITest;
import io.flutter.embedding.engine.LocalizationPluginTest;
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.renderer.FlutterRendererTest;
import io.flutter.embedding.engine.systemchannels.KeyEventChannelTest;
Expand Down Expand Up @@ -44,6 +46,7 @@
@SuiteClasses({
AccessibilityBridgeTest.class,
AndroidKeyProcessorTest.class,
ApplicationInfoLoaderTest.class,
DartExecutorTest.class,
FlutterActivityAndFragmentDelegateTest.class,
FlutterActivityTest.class,
Expand All @@ -56,6 +59,8 @@
FlutterInjectorTest.class,
FlutterJNITest.class,
FlutterLaunchTests.class,
FlutterLoaderTest.class,
FlutterShellArgsTest.class,
FlutterRendererTest.class,
FlutterShellArgsTest.class,
FlutterViewTest.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// 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;

import static junit.framework.TestCase.assertEquals;
Expand All @@ -8,11 +12,13 @@
import static org.mockito.Mockito.when;

import androidx.annotation.NonNull;
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.FlutterEngine;
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.plugins.FlutterPlugin;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
Expand All @@ -22,25 +28,23 @@
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class PluginComponentTest {
@Before
public void setUp() {
FlutterInjector.reset();
}

@Test
public void pluginsCanAccessFlutterAssetPaths() {
// Setup test.
FlutterInjector.setInstance(new FlutterInjector.Builder().setShouldLoadNative(false).build());
FlutterJNI flutterJNI = mock(FlutterJNI.class);
when(flutterJNI.isAttached()).thenReturn(true);
FlutterApplicationInfo emptyInfo =
new FlutterApplicationInfo(null, null, null, null, null, null, false, false);

FlutterLoader realFlutterLoader = new FlutterLoader();
FlutterLoader spyFlutterLoader = spy(realFlutterLoader);

// Let the real startInitialization be called, but mock the rest so it doesn't try to load
// the real native library.
doNothing().when(spyFlutterLoader).loadNativeLibrary();
doNothing().when(spyFlutterLoader).ensureInitializationComplete(any(), any());
FlutterLoader flutterLoader = new FlutterLoader();

// Execute behavior under test.
FlutterEngine flutterEngine =
new FlutterEngine(RuntimeEnvironment.application, spyFlutterLoader, flutterJNI);
new FlutterEngine(RuntimeEnvironment.application, flutterLoader, flutterJNI);

// As soon as our plugin is registered it will look up asset paths and store them
// for our verification.
Expand All @@ -56,6 +60,7 @@ public void pluginsCanAccessFlutterAssetPaths() {
assertEquals(
"flutter_assets/packages/fakepackage/some/path/fake_asset.jpg",
plugin.getAssetPathBasedOnSubpathAndPackage());
FlutterInjector.reset();
}

private static class PluginThatAccessesAssets implements FlutterPlugin {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package test.io.flutter.embedding.engine.dart;

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.eq;
import static org.mockito.Mockito.mock;
Expand All @@ -9,17 +11,33 @@
import static org.mockito.Mockito.when;

import android.content.res.AssetManager;
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.FlutterJNI;
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 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 DartExecutorTest {
@Mock FlutterLoader mockFlutterLoader;

@Before
public void setUp() {
FlutterInjector.reset();
MockitoAnnotations.initMocks(this);
}

@Test
public void itSendsBinaryMessages() {
// Setup test.
Expand Down Expand Up @@ -49,4 +67,20 @@ public void itNotifiesLowMemoryWarning() {
dartExecutor.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");
DartEntrypoint entrypoint = DartEntrypoint.createDefault();
assertEquals(entrypoint.pathToBundle, "my/custom/path");
assertEquals(entrypoint.dartEntrypointFunctionName, "main");
}
}
Loading