未验证 提交 b8da72ae 编写于 作者: D Dan Field 提交者: GitHub

Allow injection of ExecutorService (#28543)

* Allow injection of ExecutorService

* Set thread names, use cached pool
上级 a07c4db2
......@@ -10,6 +10,9 @@ import androidx.annotation.VisibleForTesting;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager;
import io.flutter.embedding.engine.loader.FlutterLoader;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/**
* This class is a simple dependency injector for the relatively thin Android part of the Flutter
......@@ -31,7 +34,6 @@ public final class FlutterInjector {
* <p>This can only be called at the beginning of the program before the {@link #instance()} is
* accessed.
*/
@VisibleForTesting
public static void setInstance(@NonNull FlutterInjector injector) {
if (accessed) {
throw new IllegalStateException(
......@@ -68,16 +70,18 @@ public final class FlutterInjector {
private FlutterInjector(
@NonNull FlutterLoader flutterLoader,
@Nullable DeferredComponentManager deferredComponentManager,
@NonNull FlutterJNI.Factory flutterJniFactory) {
@NonNull FlutterJNI.Factory flutterJniFactory,
@NonNull ExecutorService executorService) {
this.flutterLoader = flutterLoader;
this.deferredComponentManager = deferredComponentManager;
this.flutterJniFactory = flutterJniFactory;
this.executorService = executorService;
}
private FlutterLoader flutterLoader;
private DeferredComponentManager deferredComponentManager;
private FlutterJNI.Factory flutterJniFactory;
private ExecutorService executorService;
/**
* Returns the {@link io.flutter.embedding.engine.loader.FlutterLoader} instance to use for the
* Flutter Android engine embedding.
......@@ -96,6 +100,10 @@ public final class FlutterInjector {
return deferredComponentManager;
}
public ExecutorService executorService() {
return executorService;
}
@NonNull
public FlutterJNI.Factory getFlutterJNIFactory() {
return flutterJniFactory;
......@@ -108,9 +116,20 @@ public final class FlutterInjector {
* <p>Non-overridden values have reasonable defaults.
*/
public static final class Builder {
private class NamedThreadFactory implements ThreadFactory {
private int threadId = 0;
public Thread newThread(Runnable command) {
Thread thread = new Thread(command);
thread.setName("flutter-worker-" + threadId++);
return thread;
}
}
private FlutterLoader flutterLoader;
private DeferredComponentManager deferredComponentManager;
private FlutterJNI.Factory flutterJniFactory;
private ExecutorService executorService;
/**
* Sets a {@link io.flutter.embedding.engine.loader.FlutterLoader} override.
*
......@@ -132,13 +151,22 @@ public final class FlutterInjector {
return this;
}
public Builder setExecutorService(@NonNull ExecutorService executorService) {
this.executorService = executorService;
return this;
}
private void fillDefaults() {
if (flutterJniFactory == null) {
flutterJniFactory = new FlutterJNI.Factory();
}
if (executorService == null) {
executorService = Executors.newCachedThreadPool(new NamedThreadFactory());
}
if (flutterLoader == null) {
flutterLoader = new FlutterLoader(flutterJniFactory.provideFlutterJNI());
flutterLoader = new FlutterLoader(flutterJniFactory.provideFlutterJNI(), executorService);
}
// DeferredComponentManager's intended default is null.
}
......@@ -150,7 +178,8 @@ public final class FlutterInjector {
public FlutterInjector build() {
fillDefaults();
return new FlutterInjector(flutterLoader, deferredComponentManager, flutterJniFactory);
return new FlutterInjector(
flutterLoader, deferredComponentManager, flutterJniFactory, executorService);
}
}
}
......@@ -25,7 +25,7 @@ import io.flutter.view.VsyncWaiter;
import java.io.File;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
/** Finds Flutter resources in an application APK and also loads Flutter's native library. */
......@@ -70,19 +70,34 @@ public class FlutterLoader {
return instance;
}
/** Creates a {@code FlutterLoader} that uses a default constructed {@link FlutterJNI}. */
/**
* Creates a {@code FlutterLoader} that uses a default constructed {@link FlutterJNI} and {@link
* ExecutorService}.
*/
public FlutterLoader() {
this(FlutterInjector.instance().getFlutterJNIFactory().provideFlutterJNI());
}
/**
* Creates a {@code FlutterLoader} with the specified {@link FlutterJNI}.
* Creates a {@code FlutterLoader} that uses a default constructed {@link ExecutorService}.
*
* @param flutterJNI The {@link FlutterJNI} instance to use for loading the libflutter.so C++
* library, setting up the font manager, and calling into C++ initialization.
*/
public FlutterLoader(@NonNull FlutterJNI flutterJNI) {
this(flutterJNI, FlutterInjector.instance().executorService());
}
/**
* Creates a {@code FlutterLoader} with the specified {@link FlutterJNI}.
*
* @param flutterJNI The {@link FlutterJNI} instance to use for loading the libflutter.so C++
* library, setting up the font manager, and calling into C++ initialization.
* @param executorService The {@link ExecutorService} to use when creating new threads.
*/
public FlutterLoader(@NonNull FlutterJNI flutterJNI, @NonNull ExecutorService executorService) {
this.flutterJNI = flutterJNI;
this.executorService = executorService;
}
private boolean initialized = false;
......@@ -90,6 +105,8 @@ public class FlutterLoader {
private long initStartTimestampMillis;
private FlutterApplicationInfo flutterApplicationInfo;
private FlutterJNI flutterJNI;
private ExecutorService executorService;
private WindowManager windowManager;
private static class InitResult {
final String appStoragePath;
......@@ -155,14 +172,7 @@ public class FlutterLoader {
// 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.
Executors.newSingleThreadExecutor()
.execute(
new Runnable() {
@Override
public void run() {
flutterJNI.prefetchDefaultFontManager();
}
});
executorService.execute(() -> flutterJNI.prefetchDefaultFontManager());
if (resourceExtractor != null) {
resourceExtractor.waitForCompletion();
......@@ -174,7 +184,7 @@ public class FlutterLoader {
PathUtils.getDataDirectory(appContext));
}
};
initResultFuture = Executors.newSingleThreadExecutor().submit(initTask);
initResultFuture = executorService.submit(initTask);
}
/**
......@@ -307,30 +317,22 @@ public class FlutterLoader {
callbackHandler.post(callback);
return;
}
Executors.newSingleThreadExecutor()
.execute(
new Runnable() {
@Override
public void run() {
InitResult result;
try {
result = initResultFuture.get();
} catch (Exception e) {
Log.e(TAG, "Flutter initialization failed.", e);
throw new RuntimeException(e);
}
new Handler(Looper.getMainLooper())
.post(
new Runnable() {
@Override
public void run() {
ensureInitializationComplete(
applicationContext.getApplicationContext(), args);
callbackHandler.post(callback);
}
});
}
});
executorService.execute(
() -> {
InitResult result;
try {
result = initResultFuture.get();
} catch (Exception e) {
Log.e(TAG, "Flutter initialization failed.", e);
throw new RuntimeException(e);
}
new Handler(Looper.getMainLooper())
.post(
() -> {
ensureInitializationComplete(applicationContext.getApplicationContext(), args);
callbackHandler.post(callback);
});
});
}
/** Returns whether the FlutterLoader has finished loading the native library. */
......
......@@ -11,6 +11,12 @@ import static org.junit.Assert.assertThrows;
import io.flutter.embedding.engine.deferredcomponents.PlayStoreDeferredComponentManager;
import io.flutter.embedding.engine.loader.FlutterLoader;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
......@@ -25,6 +31,7 @@ import org.robolectric.annotation.Config;
public class FlutterInjectorTest {
@Mock FlutterLoader mockFlutterLoader;
@Mock PlayStoreDeferredComponentManager mockDeferredComponentManager;
@Mock ExecutorService mockExecutorService;
@Before
public void setUp() {
......@@ -44,6 +51,30 @@ public class FlutterInjectorTest {
FlutterInjector injector = FlutterInjector.instance();
assertNotNull(injector.flutterLoader());
assertNull(injector.deferredComponentManager());
assertNotNull(injector.executorService());
}
@Test
public void executorCreatesAndNamesNewThreadsByDefault()
throws InterruptedException, ExecutionException {
// Implicitly builds when first accessed.
FlutterInjector injector = FlutterInjector.instance();
List<Callable<String>> callables =
Arrays.asList(
() -> {
return Thread.currentThread().getName();
},
() -> {
return Thread.currentThread().getName();
});
List<Future<String>> threadNames;
threadNames = injector.executorService().invokeAll(callables);
assertEquals(threadNames.size(), 2);
assertEquals(threadNames.get(0).get(), "flutter-worker-0");
assertEquals(threadNames.get(1).get(), "flutter-worker-1");
}
@Test
......@@ -64,6 +95,14 @@ public class FlutterInjectorTest {
assertEquals(injector.deferredComponentManager(), mockDeferredComponentManager);
}
@Test
public void canInjectExecutorService() {
FlutterInjector.setInstance(
new FlutterInjector.Builder().setExecutorService(mockExecutorService).build());
FlutterInjector injector = FlutterInjector.instance();
assertEquals(injector.executorService(), mockExecutorService);
}
@Test()
public void cannotBeChangedOnceRead() {
FlutterInjector.instance();
......
......@@ -10,12 +10,10 @@ import static org.mockito.Mockito.mock;
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.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;
......@@ -27,17 +25,10 @@ import org.robolectric.annotation.Config;
public class PluginComponentTest {
boolean jniAttached;
@Before
public void setUp() {
FlutterInjector.reset();
}
@Test
public void pluginsCanAccessFlutterAssetPaths() {
// Setup test.
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
FlutterInjector.setInstance(
new FlutterInjector.Builder().setFlutterLoader(new FlutterLoader(mockFlutterJNI)).build());
FlutterJNI flutterJNI = mock(FlutterJNI.class);
jniAttached = false;
when(flutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
......@@ -63,7 +54,6 @@ public class PluginComponentTest {
assertEquals(
"flutter_assets/packages/fakepackage/some/path/fake_asset.jpg",
plugin.getAssetPathBasedOnSubpathAndPackage());
FlutterInjector.reset();
}
private static class PluginThatAccessesAssets implements FlutterPlugin {
......
......@@ -7,6 +7,7 @@ package io.flutter.embedding.engine.loader;
import static android.os.Looper.getMainLooper;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.eq;
......@@ -20,6 +21,8 @@ import android.content.Context;
import io.flutter.embedding.engine.FlutterJNI;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
......@@ -78,4 +81,15 @@ public class FlutterLoaderTest {
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertTrue(arguments.contains(oldGenHeapArg));
}
@Test
public void itUsesCorrectExecutorService() {
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
ExecutorService mockExecutorService = mock(ExecutorService.class);
FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI, mockExecutorService);
assertFalse(flutterLoader.initialized());
flutterLoader.startInitialization(RuntimeEnvironment.application);
verify(mockExecutorService, times(1)).submit(any(Callable.class));
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册