blob: 66b2fff70783a22e2ad33505710cfff5bd1454c1 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.view;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import io.flutter.util.PathUtils;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* A class to intialize the Flutter engine.
*/
public class FlutterMain {
private static final String TAG = "FlutterMain";
// Must match values in sky::shell::switches
private static final String AOT_SHARED_LIBRARY_PATH = "aot-shared-library-path";
private static final String AOT_SNAPSHOT_PATH_KEY = "aot-snapshot-path";
private static final String AOT_VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data";
private static final String AOT_VM_SNAPSHOT_INSTR_KEY = "vm-snapshot-instr";
private static final String AOT_ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data";
private static final String AOT_ISOLATE_SNAPSHOT_INSTR_KEY = "isolate-snapshot-instr";
private static final String FLX_KEY = "flx";
private static final String SNAPSHOT_BLOB_KEY = "snapshot-blob";
private static final String FLUTTER_ASSETS_DIR_KEY = "flutter-assets-dir";
// XML Attribute keys supported in AndroidManifest.xml
public static final String PUBLIC_AOT_AOT_SHARED_LIBRARY_PATH =
FlutterMain.class.getName() + '.' + AOT_SHARED_LIBRARY_PATH;
public static final String PUBLIC_AOT_VM_SNAPSHOT_DATA_KEY =
FlutterMain.class.getName() + '.' + AOT_VM_SNAPSHOT_DATA_KEY;
public static final String PUBLIC_AOT_VM_SNAPSHOT_INSTR_KEY =
FlutterMain.class.getName() + '.' + AOT_VM_SNAPSHOT_INSTR_KEY;
public static final String PUBLIC_AOT_ISOLATE_SNAPSHOT_DATA_KEY =
FlutterMain.class.getName() + '.' + AOT_ISOLATE_SNAPSHOT_DATA_KEY;
public static final String PUBLIC_AOT_ISOLATE_SNAPSHOT_INSTR_KEY =
FlutterMain.class.getName() + '.' + AOT_ISOLATE_SNAPSHOT_INSTR_KEY;
public static final String PUBLIC_FLX_KEY =
FlutterMain.class.getName() + '.' + FLX_KEY;
public static final String PUBLIC_SNAPSHOT_BLOB_KEY =
FlutterMain.class.getName() + '.' + SNAPSHOT_BLOB_KEY;
public static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
FlutterMain.class.getName() + '.' + FLUTTER_ASSETS_DIR_KEY;
// Resource names used for components of the precompiled snapshot.
private static final String DEFAULT_AOT_SHARED_LIBRARY_PATH= "app.so";
private static final String DEFAULT_AOT_VM_SNAPSHOT_DATA = "vm_snapshot_data";
private static final String DEFAULT_AOT_VM_SNAPSHOT_INSTR = "vm_snapshot_instr";
private static final String DEFAULT_AOT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data";
private static final String DEFAULT_AOT_ISOLATE_SNAPSHOT_INSTR = "isolate_snapshot_instr";
private static final String DEFAULT_FLX = "app.flx";
private static final String DEFAULT_SNAPSHOT_BLOB = "snapshot_blob.bin";
private static final String DEFAULT_KERNEL_BLOB = "kernel_blob.bin";
private static final String DEFAULT_PLATFORM_DILL = "platform.dill";
private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";
// Assets that are shared among all Flutter apps within an APK.
private static final String SHARED_ASSET_DIR = "flutter_shared";
private static final String SHARED_ASSET_ICU_DATA = "icudtl.dat";
private static String fromFlutterAssets(String filePath) {
return sFlutterAssetsDir + File.separator + filePath;
}
// Mutable because default values can be overridden via config properties
private static String sAotSharedLibraryPath = DEFAULT_AOT_SHARED_LIBRARY_PATH;
private static String sAotVmSnapshotData = DEFAULT_AOT_VM_SNAPSHOT_DATA;
private static String sAotVmSnapshotInstr = DEFAULT_AOT_VM_SNAPSHOT_INSTR;
private static String sAotIsolateSnapshotData = DEFAULT_AOT_ISOLATE_SNAPSHOT_DATA;
private static String sAotIsolateSnapshotInstr = DEFAULT_AOT_ISOLATE_SNAPSHOT_INSTR;
private static String sFlx = DEFAULT_FLX;
private static String sSnapshotBlob = DEFAULT_SNAPSHOT_BLOB;
private static String sFlutterAssetsDir = DEFAULT_FLUTTER_ASSETS_DIR;
private static boolean sInitialized = false;
private static ResourceExtractor sResourceExtractor;
private static boolean sIsPrecompiledAsBlobs;
private static boolean sIsPrecompiledAsSharedLibrary;
private static Settings sSettings;
private static String sIcuDataPath;
private static final class ImmutableSetBuilder<T> {
static <T> ImmutableSetBuilder<T> newInstance() {
return new ImmutableSetBuilder<>();
}
HashSet<T> set = new HashSet<>();
private ImmutableSetBuilder() {}
ImmutableSetBuilder<T> add(T element) {
set.add(element);
return this;
}
@SafeVarargs
final ImmutableSetBuilder<T> add(T... elements) {
for (T element : elements) {
set.add(element);
}
return this;
}
Set<T> build() {
return Collections.unmodifiableSet(set);
}
}
public static class Settings {
private String logTag;
public String getLogTag() {
return logTag;
}
/**
* Set the tag associated with Flutter app log messages.
* @param tag Log tag.
*/
public void setLogTag(String tag) {
logTag = tag;
}
}
/**
* Starts initialization of the native system.
* @param applicationContext The Android application context.
*/
public static void startInitialization(Context applicationContext) {
startInitialization(applicationContext, new Settings());
}
/**
* Starts initialization of the native system.
* @param applicationContext The Android application context.
* @param settings Configuration settings.
*/
public static void startInitialization(Context applicationContext, Settings settings) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("startInitialization must be called on the main thread");
}
// Do not run startInitialization more than once.
if (sSettings != null) {
return;
}
sSettings = settings;
long initStartTimestampMillis = SystemClock.uptimeMillis();
initConfig(applicationContext);
initAot(applicationContext);
initResources(applicationContext);
System.loadLibrary("flutter");
// We record the initialization time using SystemClock because at the start of the
// initialization we have not yet loaded the native library to call into dart_tools_api.h.
// To get Timeline timestamp of the start of initialization we simply subtract the delta
// from the Timeline timestamp at the current moment (the assumption is that the overhead
// of the JNI call is negligible).
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
nativeRecordStartTimestamp(initTimeMillis);
}
/**
* Blocks until initialization of the native system has completed.
* @param applicationContext The Android application context.
* @param args Flags sent to the Flutter runtime.
*/
public static void ensureInitializationComplete(Context applicationContext, String[] args) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
}
if (sSettings == null) {
throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
}
if (sInitialized) {
return;
}
try {
sResourceExtractor.waitForCompletion();
List<String> shellArgs = new ArrayList<>();
shellArgs.add("--icu-data-file-path=" + sIcuDataPath);
if (args != null) {
Collections.addAll(shellArgs, args);
}
if (sIsPrecompiledAsSharedLibrary) {
shellArgs.add("--" + AOT_SHARED_LIBRARY_PATH + "=" +
new File(PathUtils.getDataDirectory(applicationContext), sAotSharedLibraryPath));
} else {
if (sIsPrecompiledAsBlobs) {
shellArgs.add("--" + AOT_SNAPSHOT_PATH_KEY + "=" +
PathUtils.getDataDirectory(applicationContext));
} else {
shellArgs.add("--cache-dir-path=" +
PathUtils.getCacheDirectory(applicationContext));
shellArgs.add("--" + AOT_SNAPSHOT_PATH_KEY + "=" +
PathUtils.getDataDirectory(applicationContext) + "/" + sFlutterAssetsDir);
}
shellArgs.add("--" + AOT_VM_SNAPSHOT_DATA_KEY + "=" + sAotVmSnapshotData);
shellArgs.add("--" + AOT_VM_SNAPSHOT_INSTR_KEY + "=" + sAotVmSnapshotInstr);
shellArgs.add("--" + AOT_ISOLATE_SNAPSHOT_DATA_KEY + "=" + sAotIsolateSnapshotData);
shellArgs.add("--" + AOT_ISOLATE_SNAPSHOT_INSTR_KEY + "=" + sAotIsolateSnapshotInstr);
}
if (sSettings.getLogTag() != null) {
shellArgs.add("--log-tag=" + sSettings.getLogTag());
}
String appBundlePath = findAppBundlePath(applicationContext);
nativeInit(applicationContext, shellArgs.toArray(new String[0]),
appBundlePath);
sInitialized = true;
} catch (Exception e) {
Log.e(TAG, "Flutter initialization failed.", e);
throw new RuntimeException(e);
}
}
private static native void nativeInit(Context context, String[] args, String bundlePath);
private static native void nativeRecordStartTimestamp(long initTimeMillis);
/**
* Initialize our Flutter config values by obtaining them from the
* manifest XML file, falling back to default values.
*/
private static void initConfig(Context applicationContext) {
try {
Bundle metadata = applicationContext.getPackageManager().getApplicationInfo(
applicationContext.getPackageName(), PackageManager.GET_META_DATA).metaData;
if (metadata != null) {
sAotSharedLibraryPath = metadata.getString(PUBLIC_AOT_AOT_SHARED_LIBRARY_PATH, DEFAULT_AOT_SHARED_LIBRARY_PATH);
sAotVmSnapshotData = metadata.getString(PUBLIC_AOT_VM_SNAPSHOT_DATA_KEY, DEFAULT_AOT_VM_SNAPSHOT_DATA);
sAotVmSnapshotInstr = metadata.getString(PUBLIC_AOT_VM_SNAPSHOT_INSTR_KEY, DEFAULT_AOT_VM_SNAPSHOT_INSTR);
sAotIsolateSnapshotData = metadata.getString(PUBLIC_AOT_ISOLATE_SNAPSHOT_DATA_KEY, DEFAULT_AOT_ISOLATE_SNAPSHOT_DATA);
sAotIsolateSnapshotInstr = metadata.getString(PUBLIC_AOT_ISOLATE_SNAPSHOT_INSTR_KEY, DEFAULT_AOT_ISOLATE_SNAPSHOT_INSTR);
sFlx = metadata.getString(PUBLIC_FLX_KEY, DEFAULT_FLX);
sSnapshotBlob = metadata.getString(PUBLIC_SNAPSHOT_BLOB_KEY, DEFAULT_SNAPSHOT_BLOB);
sFlutterAssetsDir = metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, DEFAULT_FLUTTER_ASSETS_DIR);
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
}
private static void initResources(Context applicationContext) {
Context context = applicationContext;
new ResourceCleaner(context).start();
sResourceExtractor = new ResourceExtractor(context);
// Search for the icudtl.dat file at the old and new locations.
// TODO(jsimmons): remove the old location when all tools have been updated.
Set<String> sharedAssets = listAssets(applicationContext, SHARED_ASSET_DIR);
String icuAssetPath;
if (sharedAssets.contains(SHARED_ASSET_ICU_DATA)) {
icuAssetPath = SHARED_ASSET_DIR + File.separator + SHARED_ASSET_ICU_DATA;
} else {
icuAssetPath = SHARED_ASSET_ICU_DATA;
}
sResourceExtractor.addResource(icuAssetPath);
sIcuDataPath = PathUtils.getDataDirectory(applicationContext) + File.separator + icuAssetPath;
sResourceExtractor
.addResource(fromFlutterAssets(sFlx))
.addResource(fromFlutterAssets(sSnapshotBlob))
.addResource(fromFlutterAssets(sAotVmSnapshotData))
.addResource(fromFlutterAssets(sAotVmSnapshotInstr))
.addResource(fromFlutterAssets(sAotIsolateSnapshotData))
.addResource(fromFlutterAssets(sAotIsolateSnapshotInstr))
.addResource(fromFlutterAssets(DEFAULT_KERNEL_BLOB))
.addResource(fromFlutterAssets(DEFAULT_PLATFORM_DILL));
if (sIsPrecompiledAsSharedLibrary) {
sResourceExtractor
.addResource(sAotSharedLibraryPath);
} else {
sResourceExtractor
.addResource(sAotVmSnapshotData)
.addResource(sAotVmSnapshotInstr)
.addResource(sAotIsolateSnapshotData)
.addResource(sAotIsolateSnapshotInstr)
.addResource(sSnapshotBlob);
}
sResourceExtractor.start();
}
/**
* Returns a list of the file names at the root of the application's asset
* path.
*/
private static Set<String> listAssets(Context applicationContext, String path) {
AssetManager manager = applicationContext.getResources().getAssets();
try {
return ImmutableSetBuilder.<String>newInstance()
.add(manager.list(path))
.build();
} catch (IOException e) {
Log.e(TAG, "Unable to list assets", e);
throw new RuntimeException(e);
}
}
private static void initAot(Context applicationContext) {
Set<String> assets = listAssets(applicationContext, "");
sIsPrecompiledAsBlobs = assets.containsAll(Arrays.asList(
sAotVmSnapshotData,
sAotVmSnapshotInstr,
sAotIsolateSnapshotData,
sAotIsolateSnapshotInstr
));
sIsPrecompiledAsSharedLibrary = assets.contains(sAotSharedLibraryPath);
if (sIsPrecompiledAsBlobs && sIsPrecompiledAsSharedLibrary) {
throw new RuntimeException(
"Found precompiled app as shared library and as Dart VM snapshots.");
}
}
public static boolean isRunningPrecompiledCode() {
return sIsPrecompiledAsBlobs || sIsPrecompiledAsSharedLibrary;
}
public static String findAppBundlePath(Context applicationContext) {
String dataDirectory = PathUtils.getDataDirectory(applicationContext);
File appBundle = new File(dataDirectory, sFlutterAssetsDir);
return appBundle.exists() ? appBundle.getPath() : null;
}
/**
* Returns the file name for the given asset.
* The returned file name can be used to access the asset in the APK
* through the {@link AssetManager} API.
*
* @param asset the name of the asset. The name can be hierarchical
* @return the filename to be used with {@link AssetManager}
*/
public static String getLookupKeyForAsset(String asset) {
return fromFlutterAssets(asset);
}
/**
* Returns the file name for the given asset which originates from the
* specified packageName. The returned file name can be used to access
* the asset in the APK through the {@link AssetManager} API.
*
* @param asset the name of the asset. The name can be hierarchical
* @param packageName the name of the package from which the asset originates
* @return the file name to be used with {@link AssetManager}
*/
public static String getLookupKeyForAsset(String asset, String packageName) {
return getLookupKeyForAsset(
"packages" + File.separator + packageName + File.separator + asset);
}
}