Skip to content

Commit

Permalink
Give more control over when dynamic patches get downloaded and instal…
Browse files Browse the repository at this point in the history
…led. (flutter#7327)

This change introduces manifest properties that control when dynamic patches are downloaded and installed in the application lifecycle.

Application developer can choose whether between install on restart, install on resume, or immediate forced install of dynamic patches.
  • Loading branch information
sbaranov authored Jan 2, 2019
1 parent 2d291c7 commit 8cca33a
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
27 changes: 25 additions & 2 deletions shell/platform/android/io/flutter/view/FlutterMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -283,19 +295,30 @@ 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)
.addResource(sAotVmSnapshotInstr)
.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.
Expand Down
15 changes: 14 additions & 1 deletion shell/platform/android/io/flutter/view/ResourceExtractor.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ ResourceExtractor start() {
}

void waitForCompletion() {
assert mExtractTask != null;
if (mExtractTask == null) {
return;
}

try {
mExtractTask.get();
Expand All @@ -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
Expand Down
94 changes: 92 additions & 2 deletions shell/platform/android/io/flutter/view/ResourceUpdater.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String, Void> {
@Override
protected Void doInBackground(String... args) {
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 8cca33a

Please sign in to comment.