Flutter Engine
The Flutter Engine
FlutterLoader.java
Go to the documentation of this file.
1// Copyright 2013 The Flutter 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
5package io.flutter.embedding.engine.loader;
6
7import android.app.ActivityManager;
8import android.content.Context;
9import android.content.pm.ApplicationInfo;
10import android.content.pm.PackageManager;
11import android.content.res.AssetManager;
12import android.hardware.display.DisplayManager;
13import android.os.Bundle;
14import android.os.Handler;
15import android.os.Looper;
16import android.os.SystemClock;
17import android.util.DisplayMetrics;
18import androidx.annotation.NonNull;
19import androidx.annotation.Nullable;
20import io.flutter.BuildConfig;
21import io.flutter.FlutterInjector;
22import io.flutter.Log;
23import io.flutter.embedding.engine.FlutterJNI;
24import io.flutter.util.HandlerCompat;
25import io.flutter.util.PathUtils;
26import io.flutter.util.TraceSection;
27import io.flutter.view.VsyncWaiter;
28import java.io.File;
29import java.util.*;
30import java.util.concurrent.Callable;
31import java.util.concurrent.ExecutorService;
32import java.util.concurrent.Future;
33
34/** Finds Flutter resources in an application APK and also loads Flutter's native library. */
35public class FlutterLoader {
36 private static final String TAG = "FlutterLoader";
37
38 private static final String OLD_GEN_HEAP_SIZE_META_DATA_KEY =
39 "io.flutter.embedding.android.OldGenHeapSize";
40 private static final String ENABLE_IMPELLER_META_DATA_KEY =
41 "io.flutter.embedding.android.EnableImpeller";
42 private static final String ENABLE_VULKAN_VALIDATION_META_DATA_KEY =
43 "io.flutter.embedding.android.EnableVulkanValidation";
44 private static final String IMPELLER_BACKEND_META_DATA_KEY =
45 "io.flutter.embedding.android.ImpellerBackend";
46 private static final String IMPELLER_OPENGL_GPU_TRACING_DATA_KEY =
47 "io.flutter.embedding.android.EnableOpenGLGPUTracing";
48 private static final String IMPELLER_VULKAN_GPU_TRACING_DATA_KEY =
49 "io.flutter.embedding.android.EnableVulkanGPUTracing";
50
51 /**
52 * Set whether leave or clean up the VM after the last shell shuts down. It can be set from app's
53 * meta-data in <application /> in AndroidManifest.xml. Set it to true in to leave the Dart VM,
54 * set it to false to destroy VM.
55 *
56 * <p>If your want to let your app destroy the last shell and re-create shells more quickly, set
57 * it to true, otherwise if you want to clean up the memory of the leak VM, set it to false.
58 *
59 * <p>TODO(eggfly): Should it be set to false by default?
60 * https://github.com/flutter/flutter/issues/96843
61 */
62 private static final String LEAK_VM_META_DATA_KEY = "io.flutter.embedding.android.LeakVM";
63
64 // Must match values in flutter::switches
65 static final String AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name";
66 static final String AOT_VMSERVICE_SHARED_LIBRARY_NAME = "aot-vmservice-shared-library-name";
67 static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path";
68 static final String VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data";
69 static final String ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data";
70 static final String FLUTTER_ASSETS_DIR_KEY = "flutter-assets-dir";
71 static final String AUTOMATICALLY_REGISTER_PLUGINS_KEY = "automatically-register-plugins";
72
73 // Resource names used for components of the precompiled snapshot.
74 private static final String DEFAULT_LIBRARY = "libflutter.so";
75 private static final String DEFAULT_KERNEL_BLOB = "kernel_blob.bin";
76 private static final String VMSERVICE_SNAPSHOT_LIBRARY = "libvmservice_snapshot.so";
77
78 private static FlutterLoader instance;
79
80 /**
81 * Creates a {@code FlutterLoader} that uses a default constructed {@link FlutterJNI} and {@link
82 * ExecutorService}.
83 */
84 public FlutterLoader() {
85 this(FlutterInjector.instance().getFlutterJNIFactory().provideFlutterJNI());
86 }
87
88 /**
89 * Creates a {@code FlutterLoader} that uses a default constructed {@link ExecutorService}.
90 *
91 * @param flutterJNI The {@link FlutterJNI} instance to use for loading the libflutter.so C++
92 * library, setting up the font manager, and calling into C++ initialization.
93 */
94 public FlutterLoader(@NonNull FlutterJNI flutterJNI) {
95 this(flutterJNI, FlutterInjector.instance().executorService());
96 }
97
98 /**
99 * Creates a {@code FlutterLoader} with the specified {@link FlutterJNI}.
100 *
101 * @param flutterJNI The {@link FlutterJNI} instance to use for loading the libflutter.so C++
102 * library, setting up the font manager, and calling into C++ initialization.
103 * @param executorService The {@link ExecutorService} to use when creating new threads.
104 */
105 public FlutterLoader(@NonNull FlutterJNI flutterJNI, @NonNull ExecutorService executorService) {
106 this.flutterJNI = flutterJNI;
107 this.executorService = executorService;
108 }
109
110 private boolean initialized = false;
111 @Nullable private Settings settings;
112 private long initStartTimestampMillis;
113 private FlutterApplicationInfo flutterApplicationInfo;
114 private FlutterJNI flutterJNI;
115 private ExecutorService executorService;
116
117 private static class InitResult {
118 final String appStoragePath;
119 final String engineCachesPath;
120 final String dataDirPath;
121
122 private InitResult(String appStoragePath, String engineCachesPath, String dataDirPath) {
123 this.appStoragePath = appStoragePath;
124 this.engineCachesPath = engineCachesPath;
125 this.dataDirPath = dataDirPath;
126 }
127 }
128
129 @Nullable Future<InitResult> initResultFuture;
130
131 /**
132 * Starts initialization of the native system.
133 *
134 * @param applicationContext The Android application context.
135 */
136 public void startInitialization(@NonNull Context applicationContext) {
137 startInitialization(applicationContext, new Settings());
138 }
139
140 /**
141 * Starts initialization of the native system.
142 *
143 * <p>This loads the Flutter engine's native library to enable subsequent JNI calls. This also
144 * starts locating and unpacking Dart resources packaged in the app's APK.
145 *
146 * <p>Calling this method multiple times has no effect.
147 *
148 * @param applicationContext The Android application context.
149 * @param settings Configuration settings.
150 */
151 public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
152 // Do not run startInitialization more than once.
153 if (this.settings != null) {
154 return;
155 }
156 if (Looper.myLooper() != Looper.getMainLooper()) {
157 throw new IllegalStateException("startInitialization must be called on the main thread");
158 }
159
160 try (TraceSection e = TraceSection.scoped("FlutterLoader#startInitialization")) {
161 // Ensure that the context is actually the application context.
162 final Context appContext = applicationContext.getApplicationContext();
163
164 this.settings = settings;
165
166 initStartTimestampMillis = SystemClock.uptimeMillis();
167 flutterApplicationInfo = ApplicationInfoLoader.load(appContext);
168
169 final DisplayManager dm =
170 (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
171 VsyncWaiter waiter = VsyncWaiter.getInstance(dm, flutterJNI);
172 waiter.init();
173
174 // Use a background thread for initialization tasks that require disk access.
175 Callable<InitResult> initTask =
176 new Callable<InitResult>() {
177 @Override
178 public InitResult call() {
179 try (TraceSection e = TraceSection.scoped("FlutterLoader initTask")) {
180 ResourceExtractor resourceExtractor = initResources(appContext);
181
182 try {
183 flutterJNI.loadLibrary();
184 } catch (UnsatisfiedLinkError unsatisfiedLinkError) {
185 String couldntFindVersion = "couldn't find \"libflutter.so\"";
186 String notFoundVersion = "dlopen failed: library \"libflutter.so\" not found";
187
188 if (unsatisfiedLinkError.toString().contains(couldntFindVersion)
189 || unsatisfiedLinkError.toString().contains(notFoundVersion)) {
190 // To gather more information for
191 // https://github.com/flutter/flutter/issues/144291,
192 // log the contents of the native libraries directory as well as the
193 // cpu architecture.
194
195 String cpuArch = System.getProperty("os.arch");
196 File nativeLibsDir = new File(flutterApplicationInfo.nativeLibraryDir);
197 String[] nativeLibsContents = nativeLibsDir.list();
198
199 throw new UnsupportedOperationException(
200 "Could not load libflutter.so this is possibly because the application"
201 + " is running on an architecture that Flutter Android does not support (e.g. x86)"
202 + " see https://docs.flutter.dev/deployment/android#what-are-the-supported-target-architectures"
203 + " for more detail.\n"
204 + "App is using cpu architecture: "
205 + cpuArch
206 + ", and the native libraries directory (with path "
207 + nativeLibsDir.getAbsolutePath()
208 + ") contains the following files: "
209 + Arrays.toString(nativeLibsContents),
210 unsatisfiedLinkError);
211 }
212
213 throw unsatisfiedLinkError;
214 }
215
216 flutterJNI.updateRefreshRate();
217
218 // Prefetch the default font manager as soon as possible on a background thread.
219 // It helps to reduce time cost of engine setup that blocks the platform thread.
220 executorService.execute(() -> flutterJNI.prefetchDefaultFontManager());
221
222 if (resourceExtractor != null) {
223 resourceExtractor.waitForCompletion();
224 }
225
226 return new InitResult(
227 PathUtils.getFilesDir(appContext),
228 PathUtils.getCacheDirectory(appContext),
229 PathUtils.getDataDirectory(appContext));
230 }
231 }
232 };
233 initResultFuture = executorService.submit(initTask);
234 }
235 }
236
237 /**
238 * Blocks until initialization of the native system has completed.
239 *
240 * <p>Calling this method multiple times has no effect.
241 *
242 * @param applicationContext The Android application context.
243 * @param args Flags sent to the Flutter runtime.
244 */
246 @NonNull Context applicationContext, @Nullable String[] args) {
247 if (initialized) {
248 return;
249 }
250 if (Looper.myLooper() != Looper.getMainLooper()) {
251 throw new IllegalStateException(
252 "ensureInitializationComplete must be called on the main thread");
253 }
254 if (settings == null) {
255 throw new IllegalStateException(
256 "ensureInitializationComplete must be called after startInitialization");
257 }
258
259 try (TraceSection e = TraceSection.scoped("FlutterLoader#ensureInitializationComplete")) {
260 InitResult result = initResultFuture.get();
261
262 List<String> shellArgs = new ArrayList<>();
263 shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
264
265 shellArgs.add(
266 "--icu-native-lib-path="
267 + flutterApplicationInfo.nativeLibraryDir
268 + File.separator
269 + DEFAULT_LIBRARY);
270 if (args != null) {
271 Collections.addAll(shellArgs, args);
272 }
273
274 String kernelPath = null;
276 String snapshotAssetPath =
277 result.dataDirPath + File.separator + flutterApplicationInfo.flutterAssetsDir;
278 kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
279 shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
280 shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.vmSnapshotData);
281 shellArgs.add(
282 "--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.isolateSnapshotData);
283 } else {
284 shellArgs.add(
285 "--" + AOT_SHARED_LIBRARY_NAME + "=" + flutterApplicationInfo.aotSharedLibraryName);
286
287 // Most devices can load the AOT shared library based on the library name
288 // with no directory path. Provide a fully qualified path to the library
289 // as a workaround for devices where that fails.
290 shellArgs.add(
291 "--"
293 + "="
294 + flutterApplicationInfo.nativeLibraryDir
295 + File.separator
296 + flutterApplicationInfo.aotSharedLibraryName);
297
298 // In profile mode, provide a separate library containing a snapshot for
299 // launching the Dart VM service isolate.
300 if (BuildConfig.PROFILE) {
301 shellArgs.add(
302 "--" + AOT_VMSERVICE_SHARED_LIBRARY_NAME + "=" + VMSERVICE_SNAPSHOT_LIBRARY);
303 }
304 }
305
306 shellArgs.add("--cache-dir-path=" + result.engineCachesPath);
307 if (flutterApplicationInfo.domainNetworkPolicy != null) {
308 shellArgs.add("--domain-network-policy=" + flutterApplicationInfo.domainNetworkPolicy);
309 }
310 if (settings.getLogTag() != null) {
311 shellArgs.add("--log-tag=" + settings.getLogTag());
312 }
313
314 ApplicationInfo applicationInfo =
315 applicationContext
316 .getPackageManager()
317 .getApplicationInfo(
318 applicationContext.getPackageName(), PackageManager.GET_META_DATA);
319 Bundle metaData = applicationInfo.metaData;
320 int oldGenHeapSizeMegaBytes =
321 metaData != null ? metaData.getInt(OLD_GEN_HEAP_SIZE_META_DATA_KEY) : 0;
322 if (oldGenHeapSizeMegaBytes == 0) {
323 // default to half of total memory.
324 ActivityManager activityManager =
325 (ActivityManager) applicationContext.getSystemService(Context.ACTIVITY_SERVICE);
326 ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
327 activityManager.getMemoryInfo(memInfo);
328 oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2);
329 }
330 shellArgs.add("--old-gen-heap-size=" + oldGenHeapSizeMegaBytes);
331
332 DisplayMetrics displayMetrics = applicationContext.getResources().getDisplayMetrics();
333 int screenWidth = displayMetrics.widthPixels;
334 int screenHeight = displayMetrics.heightPixels;
335 // This is the formula Android uses.
336 // https://android.googlesource.com/platform/frameworks/base/+/39ae5bac216757bc201490f4c7b8c0f63006c6cd/libs/hwui/renderthread/CacheManager.cpp#45
337 int resourceCacheMaxBytesThreshold = screenWidth * screenHeight * 12 * 4;
338 shellArgs.add("--resource-cache-max-bytes-threshold=" + resourceCacheMaxBytesThreshold);
339
340 shellArgs.add("--prefetched-default-font-manager");
341
342 if (metaData != null) {
343 if (metaData.getBoolean(ENABLE_IMPELLER_META_DATA_KEY, false)) {
344 shellArgs.add("--enable-impeller");
345 }
346 if (metaData.getBoolean(ENABLE_VULKAN_VALIDATION_META_DATA_KEY, false)) {
347 shellArgs.add("--enable-vulkan-validation");
348 }
349 if (metaData.getBoolean(IMPELLER_OPENGL_GPU_TRACING_DATA_KEY, false)) {
350 shellArgs.add("--enable-opengl-gpu-tracing");
351 }
352 if (metaData.getBoolean(IMPELLER_VULKAN_GPU_TRACING_DATA_KEY, false)) {
353 shellArgs.add("--enable-vulkan-gpu-tracing");
354 }
355 String backend = metaData.getString(IMPELLER_BACKEND_META_DATA_KEY);
356 if (backend != null) {
357 shellArgs.add("--impeller-backend=" + backend);
358 }
359 }
360
361 final String leakVM = isLeakVM(metaData) ? "true" : "false";
362 shellArgs.add("--leak-vm=" + leakVM);
363
364 long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
365
366 flutterJNI.init(
367 applicationContext,
368 shellArgs.toArray(new String[0]),
369 kernelPath,
370 result.appStoragePath,
371 result.engineCachesPath,
372 initTimeMillis);
373
374 initialized = true;
375 } catch (Exception e) {
376 Log.e(TAG, "Flutter initialization failed.", e);
377 throw new RuntimeException(e);
378 }
379 }
380
381 private static boolean isLeakVM(@Nullable Bundle metaData) {
382 final boolean leakVMDefaultValue = true;
383 if (metaData == null) {
384 return leakVMDefaultValue;
385 }
386 return metaData.getBoolean(LEAK_VM_META_DATA_KEY, leakVMDefaultValue);
387 }
388
389 /**
390 * Same as {@link #ensureInitializationComplete(Context, String[])} but waiting on a background
391 * thread, then invoking {@code callback} on the {@code callbackHandler}.
392 */
394 @NonNull Context applicationContext,
395 @Nullable String[] args,
396 @NonNull Handler callbackHandler,
397 @NonNull Runnable callback) {
398 if (Looper.myLooper() != Looper.getMainLooper()) {
399 throw new IllegalStateException(
400 "ensureInitializationComplete must be called on the main thread");
401 }
402 if (settings == null) {
403 throw new IllegalStateException(
404 "ensureInitializationComplete must be called after startInitialization");
405 }
406 if (initialized) {
407 callbackHandler.post(callback);
408 return;
409 }
410 executorService.execute(
411 () -> {
412 InitResult result;
413 try {
414 result = initResultFuture.get();
415 } catch (Exception e) {
416 Log.e(TAG, "Flutter initialization failed.", e);
417 throw new RuntimeException(e);
418 }
419 HandlerCompat.createAsyncHandler(Looper.getMainLooper())
420 .post(
421 () -> {
422 ensureInitializationComplete(applicationContext.getApplicationContext(), args);
423 callbackHandler.post(callback);
424 });
425 });
426 }
427
428 /** Returns whether the FlutterLoader has finished loading the native library. */
429 public boolean initialized() {
430 return initialized;
431 }
432
433 /** Extract assets out of the APK that need to be cached as uncompressed files on disk. */
434 private ResourceExtractor initResources(@NonNull Context applicationContext) {
435 ResourceExtractor resourceExtractor = null;
437 final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
438 final String packageName = applicationContext.getPackageName();
439 final PackageManager packageManager = applicationContext.getPackageManager();
440 final AssetManager assetManager = applicationContext.getResources().getAssets();
441 resourceExtractor =
442 new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);
443
444 // In debug/JIT mode these assets will be written to disk and then
445 // mapped into memory so they can be provided to the Dart VM.
446 resourceExtractor
447 .addResource(fullAssetPathFrom(flutterApplicationInfo.vmSnapshotData))
448 .addResource(fullAssetPathFrom(flutterApplicationInfo.isolateSnapshotData))
449 .addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB));
450
451 resourceExtractor.start();
452 }
453 return resourceExtractor;
454 }
455
456 @NonNull
457 public String findAppBundlePath() {
458 return flutterApplicationInfo.flutterAssetsDir;
459 }
460
461 /**
462 * Returns the file name for the given asset. The returned file name can be used to access the
463 * asset in the APK through the {@link android.content.res.AssetManager} API.
464 *
465 * @param asset the name of the asset. The name can be hierarchical
466 * @return the filename to be used with {@link android.content.res.AssetManager}
467 */
468 @NonNull
469 public String getLookupKeyForAsset(@NonNull String asset) {
470 return fullAssetPathFrom(asset);
471 }
472
473 /**
474 * Returns the file name for the given asset which originates from the specified packageName. The
475 * returned file name can be used to access the asset in the APK through the {@link
476 * android.content.res.AssetManager} API.
477 *
478 * @param asset the name of the asset. The name can be hierarchical
479 * @param packageName the name of the package from which the asset originates
480 * @return the file name to be used with {@link android.content.res.AssetManager}
481 */
482 @NonNull
483 public String getLookupKeyForAsset(@NonNull String asset, @NonNull String packageName) {
484 return getLookupKeyForAsset("packages" + File.separator + packageName + File.separator + asset);
485 }
486
487 /** Returns the configuration on whether flutter engine should automatically register plugins. */
488 @NonNull
490 return flutterApplicationInfo.automaticallyRegisterPlugins;
491 }
492
493 @NonNull
494 private String fullAssetPathFrom(@NonNull String filePath) {
495 return flutterApplicationInfo.flutterAssetsDir + File.separator + filePath;
496 }
497
498 public static class Settings {
499 private String logTag;
500
501 @Nullable
502 public String getLogTag() {
503 return logTag;
504 }
505
506 /**
507 * Set the tag associated with Flutter app log messages.
508 *
509 * @param tag Log tag.
510 */
511 public void setLogTag(String tag) {
512 logTag = tag;
513 }
514 }
515}
const char * backend
static final boolean DEBUG
static final boolean PROFILE
static final boolean JIT_RELEASE
FlutterJNI.Factory getFlutterJNIFactory()
static void e(@NonNull String tag, @NonNull String message)
Definition: Log.java:84
void init( @NonNull Context context, @NonNull String[] args, @Nullable String bundlePath, @NonNull String appStoragePath, @NonNull String engineCachesPath, long initTimeMillis)
static FlutterApplicationInfo load(@NonNull Context applicationContext)
FlutterLoader(@NonNull FlutterJNI flutterJNI, @NonNull ExecutorService executorService)
String getLookupKeyForAsset(@NonNull String asset)
void ensureInitializationComplete( @NonNull Context applicationContext, @Nullable String[] args)
void startInitialization(@NonNull Context applicationContext)
String getLookupKeyForAsset(@NonNull String asset, @NonNull String packageName)
void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings)
FlutterLoader(@NonNull FlutterJNI flutterJNI)
void ensureInitializationCompleteAsync( @NonNull Context applicationContext, @Nullable String[] args, @NonNull Handler callbackHandler, @NonNull Runnable callback)
ResourceExtractor addResource(@NonNull String resource)
static Handler createAsyncHandler(Looper looper)
static String getDataDirectory(@NonNull Context applicationContext)
Definition: PathUtils.java:25
static String getCacheDirectory(@NonNull Context applicationContext)
Definition: PathUtils.java:35
static String getFilesDir(@NonNull Context applicationContext)
Definition: PathUtils.java:16
static TraceSection scoped(String name)
static VsyncWaiter getInstance(float fps, @NonNull FlutterJNI flutterJNI)
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
GAsyncResult * result
def call(args)
Definition: dom.py:159
#define TAG()