diff --git a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java index 3e15de0cb6187..9e06e8f384de3 100644 --- a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java +++ b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java @@ -220,6 +220,7 @@ public void onStart() { @Override public void onResume() { Application app = (Application) activity.getApplicationContext(); + FlutterMain.onResume(app); if (app instanceof FlutterApplication) { FlutterApplication flutterApp = (FlutterApplication) app; flutterApp.setCurrentActivity(activity); diff --git a/shell/platform/android/io/flutter/view/FlutterMain.java b/shell/platform/android/io/flutter/view/FlutterMain.java index b01f243e30f66..16d1c0ec3e2d0 100644 --- a/shell/platform/android/io/flutter/view/FlutterMain.java +++ b/shell/platform/android/io/flutter/view/FlutterMain.java @@ -5,8 +5,11 @@ package io.flutter.view; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.app.AlarmManager; +import android.app.PendingIntent; import android.os.Bundle; import android.os.Looper; import android.os.SystemClock; @@ -266,8 +269,17 @@ private static void initResources(Context applicationContext) { if (metaData != null && metaData.getBoolean("DynamicPatching")) { sResourceUpdater = new ResourceUpdater(context); - sResourceUpdater.startUpdateDownloadOnce(); - sResourceUpdater.waitForDownloadCompletion(); + // Also checking for ON_RESUME here since it's more efficient than waiting for actual + // onResume. Even though actual onResume is imminent when the app has just restarted, + // it's better to start downloading now, in parallel with the rest of initialization, + // and avoid a second application restart a bit later when actual onResume happens. + if (sResourceUpdater.getDownloadMode() == ResourceUpdater.DownloadMode.ON_RESTART || + sResourceUpdater.getDownloadMode() == ResourceUpdater.DownloadMode.ON_RESUME) { + sResourceUpdater.startUpdateDownloadOnce(); + if (sResourceUpdater.getInstallMode() == ResourceUpdater.InstallMode.IMMEDIATE) { + sResourceUpdater.waitForDownloadCompletion(); + } + } } sResourceExtractor = new ResourceExtractor(context); @@ -283,9 +295,11 @@ private static void initResources(Context applicationContext) { .addResource(fromFlutterAssets(sAotIsolateSnapshotData)) .addResource(fromFlutterAssets(sAotIsolateSnapshotInstr)) .addResource(fromFlutterAssets(DEFAULT_KERNEL_BLOB)); + if (sIsPrecompiledAsSharedLibrary) { sResourceExtractor .addResource(sAotSharedLibraryPath); + } else { sResourceExtractor .addResource(sAotVmSnapshotData) @@ -293,9 +307,18 @@ private static void initResources(Context applicationContext) { .addResource(sAotIsolateSnapshotData) .addResource(sAotIsolateSnapshotInstr); } + sResourceExtractor.start(); } + public static void onResume(Context context) { + if (sResourceUpdater != null) { + if (sResourceUpdater.getDownloadMode() == ResourceUpdater.DownloadMode.ON_RESUME) { + sResourceUpdater.startUpdateDownloadOnce(); + } + } + } + /** * Returns a list of the file names at the root of the application's asset * path. diff --git a/shell/platform/android/io/flutter/view/ResourceExtractor.java b/shell/platform/android/io/flutter/view/ResourceExtractor.java index 0aaf58ec45474..20a07a11f48d1 100644 --- a/shell/platform/android/io/flutter/view/ResourceExtractor.java +++ b/shell/platform/android/io/flutter/view/ResourceExtractor.java @@ -112,7 +112,9 @@ ResourceExtractor start() { } void waitForCompletion() { - assert mExtractTask != null; + if (mExtractTask == null) { + return; + } try { mExtractTask.get(); @@ -125,6 +127,17 @@ void waitForCompletion() { } } + boolean filesMatch() { + JSONObject updateManifest = readUpdateManifest(); + if (!validateUpdateManifest(updateManifest)) { + updateManifest = null; + } + + final File dataDir = new File(PathUtils.getDataDirectory(mContext)); + final String timestamp = checkTimestamp(dataDir, updateManifest); + return (timestamp == null); + } + private String[] getExistingTimestamps(File dataDir) { return dataDir.list(new FilenameFilter() { @Override diff --git a/shell/platform/android/io/flutter/view/ResourceUpdater.java b/shell/platform/android/io/flutter/view/ResourceUpdater.java index 679388e6e1f14..203ae18d052f7 100644 --- a/shell/platform/android/io/flutter/view/ResourceUpdater.java +++ b/shell/platform/android/io/flutter/view/ResourceUpdater.java @@ -26,6 +26,38 @@ public final class ResourceUpdater { private static final String TAG = "ResourceUpdater"; + // Controls when to check if a new patch is available for download, and start downloading. + // Note that by default the application will not block to wait for the download to finish. + // Patches are downloaded in the background, but the developer can also use [InstallMode] + // to control whether to block on download completion, in order to install patches sooner. + enum DownloadMode { + // Check for and download patch on application restart (but not necessarily apply it). + // This is the default setting which will also check for new patches least frequently. + ON_RESTART, + + // Check for and download patch on application resume (but not necessarily apply it). + // By definition, this setting will check for new patches both on restart and resume. + ON_RESUME + } + + // Controls when to check that a new patch has been downloaded and needs to be applied. + enum InstallMode { + // Wait for next application restart before applying downloaded patch. With this + // setting, the application will not block to wait for patch download to finish. + // The application can be restarted later either by the user, or by the system, + // for any reason, at which point the newly downloaded patch will get applied. + // This is the default setting, and is the least disruptive way to apply patches. + ON_NEXT_RESTART, + + // Apply patch as soon as it's downloaded. This will block to wait for new patch + // download to finish, and will immediately apply it. This setting increases the + // urgency with which patches are installed, but may also affect startup latency. + // For now, this setting is only effective when download happens during restart. + // Patches downloaded during resume will not get installed immediately as that + // requires force restarting the app (which might be implemented in the future). + IMMEDIATE + } + private static class DownloadTask extends AsyncTask { @Override protected Void doInBackground(String... args) { @@ -136,15 +168,73 @@ public String buildUpdateDownloadURL() { return uri.normalize().toString(); } + public DownloadMode getDownloadMode() { + Bundle metaData; + try { + metaData = context.getPackageManager().getApplicationInfo( + context.getPackageName(), PackageManager.GET_META_DATA).metaData; + + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + + if (metaData == null) { + return DownloadMode.ON_RESTART; + } + + String patchDownloadMode = metaData.getString("PatchDownloadMode"); + if (patchDownloadMode == null) { + return DownloadMode.ON_RESTART; + } + + try { + return DownloadMode.valueOf(patchDownloadMode); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid PatchDownloadMode " + patchDownloadMode); + return DownloadMode.ON_RESTART; + } + } + + public InstallMode getInstallMode() { + Bundle metaData; + try { + metaData = context.getPackageManager().getApplicationInfo( + context.getPackageName(), PackageManager.GET_META_DATA).metaData; + + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + + if (metaData == null) { + return InstallMode.ON_NEXT_RESTART; + } + + String patchInstallMode = metaData.getString("PatchInstallMode"); + if (patchInstallMode == null) { + return InstallMode.ON_NEXT_RESTART; + } + + try { + return InstallMode.valueOf(patchInstallMode); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid PatchInstallMode " + patchInstallMode); + return InstallMode.ON_NEXT_RESTART; + } + } + public void startUpdateDownloadOnce() { - assert downloadTask == null; + if (downloadTask != null ) { + return; + } downloadTask = new DownloadTask(); downloadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, buildUpdateDownloadURL(), getUpdateInstallationPath()); } public void waitForDownloadCompletion() { - assert downloadTask != null; + if (downloadTask == null) { + return; + } try { downloadTask.get(); } catch (CancellationException e) {