Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 9bcf2c1

Browse files
committed
Move FlutterLoader disk I/O to a background thread to comply with Android strict mode
Fixes flutter/flutter#56145
1 parent 5e7d6d0 commit 9bcf2c1

3 files changed

Lines changed: 85 additions & 20 deletions

File tree

shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
import io.flutter.view.VsyncWaiter;
2323
import java.io.File;
2424
import java.util.*;
25+
import java.util.concurrent.Callable;
26+
import java.util.concurrent.Executors;
27+
import java.util.concurrent.Future;
2528

2629
/** Finds Flutter resources in an application APK and also loads Flutter's native library. */
2730
public class FlutterLoader {
@@ -76,10 +79,29 @@ public static FlutterLoader getInstance() {
7679
}
7780

7881
private boolean initialized = false;
79-
@Nullable private ResourceExtractor resourceExtractor;
8082
@Nullable private Settings settings;
8183
private long initStartTimestampMillis;
8284

85+
private static class InitResult {
86+
final String appStoragePath;
87+
final String engineCachesPath;
88+
final String dataDirPath;
89+
final ResourceExtractor resourceExtractor;
90+
91+
private InitResult(
92+
String appStoragePath,
93+
String engineCachesPath,
94+
String dataDirPath,
95+
ResourceExtractor resourceExtractor) {
96+
this.appStoragePath = appStoragePath;
97+
this.engineCachesPath = engineCachesPath;
98+
this.dataDirPath = dataDirPath;
99+
this.resourceExtractor = resourceExtractor;
100+
}
101+
}
102+
103+
@Nullable Future<InitResult> initResultFuture;
104+
83105
/**
84106
* Starts initialization of the native system.
85107
*
@@ -110,18 +132,32 @@ public void startInitialization(@NonNull Context applicationContext, @NonNull Se
110132
}
111133

112134
// Ensure that the context is actually the application context.
113-
applicationContext = applicationContext.getApplicationContext();
135+
final Context appContext = applicationContext.getApplicationContext();
114136

115137
this.settings = settings;
116138

117139
initStartTimestampMillis = SystemClock.uptimeMillis();
118-
initResources(applicationContext);
119140

120-
System.loadLibrary("flutter");
121-
122-
VsyncWaiter.getInstance(
123-
(WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE))
141+
VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE))
124142
.init();
143+
144+
// Use a background thread for initialization tasks that require disk access.
145+
Callable<InitResult> initTask =
146+
new Callable<InitResult>() {
147+
@Override
148+
public InitResult call() {
149+
ResourceExtractor resourceExtractor = initResources(appContext);
150+
151+
System.loadLibrary("flutter");
152+
153+
return new InitResult(
154+
PathUtils.getFilesDir(appContext),
155+
PathUtils.getCacheDirectory(appContext),
156+
PathUtils.getDataDirectory(appContext),
157+
resourceExtractor);
158+
}
159+
};
160+
initResultFuture = Executors.newSingleThreadExecutor().submit(initTask);
125161
}
126162

127163
/**
@@ -146,8 +182,10 @@ public void ensureInitializationComplete(
146182
"ensureInitializationComplete must be called after startInitialization");
147183
}
148184
try {
149-
if (resourceExtractor != null) {
150-
resourceExtractor.waitForCompletion();
185+
InitResult result = initResultFuture.get();
186+
187+
if (result.resourceExtractor != null) {
188+
result.resourceExtractor.waitForCompletion();
151189
}
152190

153191
List<String> shellArgs = new ArrayList<>();
@@ -166,8 +204,7 @@ public void ensureInitializationComplete(
166204

167205
String kernelPath = null;
168206
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
169-
String snapshotAssetPath =
170-
PathUtils.getDataDirectory(applicationContext) + File.separator + flutterAssetsDir;
207+
String snapshotAssetPath = result.dataDirPath + File.separator + flutterAssetsDir;
171208
kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
172209
shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
173210
shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + vmSnapshotData);
@@ -187,20 +224,18 @@ public void ensureInitializationComplete(
187224
+ aotSharedLibraryName);
188225
}
189226

190-
shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext));
227+
shellArgs.add("--cache-dir-path=" + result.engineCachesPath);
191228
if (settings.getLogTag() != null) {
192229
shellArgs.add("--log-tag=" + settings.getLogTag());
193230
}
194231

195-
String appStoragePath = PathUtils.getFilesDir(applicationContext);
196-
String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
197232
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
198233
FlutterJNI.nativeInit(
199234
applicationContext,
200235
shellArgs.toArray(new String[0]),
201236
kernelPath,
202-
appStoragePath,
203-
engineCachesPath,
237+
result.appStoragePath,
238+
result.engineCachesPath,
204239
initTimeMillis);
205240

206241
initialized = true;
@@ -235,8 +270,15 @@ public void ensureInitializationCompleteAsync(
235270
new Runnable() {
236271
@Override
237272
public void run() {
238-
if (resourceExtractor != null) {
239-
resourceExtractor.waitForCompletion();
273+
InitResult result;
274+
try {
275+
result = initResultFuture.get();
276+
} catch (Exception e) {
277+
Log.e(TAG, "Flutter initialization failed.", e);
278+
throw new RuntimeException(e);
279+
}
280+
if (result.resourceExtractor != null) {
281+
result.resourceExtractor.waitForCompletion();
240282
}
241283
new Handler(Looper.getMainLooper())
242284
.post(
@@ -288,7 +330,8 @@ private void initConfig(@NonNull Context applicationContext) {
288330
}
289331

290332
/** Extract assets out of the APK that need to be cached as uncompressed files on disk. */
291-
private void initResources(@NonNull Context applicationContext) {
333+
private ResourceExtractor initResources(@NonNull Context applicationContext) {
334+
ResourceExtractor resourceExtractor = null;
292335
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
293336
final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
294337
final String packageName = applicationContext.getPackageName();
@@ -306,6 +349,7 @@ private void initResources(@NonNull Context applicationContext) {
306349

307350
resourceExtractor.start();
308351
}
352+
return resourceExtractor;
309353
}
310354

311355
@NonNull

testing/scenario_app/android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
package="dev.flutter.scenarios">
44

55
<application
6-
android:name="io.flutter.app.FlutterApplication"
6+
android:name=".ScenarioApplication"
77
android:allowBackup="true"
88
android:icon="@mipmap/ic_launcher"
99
android:label="@string/app_name"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2018 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package dev.flutter.scenarios;
6+
7+
import android.os.StrictMode;
8+
import io.flutter.app.FlutterApplication;
9+
10+
public class ScenarioApplication extends FlutterApplication {
11+
@Override
12+
public void onCreate() {
13+
StrictMode.setThreadPolicy(
14+
new StrictMode.ThreadPolicy.Builder()
15+
.detectDiskReads()
16+
.detectDiskWrites()
17+
.penaltyDeath()
18+
.build());
19+
super.onCreate();
20+
}
21+
}

0 commit comments

Comments
 (0)