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