From 5a05f852de2cc23bb1ef8e0012d9b62bf73fab9d Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Tue, 5 May 2020 19:07:25 -0700 Subject: [PATCH] Move FlutterLoader disk I/O to a background thread to comply with Android strict mode Fixes https://github.com/flutter/flutter/issues/56145 --- .../engine/loader/FlutterLoader.java | 83 +++++++++++++------ .../android/app/src/main/AndroidManifest.xml | 2 +- .../scenarios/ScenarioApplication.java | 21 +++++ 3 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/ScenarioApplication.java diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index a2fdc97770a73..bd506957289fe 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -22,6 +22,9 @@ 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.Future; /** Finds Flutter resources in an application APK and also loads Flutter's native library. */ public class FlutterLoader { @@ -76,10 +79,23 @@ public static FlutterLoader getInstance() { } private boolean initialized = false; - @Nullable private ResourceExtractor resourceExtractor; @Nullable private Settings settings; private long initStartTimestampMillis; + private static class InitResult { + final String appStoragePath; + final String engineCachesPath; + final String dataDirPath; + + private InitResult(String appStoragePath, String engineCachesPath, String dataDirPath) { + this.appStoragePath = appStoragePath; + this.engineCachesPath = engineCachesPath; + this.dataDirPath = dataDirPath; + } + } + + @Nullable Future initResultFuture; + /** * Starts initialization of the native system. * @@ -110,19 +126,35 @@ public void startInitialization(@NonNull Context applicationContext, @NonNull Se } // Ensure that the context is actually the application context. - applicationContext = applicationContext.getApplicationContext(); + final Context appContext = applicationContext.getApplicationContext(); this.settings = settings; initStartTimestampMillis = SystemClock.uptimeMillis(); - initConfig(applicationContext); - initResources(applicationContext); - - System.loadLibrary("flutter"); - - VsyncWaiter.getInstance( - (WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE)) + initConfig(appContext); + VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE)) .init(); + + // Use a background thread for initialization tasks that require disk access. + Callable initTask = + new Callable() { + @Override + public InitResult call() { + ResourceExtractor resourceExtractor = initResources(appContext); + + System.loadLibrary("flutter"); + + if (resourceExtractor != null) { + resourceExtractor.waitForCompletion(); + } + + return new InitResult( + PathUtils.getFilesDir(appContext), + PathUtils.getCacheDirectory(appContext), + PathUtils.getDataDirectory(appContext)); + } + }; + initResultFuture = Executors.newSingleThreadExecutor().submit(initTask); } /** @@ -147,9 +179,7 @@ public void ensureInitializationComplete( "ensureInitializationComplete must be called after startInitialization"); } try { - if (resourceExtractor != null) { - resourceExtractor.waitForCompletion(); - } + InitResult result = initResultFuture.get(); List shellArgs = new ArrayList<>(); shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat"); @@ -167,8 +197,7 @@ public void ensureInitializationComplete( String kernelPath = null; if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) { - String snapshotAssetPath = - PathUtils.getDataDirectory(applicationContext) + File.separator + flutterAssetsDir; + String snapshotAssetPath = result.dataDirPath + File.separator + flutterAssetsDir; kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB; shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath); shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + vmSnapshotData); @@ -188,20 +217,18 @@ public void ensureInitializationComplete( + aotSharedLibraryName); } - shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext)); + shellArgs.add("--cache-dir-path=" + result.engineCachesPath); if (settings.getLogTag() != null) { shellArgs.add("--log-tag=" + settings.getLogTag()); } - String appStoragePath = PathUtils.getFilesDir(applicationContext); - String engineCachesPath = PathUtils.getCacheDirectory(applicationContext); long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis; FlutterJNI.nativeInit( applicationContext, shellArgs.toArray(new String[0]), kernelPath, - appStoragePath, - engineCachesPath, + result.appStoragePath, + result.engineCachesPath, initTimeMillis); initialized = true; @@ -232,12 +259,17 @@ public void ensureInitializationCompleteAsync( callbackHandler.post(callback); return; } - new Thread( + Executors.newSingleThreadExecutor() + .execute( new Runnable() { @Override public void run() { - if (resourceExtractor != null) { - resourceExtractor.waitForCompletion(); + 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( @@ -250,8 +282,7 @@ public void run() { } }); } - }) - .start(); + }); } @NonNull @@ -289,7 +320,8 @@ private void initConfig(@NonNull Context applicationContext) { } /** Extract assets out of the APK that need to be cached as uncompressed files on disk. */ - private void initResources(@NonNull Context applicationContext) { + private ResourceExtractor initResources(@NonNull Context applicationContext) { + ResourceExtractor resourceExtractor = null; if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) { final String dataDirPath = PathUtils.getDataDirectory(applicationContext); final String packageName = applicationContext.getPackageName(); @@ -307,6 +339,7 @@ private void initResources(@NonNull Context applicationContext) { resourceExtractor.start(); } + return resourceExtractor; } @NonNull diff --git a/testing/scenario_app/android/app/src/main/AndroidManifest.xml b/testing/scenario_app/android/app/src/main/AndroidManifest.xml index 5fa8533cebe5b..65ff9e909329b 100644 --- a/testing/scenario_app/android/app/src/main/AndroidManifest.xml +++ b/testing/scenario_app/android/app/src/main/AndroidManifest.xml @@ -3,7 +3,7 @@ package="dev.flutter.scenarios">