• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
5 package io.flutter.view;
6 
7 import android.content.Context;
8 import android.content.pm.ApplicationInfo;
9 import android.content.pm.PackageManager;
10 import android.content.res.AssetManager;
11 import android.os.Bundle;
12 import android.os.Handler;
13 import android.os.Looper;
14 import android.os.SystemClock;
15 import android.support.annotation.NonNull;
16 import android.support.annotation.Nullable;
17 import android.support.annotation.VisibleForTesting;
18 import android.util.Log;
19 import android.view.WindowManager;
20 
21 import io.flutter.BuildConfig;
22 import io.flutter.embedding.engine.FlutterJNI;
23 import io.flutter.util.PathUtils;
24 
25 import java.io.File;
26 import java.util.*;
27 
28 /**
29  * A class to intialize the Flutter engine.
30  */
31 public class FlutterMain {
32     private static final String TAG = "FlutterMain";
33 
34     // Must match values in sky::switches
35     private static final String AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name";
36     private static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path";
37     private static final String VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data";
38     private static final String ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data";
39     private static final String FLUTTER_ASSETS_DIR_KEY = "flutter-assets-dir";
40 
41     // XML Attribute keys supported in AndroidManifest.xml
42     public static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
43         FlutterMain.class.getName() + '.' + AOT_SHARED_LIBRARY_NAME;
44     public static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
45         FlutterMain.class.getName() + '.' + VM_SNAPSHOT_DATA_KEY;
46     public static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
47         FlutterMain.class.getName() + '.' + ISOLATE_SNAPSHOT_DATA_KEY;
48     public static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
49         FlutterMain.class.getName() + '.' + FLUTTER_ASSETS_DIR_KEY;
50 
51     // Resource names used for components of the precompiled snapshot.
52     private static final String DEFAULT_AOT_SHARED_LIBRARY_NAME = "libapp.so";
53     private static final String DEFAULT_VM_SNAPSHOT_DATA = "vm_snapshot_data";
54     private static final String DEFAULT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data";
55     private static final String DEFAULT_LIBRARY = "libflutter.so";
56     private static final String DEFAULT_KERNEL_BLOB = "kernel_blob.bin";
57     private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";
58 
59     private static boolean isRunningInRobolectricTest = false;
60 
61     @VisibleForTesting
setIsRunningInRobolectricTest(boolean isRunningInRobolectricTest)62     public static void setIsRunningInRobolectricTest(boolean isRunningInRobolectricTest) {
63         FlutterMain.isRunningInRobolectricTest = isRunningInRobolectricTest;
64     }
65 
66     @NonNull
fromFlutterAssets(@onNull String filePath)67     private static String fromFlutterAssets(@NonNull String filePath) {
68         return sFlutterAssetsDir + File.separator + filePath;
69     }
70 
71     // Mutable because default values can be overridden via config properties
72     private static String sAotSharedLibraryName = DEFAULT_AOT_SHARED_LIBRARY_NAME;
73     private static String sVmSnapshotData = DEFAULT_VM_SNAPSHOT_DATA;
74     private static String sIsolateSnapshotData = DEFAULT_ISOLATE_SNAPSHOT_DATA;
75     private static String sFlutterAssetsDir = DEFAULT_FLUTTER_ASSETS_DIR;
76 
77     private static boolean sInitialized = false;
78 
79     @Nullable
80     private static ResourceExtractor sResourceExtractor;
81     @Nullable
82     private static Settings sSettings;
83 
84     public static class Settings {
85         private String logTag;
86 
87         @Nullable
getLogTag()88         public String getLogTag() {
89             return logTag;
90         }
91 
92         /**
93          * Set the tag associated with Flutter app log messages.
94          * @param tag Log tag.
95          */
setLogTag(String tag)96         public void setLogTag(String tag) {
97             logTag = tag;
98         }
99     }
100 
101     /**
102      * Starts initialization of the native system.
103      * @param applicationContext The Android application context.
104      */
startInitialization(@onNull Context applicationContext)105     public static void startInitialization(@NonNull Context applicationContext) {
106         // Do nothing if we're running this in a Robolectric test.
107         if (isRunningInRobolectricTest) {
108             return;
109         }
110         startInitialization(applicationContext, new Settings());
111     }
112 
113     /**
114      * Starts initialization of the native system.
115      * @param applicationContext The Android application context.
116      * @param settings Configuration settings.
117      */
startInitialization(@onNull Context applicationContext, @NonNull Settings settings)118     public static void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
119         // Do nothing if we're running this in a Robolectric test.
120         if (isRunningInRobolectricTest) {
121             return;
122         }
123 
124         if (Looper.myLooper() != Looper.getMainLooper()) {
125           throw new IllegalStateException("startInitialization must be called on the main thread");
126         }
127         // Do not run startInitialization more than once.
128         if (sSettings != null) {
129           return;
130         }
131 
132         sSettings = settings;
133 
134         long initStartTimestampMillis = SystemClock.uptimeMillis();
135         initConfig(applicationContext);
136         initResources(applicationContext);
137 
138         System.loadLibrary("flutter");
139 
140         VsyncWaiter
141             .getInstance((WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE))
142             .init();
143 
144         // We record the initialization time using SystemClock because at the start of the
145         // initialization we have not yet loaded the native library to call into dart_tools_api.h.
146         // To get Timeline timestamp of the start of initialization we simply subtract the delta
147         // from the Timeline timestamp at the current moment (the assumption is that the overhead
148         // of the JNI call is negligible).
149         long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
150         FlutterJNI.nativeRecordStartTimestamp(initTimeMillis);
151     }
152 
153     /**
154      * Blocks until initialization of the native system has completed.
155      * @param applicationContext The Android application context.
156      * @param args Flags sent to the Flutter runtime.
157      */
ensureInitializationComplete(@onNull Context applicationContext, @Nullable String[] args)158     public static void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args) {
159         // Do nothing if we're running this in a Robolectric test.
160         if (isRunningInRobolectricTest) {
161             return;
162         }
163 
164         if (Looper.myLooper() != Looper.getMainLooper()) {
165           throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
166         }
167         if (sSettings == null) {
168           throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
169         }
170         if (sInitialized) {
171             return;
172         }
173         try {
174             if (sResourceExtractor != null) {
175                 sResourceExtractor.waitForCompletion();
176             }
177 
178             List<String> shellArgs = new ArrayList<>();
179             shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
180 
181             ApplicationInfo applicationInfo = getApplicationInfo(applicationContext);
182             shellArgs.add("--icu-native-lib-path=" + applicationInfo.nativeLibraryDir + File.separator + DEFAULT_LIBRARY);
183 
184             if (args != null) {
185                 Collections.addAll(shellArgs, args);
186             }
187 
188             String kernelPath = null;
189             if (BuildConfig.DEBUG) {
190                 String snapshotAssetPath = PathUtils.getDataDirectory(applicationContext) + File.separator + sFlutterAssetsDir;
191                 kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
192                 shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
193                 shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + sVmSnapshotData);
194                 shellArgs.add("--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + sIsolateSnapshotData);
195             } else {
196                 shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + sAotSharedLibraryName);
197 
198                 // Most devices can load the AOT shared library based on the library name
199                 // with no directory path.  Provide a fully qualified path to the library
200                 // as a workaround for devices where that fails.
201                 shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + applicationInfo.nativeLibraryDir + File.separator + sAotSharedLibraryName);
202             }
203 
204             shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext));
205             if (sSettings.getLogTag() != null) {
206                 shellArgs.add("--log-tag=" + sSettings.getLogTag());
207             }
208 
209             String appStoragePath = PathUtils.getFilesDir(applicationContext);
210             String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
211             FlutterJNI.nativeInit(applicationContext, shellArgs.toArray(new String[0]),
212                 kernelPath, appStoragePath, engineCachesPath);
213 
214             sInitialized = true;
215         } catch (Exception e) {
216             Log.e(TAG, "Flutter initialization failed.", e);
217             throw new RuntimeException(e);
218         }
219     }
220 
221     /**
222      * Same as {@link #ensureInitializationComplete(Context, String[])} but waiting on a background
223      * thread, then invoking {@code callback} on the {@code callbackHandler}.
224      */
ensureInitializationCompleteAsync( @onNull Context applicationContext, @Nullable String[] args, @NonNull Handler callbackHandler, @NonNull Runnable callback )225     public static void ensureInitializationCompleteAsync(
226         @NonNull Context applicationContext,
227         @Nullable String[] args,
228         @NonNull Handler callbackHandler,
229         @NonNull Runnable callback
230     ) {
231         // Do nothing if we're running this in a Robolectric test.
232         if (isRunningInRobolectricTest) {
233             return;
234         }
235 
236         if (Looper.myLooper() != Looper.getMainLooper()) {
237             throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
238         }
239         if (sSettings == null) {
240             throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
241         }
242         if (sInitialized) {
243             return;
244         }
245         new Thread(new Runnable() {
246             @Override
247             public void run() {
248                 if (sResourceExtractor != null) {
249                     sResourceExtractor.waitForCompletion();
250                 }
251                 new Handler(Looper.getMainLooper()).post(new Runnable() {
252                     @Override
253                     public void run() {
254                         ensureInitializationComplete(applicationContext.getApplicationContext(), args);
255                         callbackHandler.post(callback);
256                     }
257                 });
258             }
259         }).start();
260     }
261 
262     @NonNull
getApplicationInfo(@onNull Context applicationContext)263     private static ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) {
264         try {
265             return applicationContext
266                 .getPackageManager()
267                 .getApplicationInfo(applicationContext.getPackageName(), PackageManager.GET_META_DATA);
268         } catch (PackageManager.NameNotFoundException e) {
269             throw new RuntimeException(e);
270         }
271     }
272 
273     /**
274      * Initialize our Flutter config values by obtaining them from the
275      * manifest XML file, falling back to default values.
276      */
initConfig(@onNull Context applicationContext)277     private static void initConfig(@NonNull Context applicationContext) {
278         Bundle metadata = getApplicationInfo(applicationContext).metaData;
279 
280         // There isn't a `<meta-data>` tag as a direct child of `<application>` in
281         // `AndroidManifest.xml`.
282         if (metadata == null) {
283             return;
284         }
285 
286         sAotSharedLibraryName = metadata.getString(PUBLIC_AOT_SHARED_LIBRARY_NAME, DEFAULT_AOT_SHARED_LIBRARY_NAME);
287         sFlutterAssetsDir = metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, DEFAULT_FLUTTER_ASSETS_DIR);
288 
289         sVmSnapshotData = metadata.getString(PUBLIC_VM_SNAPSHOT_DATA_KEY, DEFAULT_VM_SNAPSHOT_DATA);
290         sIsolateSnapshotData = metadata.getString(PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, DEFAULT_ISOLATE_SNAPSHOT_DATA);
291     }
292 
293     /**
294      * Extract assets out of the APK that need to be cached as uncompressed
295      * files on disk.
296      */
initResources(@onNull Context applicationContext)297     private static void initResources(@NonNull Context applicationContext) {
298         new ResourceCleaner(applicationContext).start();
299 
300         if (BuildConfig.DEBUG) {
301             final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
302             final String packageName = applicationContext.getPackageName();
303             final PackageManager packageManager = applicationContext.getPackageManager();
304             final AssetManager assetManager = applicationContext.getResources().getAssets();
305             sResourceExtractor = new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);
306 
307             // In debug/JIT mode these assets will be written to disk and then
308             // mapped into memory so they can be provided to the Dart VM.
309             sResourceExtractor
310                 .addResource(fromFlutterAssets(sVmSnapshotData))
311                 .addResource(fromFlutterAssets(sIsolateSnapshotData))
312                 .addResource(fromFlutterAssets(DEFAULT_KERNEL_BLOB));
313 
314             sResourceExtractor.start();
315         }
316     }
317 
318     @NonNull
findAppBundlePath()319     public static String findAppBundlePath() {
320         return sFlutterAssetsDir;
321     }
322 
323     @Deprecated
324     @Nullable
findAppBundlePath(@onNull Context applicationContext)325     public static String findAppBundlePath(@NonNull Context applicationContext) {
326         return sFlutterAssetsDir;
327     }
328 
329     /**
330      * Returns the file name for the given asset.
331      * The returned file name can be used to access the asset in the APK
332      * through the {@link android.content.res.AssetManager} API.
333      *
334      * @param asset the name of the asset. The name can be hierarchical
335      * @return      the filename to be used with {@link android.content.res.AssetManager}
336      */
337     @NonNull
getLookupKeyForAsset(@onNull String asset)338     public static String getLookupKeyForAsset(@NonNull String asset) {
339         return fromFlutterAssets(asset);
340     }
341 
342     /**
343      * Returns the file name for the given asset which originates from the
344      * specified packageName. The returned file name can be used to access
345      * the asset in the APK through the {@link android.content.res.AssetManager} API.
346      *
347      * @param asset       the name of the asset. The name can be hierarchical
348      * @param packageName the name of the package from which the asset originates
349      * @return            the file name to be used with {@link android.content.res.AssetManager}
350      */
351     @NonNull
getLookupKeyForAsset(@onNull String asset, @NonNull String packageName)352     public static String getLookupKeyForAsset(@NonNull String asset, @NonNull String packageName) {
353         return getLookupKeyForAsset(
354             "packages" + File.separator + packageName + File.separator + asset);
355     }
356 }
357