From 1a06df7c2a1e760f849415e2092d6875e551b92f Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 29 Apr 2020 15:48:47 -0700 Subject: [PATCH 01/16] Wire up channel for restoration data --- shell/platform/android/BUILD.gn | 1 + .../FlutterActivityAndFragmentDelegate.java | 21 +++++++++++++++---- .../embedding/engine/FlutterEngine.java | 15 +++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index f68249194b751..77f29f0d54c9c 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -184,6 +184,7 @@ android_java_sources = [ "io/flutter/embedding/engine/systemchannels/NavigationChannel.java", "io/flutter/embedding/engine/systemchannels/PlatformChannel.java", "io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java", + "io/flutter/embedding/engine/systemchannels/RestorationChannel.java", "io/flutter/embedding/engine/systemchannels/SettingsChannel.java", "io/flutter/embedding/engine/systemchannels/SystemChannel.java", "io/flutter/embedding/engine/systemchannels/TextInputChannel.java", diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 93ff3e7165e0c..b1c14843b35a8 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -63,6 +63,8 @@ */ /* package */ final class FlutterActivityAndFragmentDelegate { private static final String TAG = "FlutterActivityAndFragmentDelegate"; + private static final String FRAMEWORK_BUNDLE_KEY = "framework"; + private static final String PLUGINS_BUNDLE_KEY = "plugins"; // The FlutterActivity or FlutterFragment that is delegating most of its calls // to this FlutterActivityAndFragmentDelegate. @@ -293,11 +295,18 @@ View onCreateView( } void onActivityCreated(@Nullable Bundle bundle) { - Log.v(TAG, "onActivityCreated. Giving plugins an opportunity to restore state."); + Log.v(TAG, "onActivityCreated. Giving framework and plugins an opportunity to restore state."); ensureAlive(); + Bundle pluginsBundle = null; + if (bundle != null) { + flutterEngine.getRestorationChannel() + .setRestorationDataForFramework(bundle.getByteArray(FRAMEWORK_BUNDLE_KEY)); + pluginsBundle = bundle.getBundle(PLUGINS_BUNDLE_KEY); + } + if (host.shouldAttachEngineToActivity()) { - flutterEngine.getActivityControlSurface().onRestoreInstanceState(bundle); + flutterEngine.getActivityControlSurface().onRestoreInstanceState(pluginsBundle); } } @@ -444,11 +453,15 @@ void onDestroyView() { } void onSaveInstanceState(@Nullable Bundle bundle) { - Log.v(TAG, "onSaveInstanceState. Giving plugins an opportunity to save state."); + Log.v(TAG, "onSaveInstanceState. Giving framework and plugins an opportunity to save state."); ensureAlive(); + bundle.putByteArray(FRAMEWORK_BUNDLE_KEY, flutterEngine.getRestorationChannel().getRestorationDataFromFramework()); + if (host.shouldAttachEngineToActivity()) { - flutterEngine.getActivityControlSurface().onSaveInstanceState(bundle); + final Bundle plugins = new Bundle(); + flutterEngine.getActivityControlSurface().onSaveInstanceState(plugins); + bundle.putBundle(PLUGINS_BUNDLE_KEY, plugins); } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 3b007172b80ad..29cb66a9c4d95 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -24,6 +24,7 @@ import io.flutter.embedding.engine.systemchannels.MouseCursorChannel; import io.flutter.embedding.engine.systemchannels.NavigationChannel; import io.flutter.embedding.engine.systemchannels.PlatformChannel; +import io.flutter.embedding.engine.systemchannels.RestorationChannel; import io.flutter.embedding.engine.systemchannels.SettingsChannel; import io.flutter.embedding.engine.systemchannels.SystemChannel; import io.flutter.embedding.engine.systemchannels.TextInputChannel; @@ -80,6 +81,7 @@ public class FlutterEngine { @NonNull private final LocalizationChannel localizationChannel; @NonNull private final MouseCursorChannel mouseCursorChannel; @NonNull private final NavigationChannel navigationChannel; + @NonNull private final RestorationChannel restorationChannel; @NonNull private final PlatformChannel platformChannel; @NonNull private final SettingsChannel settingsChannel; @NonNull private final SystemChannel systemChannel; @@ -224,6 +226,7 @@ public FlutterEngine( mouseCursorChannel = new MouseCursorChannel(dartExecutor); navigationChannel = new NavigationChannel(dartExecutor); platformChannel = new PlatformChannel(dartExecutor); + restorationChannel = new RestorationChannel(dartExecutor); settingsChannel = new SettingsChannel(dartExecutor); systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); @@ -380,6 +383,18 @@ public PlatformChannel getPlatformChannel() { return platformChannel; } + /** + * System channel to exchange restoration data between framework and engine. + * + *

The engine can obtain the current restoration data from the framework via this channel + * to store it on disk and - when the app is relaunched - provide the stored data back to the + * framework to recreate the original state of the app. + */ + @NonNull + public RestorationChannel getRestorationChannel() { + return restorationChannel; + } + /** * System channel that sends platform/user settings from Android to Flutter, e.g., time format, * scale factor, etc. From c7ee80b529bb37f33db4156547d0f9170d87bcdc Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 29 Apr 2020 15:57:16 -0700 Subject: [PATCH 02/16] format + add file --- .../FlutterActivityAndFragmentDelegate.java | 7 +- .../embedding/engine/FlutterEngine.java | 4 +- .../systemchannels/RestorationChannel.java | 65 +++++++++++++++++++ 3 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index b1c14843b35a8..3ba534d79eb36 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -300,7 +300,8 @@ void onActivityCreated(@Nullable Bundle bundle) { Bundle pluginsBundle = null; if (bundle != null) { - flutterEngine.getRestorationChannel() + flutterEngine + .getRestorationChannel() .setRestorationDataForFramework(bundle.getByteArray(FRAMEWORK_BUNDLE_KEY)); pluginsBundle = bundle.getBundle(PLUGINS_BUNDLE_KEY); } @@ -456,7 +457,9 @@ void onSaveInstanceState(@Nullable Bundle bundle) { Log.v(TAG, "onSaveInstanceState. Giving framework and plugins an opportunity to save state."); ensureAlive(); - bundle.putByteArray(FRAMEWORK_BUNDLE_KEY, flutterEngine.getRestorationChannel().getRestorationDataFromFramework()); + bundle.putByteArray( + FRAMEWORK_BUNDLE_KEY, + flutterEngine.getRestorationChannel().getRestorationDataFromFramework()); if (host.shouldAttachEngineToActivity()) { final Bundle plugins = new Bundle(); diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 29cb66a9c4d95..4a6dab36f9b1a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -386,8 +386,8 @@ public PlatformChannel getPlatformChannel() { /** * System channel to exchange restoration data between framework and engine. * - *

The engine can obtain the current restoration data from the framework via this channel - * to store it on disk and - when the app is relaunched - provide the stored data back to the + *

The engine can obtain the current restoration data from the framework via this channel to + * store it on disk and - when the app is relaunched - provide the stored data back to the * framework to recreate the original state of the app. */ @NonNull diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java new file mode 100644 index 0000000000000..7d7d18f4aac17 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java @@ -0,0 +1,65 @@ +// Copyright 2013 The Flutter 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.embedding.engine.systemchannels; + +import android.support.annotation.NonNull; +import io.flutter.Log; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.StandardMethodCodec; + +/** + * System channel to exchange restoration data between framework and engine. + * + *

The engine can obtain the current restoration data from the framework via this channel to + * store it on disk and - when the app is relaunched - provide the stored data back to the framework + * to recreate the original state of the app. + */ +public class RestorationChannel { + private static final String TAG = "RestorationChannel"; + + public RestorationChannel(@NonNull DartExecutor dartExecutor) { + MethodChannel channel = + new MethodChannel(dartExecutor, "flutter/restoration", StandardMethodCodec.INSTANCE); + channel.setMethodCallHandler(handler); + } + + private byte[] dataForFramework; + private byte[] dataFromFramework; + + /** Obtain the most current restoration data that the framework has provided. */ + public byte[] getRestorationDataFromFramework() { + return dataFromFramework; + } + + /** Set the restoration data that will be sent to the framework when the framework requests it. */ + public void setRestorationDataForFramework(byte[] data) { + dataForFramework = data; + } + + private final MethodChannel.MethodCallHandler handler = + new MethodChannel.MethodCallHandler() { + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + final String method = call.method; + final Object args = call.arguments; + Log.v(TAG, "Received '" + method + "' message."); + switch (method) { + case "put": + dataFromFramework = (byte[]) args; + result.success(null); + break; + case "get": + result.success(dataForFramework); + dataForFramework = null; + break; + default: + result.notImplemented(); + break; + } + } + }; +} From b207c19e2d6c138fc24ae06ccf855cc58efebf8b Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 29 Apr 2020 16:29:35 -0700 Subject: [PATCH 03/16] license --- ci/licenses_golden/licenses_flutter | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 2770d2953015b..c9818556ae409 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -736,6 +736,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/system FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/NavigationChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SystemChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java From b2a4b80c7adf152345fc18b38471965510c39129 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Mon, 4 May 2020 15:47:20 -0700 Subject: [PATCH 04/16] Make it more configurable --- .../embedding/android/FlutterActivity.java | 12 ++++ .../FlutterActivityAndFragmentDelegate.java | 32 +++++++-- .../android/FlutterActivityLaunchConfigs.java | 1 + .../embedding/android/FlutterFragment.java | 16 +++++ .../embedding/engine/FlutterEngine.java | 46 ++++++++++++- .../systemchannels/RestorationChannel.java | 65 ++++++++++++++++--- 6 files changed, 153 insertions(+), 19 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 2237f304f8277..348c7c4d7e539 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -11,6 +11,7 @@ import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_BACKGROUND_MODE; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_ENABLE_STATE_RESTORATION; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_INITIAL_ROUTE; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY; @@ -949,6 +950,17 @@ public void onFlutterUiNoLongerDisplayed() { // no-op } + @Override + public boolean shouldRestoreAndSaveState() { + if (getIntent().hasExtra(EXTRA_ENABLE_STATE_RESTORATION)) { + return getIntent().getBooleanExtra(EXTRA_ENABLE_STATE_RESTORATION, false); + } + if (getCachedEngineId() != null) { + return false; + } + return true; + } + /** * Registers all plugins that an app lists in its pubspec.yaml. * diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 3ba534d79eb36..4f41bb4998ea5 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -25,6 +25,7 @@ import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; +import io.flutter.embedding.engine.systemchannels.RestorationChannel; import io.flutter.plugin.platform.PlatformPlugin; import java.util.Arrays; @@ -229,7 +230,8 @@ void onAttach(@NonNull Context context) { new FlutterEngine( host.getContext(), host.getFlutterShellArgs().toArray(), - /*automaticallyRegisterPlugins=*/ false); + /*automaticallyRegisterPlugins=*/ false, + /*willProvideRestorationData=*/ host.shouldRestoreAndSaveState()); isFlutterEngineFromHost = false; } @@ -299,11 +301,15 @@ void onActivityCreated(@Nullable Bundle bundle) { ensureAlive(); Bundle pluginsBundle = null; + byte[] frameworkBundle = null; if (bundle != null) { - flutterEngine - .getRestorationChannel() - .setRestorationDataForFramework(bundle.getByteArray(FRAMEWORK_BUNDLE_KEY)); pluginsBundle = bundle.getBundle(PLUGINS_BUNDLE_KEY); + frameworkBundle = bundle.getByteArray(FRAMEWORK_BUNDLE_KEY); + } + + final RestorationChannel restorationChannel = flutterEngine.getRestorationChannel(); + if (host.shouldRestoreAndSaveState() && !restorationChannel.hasRestorationDataBeenSet()) { + restorationChannel.setRestorationData(frameworkBundle); } if (host.shouldAttachEngineToActivity()) { @@ -457,9 +463,11 @@ void onSaveInstanceState(@Nullable Bundle bundle) { Log.v(TAG, "onSaveInstanceState. Giving framework and plugins an opportunity to save state."); ensureAlive(); - bundle.putByteArray( - FRAMEWORK_BUNDLE_KEY, - flutterEngine.getRestorationChannel().getRestorationDataFromFramework()); + if (host.shouldRestoreAndSaveState()) { + bundle.putByteArray( + FRAMEWORK_BUNDLE_KEY, + flutterEngine.getRestorationChannel().getRestorationData()); + } if (host.shouldAttachEngineToActivity()) { final Bundle plugins = new Bundle(); @@ -820,5 +828,15 @@ PlatformPlugin providePlatformPlugin( /** Invoked by this delegate when its {@link FlutterView} stops painting pixels. */ void onFlutterUiNoLongerDisplayed(); + + /** + * Whether instance state saving and restoration is enabled. + * + *

When this returns true, the instance state provided to {@code onActivityCreated(Bundle)} + * will be forwarded to the framework via the {@code RestorationChannel} and during + * @{@code onSaveInstanceState(Bundle)} the current framework instance state obtained from + * {@code RestorationChannel} will be stored in the provided bundle. + */ + boolean shouldRestoreAndSaveState(); } } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java index d0ff418c742bc..ae7a4a48e67a8 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java @@ -23,6 +23,7 @@ public class FlutterActivityLaunchConfigs { /* package */ static final String EXTRA_CACHED_ENGINE_ID = "cached_engine_id"; /* package */ static final String EXTRA_DESTROY_ENGINE_WITH_ACTIVITY = "destroy_engine_with_activity"; + /* package */ static final String EXTRA_ENABLE_STATE_RESTORATION = "enable_state_restoration"; // Default configuration. /* package */ static final String DEFAULT_DART_ENTRYPOINT = "main"; diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index 97ae4a25254ee..a0d1120360afe 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -113,6 +113,11 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm * outlive the {@code FlutterFragment}. */ protected static final String ARG_DESTROY_ENGINE_WITH_FRAGMENT = "destroy_engine_with_fragment"; + /** + * True if the framework state in the engine attached to this engine should be stored and restored + * when this fragment is created and destroyed. + */ + protected static final String ARG_ENABLE_STATE_RESTORATION = "enable_state_restoration"; /** * Creates a {@code FlutterFragment} with a default configuration. @@ -1018,6 +1023,17 @@ public void onFlutterUiNoLongerDisplayed() { } } + @Override + public boolean shouldRestoreAndSaveState() { + if (getArguments().containsKey(ARG_ENABLE_STATE_RESTORATION)) { + return getArguments().getBoolean(ARG_ENABLE_STATE_RESTORATION); + } + if (getCachedEngineId() != null) { + return false; + } + return true; + } + /** * Annotates methods in {@code FlutterFragment} that must be called by the containing {@code * Activity}. diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 4a6dab36f9b1a..3bfa341f78a2c 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -104,6 +104,7 @@ public void onPreEngineRestart() { } platformViewsController.onPreEngineRestart(); + restorationChannel.clearData(); } }; @@ -161,6 +162,25 @@ public FlutterEngine( automaticallyRegisterPlugins); } + /** + * Same as {@link #FlutterEngine(Context, String[], boolean)} with added support for configuring + * whether the engine will receive restoration data. + */ + public FlutterEngine( + @NonNull Context context, + @Nullable String[] dartVmArgs, + boolean automaticallyRegisterPlugins, + boolean willProvideRestorationData) { + this( + context, + FlutterLoader.getInstance(), + new FlutterJNI(), + new PlatformViewsController(), + dartVmArgs, + automaticallyRegisterPlugins, + willProvideRestorationData); + } + /** * Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[])} but with no Dart * VM flags. @@ -196,6 +216,27 @@ public FlutterEngine( automaticallyRegisterPlugins); } + /** + * Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[], boolean)}, plus + * the ability to provide a custom {@code PlatformViewsController}. + */ + public FlutterEngine( + @NonNull Context context, + @NonNull FlutterLoader flutterLoader, + @NonNull FlutterJNI flutterJNI, + @NonNull PlatformViewsController platformViewsController, + @Nullable String[] dartVmArgs, + boolean automaticallyRegisterPlugins) { + this( + context, + flutterLoader, + flutterJNI, + platformViewsController, + dartVmArgs, + automaticallyRegisterPlugins, + false); + } + /** Fully configurable {@code FlutterEngine} constructor. */ public FlutterEngine( @NonNull Context context, @@ -203,7 +244,8 @@ public FlutterEngine( @NonNull FlutterJNI flutterJNI, @NonNull PlatformViewsController platformViewsController, @Nullable String[] dartVmArgs, - boolean automaticallyRegisterPlugins) { + boolean automaticallyRegisterPlugins, + boolean willProvideRestorationData) { this.flutterJNI = flutterJNI; flutterLoader.startInitialization(context.getApplicationContext()); flutterLoader.ensureInitializationComplete(context, dartVmArgs); @@ -226,7 +268,7 @@ public FlutterEngine( mouseCursorChannel = new MouseCursorChannel(dartExecutor); navigationChannel = new NavigationChannel(dartExecutor); platformChannel = new PlatformChannel(dartExecutor); - restorationChannel = new RestorationChannel(dartExecutor); + restorationChannel = new RestorationChannel(dartExecutor, willProvideRestorationData); settingsChannel = new SettingsChannel(dartExecutor); systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java index 7d7d18f4aac17..8766c59a37243 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java @@ -21,23 +21,65 @@ public class RestorationChannel { private static final String TAG = "RestorationChannel"; - public RestorationChannel(@NonNull DartExecutor dartExecutor) { + public RestorationChannel(@NonNull DartExecutor dartExecutor, @NonNull boolean waitForRestorationData) { + this.waitForRestorationData = waitForRestorationData; MethodChannel channel = new MethodChannel(dartExecutor, "flutter/restoration", StandardMethodCodec.INSTANCE); channel.setMethodCallHandler(handler); } - private byte[] dataForFramework; - private byte[] dataFromFramework; + /** + * Whether {@code setRestorationData} will be called to provide restoration data for + * the framework. + * + *

When this is set to true, the channel will delay answering any requests for restoration data + * by the framework until {@code setRestorationData} has been called. It must be set + * to false if the engine never calls {@code setRestorationData}. If it has been set + * to true, but it later turns out that there is no restoration data, + * {@code setRestorationData} must be called with null. + */ + public final boolean waitForRestorationData; + + private byte[] restorationData; + private MethodChannel.Result pendingResult; + private boolean engineHasProvidedData = false; + private boolean frameworkHasRequestedData = false; + + /** + * Whether {@code setRestorationData} has been called. + */ + public boolean hasRestorationDataBeenSet() { + return engineHasProvidedData; + } /** Obtain the most current restoration data that the framework has provided. */ - public byte[] getRestorationDataFromFramework() { - return dataFromFramework; + public byte[] getRestorationData() { + return restorationData; } /** Set the restoration data that will be sent to the framework when the framework requests it. */ - public void setRestorationDataForFramework(byte[] data) { - dataForFramework = data; + public void setRestorationData(byte[] data) { + if (!waitForRestorationData || engineHasProvidedData) { + Log.e(TAG, "Channel has not been configured to wait for restoration data or data has already been provided."); + return; + } + engineHasProvidedData = true; + if (pendingResult != null) { + pendingResult.success(data); + pendingResult = null; + } else { + restorationData = data; + } + } + + + /** Clears the current restoration data. + * + *

This should be called just prior to a hot restart. Otherwise, after the hot restart the + * state prior to the hot restart will get restored. + */ + public void clearData() { + restorationData = null; } private final MethodChannel.MethodCallHandler handler = @@ -49,12 +91,15 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result Log.v(TAG, "Received '" + method + "' message."); switch (method) { case "put": - dataFromFramework = (byte[]) args; + restorationData = (byte[]) args; result.success(null); break; case "get": - result.success(dataForFramework); - dataForFramework = null; + if (engineHasProvidedData || !waitForRestorationData) { + result.success(restorationData); + } else { + pendingResult = result; + } break; default: result.notImplemented(); From d7459cab6af2a869c423bd82526ad85f5e00b478 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Mon, 4 May 2020 15:54:07 -0700 Subject: [PATCH 05/16] format --- .../FlutterActivityAndFragmentDelegate.java | 11 ++++--- .../embedding/engine/FlutterEngine.java | 30 +++++++++---------- .../systemchannels/RestorationChannel.java | 27 +++++++++-------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 4f41bb4998ea5..277f14f3ee768 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -465,8 +465,7 @@ void onSaveInstanceState(@Nullable Bundle bundle) { if (host.shouldRestoreAndSaveState()) { bundle.putByteArray( - FRAMEWORK_BUNDLE_KEY, - flutterEngine.getRestorationChannel().getRestorationData()); + FRAMEWORK_BUNDLE_KEY, flutterEngine.getRestorationChannel().getRestorationData()); } if (host.shouldAttachEngineToActivity()) { @@ -830,12 +829,12 @@ PlatformPlugin providePlatformPlugin( void onFlutterUiNoLongerDisplayed(); /** - * Whether instance state saving and restoration is enabled. + * Whether state restoration is enabled. * *

When this returns true, the instance state provided to {@code onActivityCreated(Bundle)} - * will be forwarded to the framework via the {@code RestorationChannel} and during - * @{@code onSaveInstanceState(Bundle)} the current framework instance state obtained from - * {@code RestorationChannel} will be stored in the provided bundle. + * will be forwarded to the framework via the {@code RestorationChannel} and during {@code + * onSaveInstanceState(Bundle)} the current framework instance state obtained from {@code + * RestorationChannel} will be stored in the provided bundle. */ boolean shouldRestoreAndSaveState(); } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 3bfa341f78a2c..70eb38291feee 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -217,24 +217,24 @@ public FlutterEngine( } /** - * Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[], boolean)}, plus - * the ability to provide a custom {@code PlatformViewsController}. + * Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[], boolean)}, plus the + * ability to provide a custom {@code PlatformViewsController}. */ public FlutterEngine( - @NonNull Context context, - @NonNull FlutterLoader flutterLoader, - @NonNull FlutterJNI flutterJNI, - @NonNull PlatformViewsController platformViewsController, - @Nullable String[] dartVmArgs, - boolean automaticallyRegisterPlugins) { + @NonNull Context context, + @NonNull FlutterLoader flutterLoader, + @NonNull FlutterJNI flutterJNI, + @NonNull PlatformViewsController platformViewsController, + @Nullable String[] dartVmArgs, + boolean automaticallyRegisterPlugins) { this( - context, - flutterLoader, - flutterJNI, - platformViewsController, - dartVmArgs, - automaticallyRegisterPlugins, - false); + context, + flutterLoader, + flutterJNI, + platformViewsController, + dartVmArgs, + automaticallyRegisterPlugins, + false); } /** Fully configurable {@code FlutterEngine} constructor. */ diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java index 8766c59a37243..f66b9911fa2e9 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java @@ -21,7 +21,8 @@ public class RestorationChannel { private static final String TAG = "RestorationChannel"; - public RestorationChannel(@NonNull DartExecutor dartExecutor, @NonNull boolean waitForRestorationData) { + public RestorationChannel( + @NonNull DartExecutor dartExecutor, @NonNull boolean waitForRestorationData) { this.waitForRestorationData = waitForRestorationData; MethodChannel channel = new MethodChannel(dartExecutor, "flutter/restoration", StandardMethodCodec.INSTANCE); @@ -29,14 +30,14 @@ public RestorationChannel(@NonNull DartExecutor dartExecutor, @NonNull boolean w } /** - * Whether {@code setRestorationData} will be called to provide restoration data for - * the framework. + * Whether {@code setRestorationData} will be called to provide restoration data for the + * framework. * *

When this is set to true, the channel will delay answering any requests for restoration data - * by the framework until {@code setRestorationData} has been called. It must be set - * to false if the engine never calls {@code setRestorationData}. If it has been set - * to true, but it later turns out that there is no restoration data, - * {@code setRestorationData} must be called with null. + * by the framework until {@code setRestorationData} has been called. It must be set to false if + * the engine never calls {@code setRestorationData}. If it has been set to true, but it later + * turns out that there is no restoration data, {@code setRestorationData} must be called with + * null. */ public final boolean waitForRestorationData; @@ -45,9 +46,7 @@ public RestorationChannel(@NonNull DartExecutor dartExecutor, @NonNull boolean w private boolean engineHasProvidedData = false; private boolean frameworkHasRequestedData = false; - /** - * Whether {@code setRestorationData} has been called. - */ + /** Whether {@code setRestorationData} has been called. */ public boolean hasRestorationDataBeenSet() { return engineHasProvidedData; } @@ -60,7 +59,9 @@ public byte[] getRestorationData() { /** Set the restoration data that will be sent to the framework when the framework requests it. */ public void setRestorationData(byte[] data) { if (!waitForRestorationData || engineHasProvidedData) { - Log.e(TAG, "Channel has not been configured to wait for restoration data or data has already been provided."); + Log.e( + TAG, + "Channel has not been configured to wait for restoration data or data has already been provided."); return; } engineHasProvidedData = true; @@ -72,8 +73,8 @@ public void setRestorationData(byte[] data) { } } - - /** Clears the current restoration data. + /** + * Clears the current restoration data. * *

This should be called just prior to a hot restart. Otherwise, after the hot restart the * state prior to the hot restart will get restored. From 95563f6f04a6e40e4ff1bb7f07d32c07ac95a9fe Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Mon, 4 May 2020 18:14:26 -0700 Subject: [PATCH 06/16] review --- .../FlutterActivityAndFragmentDelegate.java | 17 +++++++++++------ .../systemchannels/RestorationChannel.java | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 277f14f3ee768..6582af7ecdc0f 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -64,8 +64,8 @@ */ /* package */ final class FlutterActivityAndFragmentDelegate { private static final String TAG = "FlutterActivityAndFragmentDelegate"; - private static final String FRAMEWORK_BUNDLE_KEY = "framework"; - private static final String PLUGINS_BUNDLE_KEY = "plugins"; + private static final String FRAMEWORK_RESTORATION_BUNDLE_KEY = "framework"; + private static final String PLUGINS_RESTORATION_BUNDLE_KEY = "plugins"; // The FlutterActivity or FlutterFragment that is delegating most of its calls // to this FlutterActivityAndFragmentDelegate. @@ -303,8 +303,8 @@ void onActivityCreated(@Nullable Bundle bundle) { Bundle pluginsBundle = null; byte[] frameworkBundle = null; if (bundle != null) { - pluginsBundle = bundle.getBundle(PLUGINS_BUNDLE_KEY); - frameworkBundle = bundle.getByteArray(FRAMEWORK_BUNDLE_KEY); + pluginsBundle = bundle.getBundle(PLUGINS_RESTORATION_BUNDLE_KEY); + frameworkBundle = bundle.getByteArray(FRAMEWORK_RESTORATION_BUNDLE_KEY); } final RestorationChannel restorationChannel = flutterEngine.getRestorationChannel(); @@ -465,13 +465,13 @@ void onSaveInstanceState(@Nullable Bundle bundle) { if (host.shouldRestoreAndSaveState()) { bundle.putByteArray( - FRAMEWORK_BUNDLE_KEY, flutterEngine.getRestorationChannel().getRestorationData()); + FRAMEWORK_RESTORATION_BUNDLE_KEY, flutterEngine.getRestorationChannel().getRestorationData()); } if (host.shouldAttachEngineToActivity()) { final Bundle plugins = new Bundle(); flutterEngine.getActivityControlSurface().onSaveInstanceState(plugins); - bundle.putBundle(PLUGINS_BUNDLE_KEY, plugins); + bundle.putBundle(PLUGINS_RESTORATION_BUNDLE_KEY, plugins); } } @@ -835,6 +835,11 @@ PlatformPlugin providePlatformPlugin( * will be forwarded to the framework via the {@code RestorationChannel} and during {@code * onSaveInstanceState(Bundle)} the current framework instance state obtained from {@code * RestorationChannel} will be stored in the provided bundle. + * + *

This may only be set to true if the engine in used has the + * {@code FlutterEngine.willProvideRestorationData} flag set to true. + * + *

This defaults to true, unless a cached engine is used. */ boolean shouldRestoreAndSaveState(); } diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java index f66b9911fa2e9..a7167c6055ab2 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java @@ -17,6 +17,18 @@ *

The engine can obtain the current restoration data from the framework via this channel to * store it on disk and - when the app is relaunched - provide the stored data back to the framework * to recreate the original state of the app. + * + *

The channel only accepts restoration data for the engine when {@code waitForRestorationData} + * is set to true. If it is not set, it will send null to the framework as initial restoration data. + * When it is set to true, it will wait until {@code setRestorationData(byte[])} has been called + * before responding to restoration data requests from the framework. In other words, if + * {@code waitForRestorationData} is true, {@code setRestorationData(byte[])} must be called + * (possibly with null as argument if no restoration data is available). + * + *

Restoration data for the framework can only be set once via + * {@code setRestorationData(byte[])}. The current restoration data provided by the framework can be + * read via {@code getRestorationData()}. + * */ public class RestorationChannel { private static final String TAG = "RestorationChannel"; @@ -38,6 +50,9 @@ public RestorationChannel( * the engine never calls {@code setRestorationData}. If it has been set to true, but it later * turns out that there is no restoration data, {@code setRestorationData} must be called with * null. + * + *

When constructing an engine set this to true if you want to provide restoration data to + * the framework by calling {@code setRestorationData(byte[])}. */ public final boolean waitForRestorationData; @@ -46,7 +61,7 @@ public RestorationChannel( private boolean engineHasProvidedData = false; private boolean frameworkHasRequestedData = false; - /** Whether {@code setRestorationData} has been called. */ + /** Whether {@code setRestorationData(byte[])} has been called. */ public boolean hasRestorationDataBeenSet() { return engineHasProvidedData; } From 91fe70f8e5658d207cfd96c81754213d602328b6 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 10 Jun 2020 10:22:58 -0700 Subject: [PATCH 07/16] refactor --- .../FlutterActivityAndFragmentDelegate.java | 12 ++-- .../embedding/engine/FlutterEngine.java | 7 ++ .../systemchannels/RestorationChannel.java | 64 +++++++++++-------- 3 files changed, 48 insertions(+), 35 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 6582af7ecdc0f..ec9f80650312e 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -25,7 +25,6 @@ import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; -import io.flutter.embedding.engine.systemchannels.RestorationChannel; import io.flutter.plugin.platform.PlatformPlugin; import java.util.Arrays; @@ -307,9 +306,8 @@ void onActivityCreated(@Nullable Bundle bundle) { frameworkBundle = bundle.getByteArray(FRAMEWORK_RESTORATION_BUNDLE_KEY); } - final RestorationChannel restorationChannel = flutterEngine.getRestorationChannel(); - if (host.shouldRestoreAndSaveState() && !restorationChannel.hasRestorationDataBeenSet()) { - restorationChannel.setRestorationData(frameworkBundle); + if (host.shouldRestoreAndSaveState()) { + flutterEngine.getRestorationChannel().setRestorationData(frameworkBundle); } if (host.shouldAttachEngineToActivity()) { @@ -465,7 +463,8 @@ void onSaveInstanceState(@Nullable Bundle bundle) { if (host.shouldRestoreAndSaveState()) { bundle.putByteArray( - FRAMEWORK_RESTORATION_BUNDLE_KEY, flutterEngine.getRestorationChannel().getRestorationData()); + FRAMEWORK_RESTORATION_BUNDLE_KEY, + flutterEngine.getRestorationChannel().getRestorationData()); } if (host.shouldAttachEngineToActivity()) { @@ -836,9 +835,6 @@ PlatformPlugin providePlatformPlugin( * onSaveInstanceState(Bundle)} the current framework instance state obtained from {@code * RestorationChannel} will be stored in the provided bundle. * - *

This may only be set to true if the engine in used has the - * {@code FlutterEngine.willProvideRestorationData} flag set to true. - * *

This defaults to true, unless a cached engine is used. */ boolean shouldRestoreAndSaveState(); diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 70eb38291feee..2290b13ba6f21 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -165,6 +165,13 @@ public FlutterEngine( /** * Same as {@link #FlutterEngine(Context, String[], boolean)} with added support for configuring * whether the engine will receive restoration data. + * + *

When the engine is configured to receive restoration data {@code + * RestorationChannel.setRestorationData(byte[] data)} must be called to provide the restoration + * data. All requests to get restoration data from the framework will be blocked until that method + * is called. If the engine is configured to wait for restoration data, but it turns out later + * that no restoration data has been provided by the operating system, that method must still be + * called with null as an argument to indicate "no data". */ public FlutterEngine( @NonNull Context context, diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java index a7167c6055ab2..e63ae0da095f0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java @@ -18,17 +18,15 @@ * store it on disk and - when the app is relaunched - provide the stored data back to the framework * to recreate the original state of the app. * - *

The channel only accepts restoration data for the engine when {@code waitForRestorationData} - * is set to true. If it is not set, it will send null to the framework as initial restoration data. - * When it is set to true, it will wait until {@code setRestorationData(byte[])} has been called - * before responding to restoration data requests from the framework. In other words, if - * {@code waitForRestorationData} is true, {@code setRestorationData(byte[])} must be called - * (possibly with null as argument if no restoration data is available). - * - *

Restoration data for the framework can only be set once via - * {@code setRestorationData(byte[])}. The current restoration data provided by the framework can be - * read via {@code getRestorationData()}. + *

The channel can be configured to delay responding to the framework's request for restoration + * data via {@code waitForRestorationData} until the engine-side has provided the data. This is + * useful for use cases where the engine is pre-warmed at a point in the application's life cycle + * where the operating system has not been made available to the engine yet. For example, if the + * engine is pre-warmed as part of the Application before an Activity is created, this flag should + * be set to true because Android will only provide the restoration data to the Activity. * + *

The current restoration data provided by the framework can be read via {@code + * getRestorationData()}. */ public class RestorationChannel { private static final String TAG = "RestorationChannel"; @@ -36,8 +34,7 @@ public class RestorationChannel { public RestorationChannel( @NonNull DartExecutor dartExecutor, @NonNull boolean waitForRestorationData) { this.waitForRestorationData = waitForRestorationData; - MethodChannel channel = - new MethodChannel(dartExecutor, "flutter/restoration", StandardMethodCodec.INSTANCE); + channel = new MethodChannel(dartExecutor, "flutter/restoration", StandardMethodCodec.INSTANCE); channel.setMethodCallHandler(handler); } @@ -50,39 +47,51 @@ public RestorationChannel( * the engine never calls {@code setRestorationData}. If it has been set to true, but it later * turns out that there is no restoration data, {@code setRestorationData} must be called with * null. - * - *

When constructing an engine set this to true if you want to provide restoration data to - * the framework by calling {@code setRestorationData(byte[])}. */ public final boolean waitForRestorationData; + private MethodChannel channel; private byte[] restorationData; private MethodChannel.Result pendingResult; private boolean engineHasProvidedData = false; private boolean frameworkHasRequestedData = false; - /** Whether {@code setRestorationData(byte[])} has been called. */ - public boolean hasRestorationDataBeenSet() { - return engineHasProvidedData; - } - /** Obtain the most current restoration data that the framework has provided. */ public byte[] getRestorationData() { return restorationData; } - /** Set the restoration data that will be sent to the framework when the framework requests it. */ + /** Set the restoration data from which the framework will restore its state. */ public void setRestorationData(byte[] data) { - if (!waitForRestorationData || engineHasProvidedData) { - Log.e( - TAG, - "Channel has not been configured to wait for restoration data or data has already been provided."); - return; - } engineHasProvidedData = true; if (pendingResult != null) { pendingResult.success(data); pendingResult = null; + } else if (frameworkHasRequestedData) { + channel.invokeMethod( + "push", + data, + new MethodChannel.Result() { + @Override + public void success(Object result) { + restorationData = data; + } + + @Override + public void error(String errorCode, String errorMessage, Object errorDetails) { + Log.e( + TAG, + "Error " + + errorCode + + " while sending restoration data to framework: " + + errorMessage); + } + + @Override + public void notImplemented() { + // Nothing to do. + } + }); } else { restorationData = data; } @@ -111,6 +120,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result.success(null); break; case "get": + frameworkHasRequestedData = true; if (engineHasProvidedData || !waitForRestorationData) { result.success(restorationData); } else { From 07ac9603c1e956ed456787d98495096df66e2b2c Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 10 Jun 2020 13:21:56 -0700 Subject: [PATCH 08/16] tests --- shell/platform/android/BUILD.gn | 1 + .../engine/systemchannels/RestorationChannel.java | 10 +++++++++- .../io/flutter/plugin/common/MethodChannel.java | 2 +- .../android/test/io/flutter/FlutterTestSuite.java | 2 ++ .../embedding/android/FlutterAndroidComponentTest.java | 5 +++++ testing/run_tests.py | 2 +- 6 files changed, 19 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 77f29f0d54c9c..a14e3809d22d8 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -444,6 +444,7 @@ action("robolectric_tests") { "test/io/flutter/embedding/engine/dart/DartExecutorTest.java", "test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java", "test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java", + "test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java", "test/io/flutter/external/FlutterLaunchTests.java", "test/io/flutter/plugin/common/StandardMessageCodecTest.java", "test/io/flutter/plugin/common/StandardMethodCodecTest.java", diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java index e63ae0da095f0..90b573840b2d9 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java @@ -33,8 +33,15 @@ public class RestorationChannel { public RestorationChannel( @NonNull DartExecutor dartExecutor, @NonNull boolean waitForRestorationData) { + this( + new MethodChannel(dartExecutor, "flutter/restoration", StandardMethodCodec.INSTANCE), + waitForRestorationData); + } + + RestorationChannel(MethodChannel channel, @NonNull boolean waitForRestorationData) { + this.channel = channel; this.waitForRestorationData = waitForRestorationData; - channel = new MethodChannel(dartExecutor, "flutter/restoration", StandardMethodCodec.INSTANCE); + channel.setMethodCallHandler(handler); } @@ -67,6 +74,7 @@ public void setRestorationData(byte[] data) { if (pendingResult != null) { pendingResult.success(data); pendingResult = null; + restorationData = data; } else if (frameworkHasRequestedData) { channel.invokeMethod( "push", diff --git a/shell/platform/android/io/flutter/plugin/common/MethodChannel.java b/shell/platform/android/io/flutter/plugin/common/MethodChannel.java index f8cbb2c71090b..41dbae9c9f8dd 100644 --- a/shell/platform/android/io/flutter/plugin/common/MethodChannel.java +++ b/shell/platform/android/io/flutter/plugin/common/MethodChannel.java @@ -26,7 +26,7 @@ *

The logical identity of the channel is given by its name. Identically named channels will * interfere with each other's communication. */ -public final class MethodChannel { +public class MethodChannel { private static final String TAG = "MethodChannel#"; private final BinaryMessenger messenger; diff --git a/shell/platform/android/test/io/flutter/FlutterTestSuite.java b/shell/platform/android/test/io/flutter/FlutterTestSuite.java index 7a101deb2f9bc..7df51ee9dba62 100644 --- a/shell/platform/android/test/io/flutter/FlutterTestSuite.java +++ b/shell/platform/android/test/io/flutter/FlutterTestSuite.java @@ -16,6 +16,7 @@ import io.flutter.embedding.engine.RenderingComponentTest; import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistryTest; import io.flutter.embedding.engine.renderer.FlutterRendererTest; +import io.flutter.embedding.engine.systemchannels.RestorationChannelTest; import io.flutter.external.FlutterLaunchTests; import io.flutter.plugin.common.StandardMessageCodecTest; import io.flutter.plugin.common.StandardMethodCodecTest; @@ -63,6 +64,7 @@ TextInputPluginTest.class, MouseCursorPluginTest.class, AccessibilityBridgeTest.class, + RestorationChannelTest.class, }) /** Runs all of the unit tests listed in the {@code @SuiteClasses} annotation. */ public class FlutterTestSuite {} diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java index 66d8f5dc0135b..9255cb4077b67 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java @@ -272,6 +272,11 @@ public boolean shouldAttachEngineToActivity() { return true; } + @Override + public boolean shouldRestoreAndSaveState() { + return true; + } + @Override public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) {} diff --git a/testing/run_tests.py b/testing/run_tests.py index 8ba97770a1719..46e51afa33a64 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -320,7 +320,7 @@ def EnsureIosTestsAreBuilt(ios_out_dir): def AssertExpectedJavaVersion(): """Checks that the user has Java 8 which is the supported Java version for Android""" - EXPECTED_VERSION = '1.8' + EXPECTED_VERSION = '11.0' # `java -version` is output to stderr. https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4380614 version_output = subprocess.check_output(['java', '-version'], stderr=subprocess.STDOUT) match = bool(re.compile('version "%s' % EXPECTED_VERSION).search(version_output)) From 9b56d6866614edf02277f8861f0752a83006a571 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 10 Jun 2020 13:23:27 -0700 Subject: [PATCH 09/16] revert unintentional change --- testing/run_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/run_tests.py b/testing/run_tests.py index 46e51afa33a64..8ba97770a1719 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -320,7 +320,7 @@ def EnsureIosTestsAreBuilt(ios_out_dir): def AssertExpectedJavaVersion(): """Checks that the user has Java 8 which is the supported Java version for Android""" - EXPECTED_VERSION = '11.0' + EXPECTED_VERSION = '1.8' # `java -version` is output to stderr. https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4380614 version_output = subprocess.check_output(['java', '-version'], stderr=subprocess.STDOUT) match = bool(re.compile('version "%s' % EXPECTED_VERSION).search(version_output)) From 09ec4451bf79264532c2a894cb88006572d9ede8 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 10 Jun 2020 13:26:47 -0700 Subject: [PATCH 10/16] tests --- .../RestorationChannelTest.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 shell/platform/android/test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java new file mode 100644 index 0000000000000..1543a6e7817cd --- /dev/null +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java @@ -0,0 +1,107 @@ +package io.flutter.embedding.engine.systemchannels; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.annotation.TargetApi; +import android.view.PointerIcon; +import io.flutter.embedding.android.FlutterView; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.systemchannels.RestorationChannel; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import java.util.HashMap; +import org.json.JSONException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import android.support.annotation.NonNull; +import io.flutter.Log; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.StandardMethodCodec; +import org.mockito.ArgumentCaptor; + +@Config( + manifest = Config.NONE, + shadows = {}) +@RunWith(RobolectricTestRunner.class) +@TargetApi(24) +public class RestorationChannelTest { + @Test + public void itDoesNotDoAnythingWhenRestorationDataIsSetBeforeFrameworkAsks() throws JSONException { + MethodChannel rawChannel = mock(MethodChannel.class); + RestorationChannel restorationChannel = new RestorationChannel(rawChannel, /*waitForRestorationData=*/ false); + restorationChannel.setRestorationData("Any String you want".getBytes()); + verify(rawChannel, times(0)).invokeMethod(any(), any()); + } + + @Test + public void itSendsDataOverWhenRequestIsPending() throws JSONException { + byte[] data = "Any String you want".getBytes(); + + MethodChannel rawChannel = mock(MethodChannel.class); + RestorationChannel restorationChannel = new RestorationChannel(rawChannel, /*waitForRestorationData=*/ true); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(MethodChannel.MethodCallHandler.class); + verify(rawChannel).setMethodCallHandler(argumentCaptor.capture()); + + MethodChannel.Result result = mock(MethodChannel.Result.class); + argumentCaptor.getValue().onMethodCall(new MethodCall("get", null), result); + verifyZeroInteractions(result); + + restorationChannel.setRestorationData(data); + verify(rawChannel, times(0)).invokeMethod(any(), any()); + verify(result).success(data); + + // Next get request is answered right away. + MethodChannel.Result result2 = mock(MethodChannel.Result.class); + argumentCaptor.getValue().onMethodCall(new MethodCall("get", null), result2); + verify(result2).success(data); + } + + @Test + public void itPushesNewData() throws JSONException { + byte[] data = "Any String you want".getBytes(); + + MethodChannel rawChannel = mock(MethodChannel.class); + RestorationChannel restorationChannel = new RestorationChannel(rawChannel, /*waitForRestorationData=*/ false); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(MethodChannel.MethodCallHandler.class); + verify(rawChannel).setMethodCallHandler(argumentCaptor.capture()); + + MethodChannel.Result result = mock(MethodChannel.Result.class); + argumentCaptor.getValue().onMethodCall(new MethodCall("get", null), result); + verify(result).success(null); + + restorationChannel.setRestorationData(data); + assertEquals(restorationChannel.getRestorationData(), null); + + ArgumentCaptor resultCapture = ArgumentCaptor.forClass(MethodChannel.Result.class); + verify(rawChannel).invokeMethod(eq("push"), eq(data), resultCapture.capture()); + resultCapture.getValue().success(null); + assertEquals(restorationChannel.getRestorationData(), data); + } + + @Test + public void itHoldsOnToDataFromFramework() throws JSONException { + byte[] data = "Any String you want".getBytes(); + + MethodChannel rawChannel = mock(MethodChannel.class); + RestorationChannel restorationChannel = new RestorationChannel(rawChannel, /*waitForRestorationData=*/ false); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(MethodChannel.MethodCallHandler.class); + verify(rawChannel).setMethodCallHandler(argumentCaptor.capture()); + + MethodChannel.Result result = mock(MethodChannel.Result.class); + argumentCaptor.getValue().onMethodCall(new MethodCall("put", data), result); + assertEquals(restorationChannel.getRestorationData(), data); + } +} + From 98dd9186888b3be78b1008b4151bde5dcb170fb6 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Wed, 10 Jun 2020 13:27:55 -0700 Subject: [PATCH 11/16] format --- .../RestorationChannelTest.java | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java index 1543a6e7817cd..c4cffca60122b 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java @@ -4,32 +4,19 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import android.annotation.TargetApi; -import android.view.PointerIcon; -import io.flutter.embedding.android.FlutterView; -import io.flutter.embedding.engine.dart.DartExecutor; -import io.flutter.embedding.engine.systemchannels.RestorationChannel; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; -import java.util.HashMap; import org.json.JSONException; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import android.support.annotation.NonNull; -import io.flutter.Log; -import io.flutter.embedding.engine.dart.DartExecutor; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.StandardMethodCodec; -import org.mockito.ArgumentCaptor; @Config( manifest = Config.NONE, @@ -38,9 +25,11 @@ @TargetApi(24) public class RestorationChannelTest { @Test - public void itDoesNotDoAnythingWhenRestorationDataIsSetBeforeFrameworkAsks() throws JSONException { + public void itDoesNotDoAnythingWhenRestorationDataIsSetBeforeFrameworkAsks() + throws JSONException { MethodChannel rawChannel = mock(MethodChannel.class); - RestorationChannel restorationChannel = new RestorationChannel(rawChannel, /*waitForRestorationData=*/ false); + RestorationChannel restorationChannel = + new RestorationChannel(rawChannel, /*waitForRestorationData=*/ false); restorationChannel.setRestorationData("Any String you want".getBytes()); verify(rawChannel, times(0)).invokeMethod(any(), any()); } @@ -50,8 +39,10 @@ public void itSendsDataOverWhenRequestIsPending() throws JSONException { byte[] data = "Any String you want".getBytes(); MethodChannel rawChannel = mock(MethodChannel.class); - RestorationChannel restorationChannel = new RestorationChannel(rawChannel, /*waitForRestorationData=*/ true); - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(MethodChannel.MethodCallHandler.class); + RestorationChannel restorationChannel = + new RestorationChannel(rawChannel, /*waitForRestorationData=*/ true); + ArgumentCaptor argumentCaptor = + ArgumentCaptor.forClass(MethodChannel.MethodCallHandler.class); verify(rawChannel).setMethodCallHandler(argumentCaptor.capture()); MethodChannel.Result result = mock(MethodChannel.Result.class); @@ -73,8 +64,10 @@ public void itPushesNewData() throws JSONException { byte[] data = "Any String you want".getBytes(); MethodChannel rawChannel = mock(MethodChannel.class); - RestorationChannel restorationChannel = new RestorationChannel(rawChannel, /*waitForRestorationData=*/ false); - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(MethodChannel.MethodCallHandler.class); + RestorationChannel restorationChannel = + new RestorationChannel(rawChannel, /*waitForRestorationData=*/ false); + ArgumentCaptor argumentCaptor = + ArgumentCaptor.forClass(MethodChannel.MethodCallHandler.class); verify(rawChannel).setMethodCallHandler(argumentCaptor.capture()); MethodChannel.Result result = mock(MethodChannel.Result.class); @@ -84,7 +77,8 @@ public void itPushesNewData() throws JSONException { restorationChannel.setRestorationData(data); assertEquals(restorationChannel.getRestorationData(), null); - ArgumentCaptor resultCapture = ArgumentCaptor.forClass(MethodChannel.Result.class); + ArgumentCaptor resultCapture = + ArgumentCaptor.forClass(MethodChannel.Result.class); verify(rawChannel).invokeMethod(eq("push"), eq(data), resultCapture.capture()); resultCapture.getValue().success(null); assertEquals(restorationChannel.getRestorationData(), data); @@ -95,8 +89,10 @@ public void itHoldsOnToDataFromFramework() throws JSONException { byte[] data = "Any String you want".getBytes(); MethodChannel rawChannel = mock(MethodChannel.class); - RestorationChannel restorationChannel = new RestorationChannel(rawChannel, /*waitForRestorationData=*/ false); - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(MethodChannel.MethodCallHandler.class); + RestorationChannel restorationChannel = + new RestorationChannel(rawChannel, /*waitForRestorationData=*/ false); + ArgumentCaptor argumentCaptor = + ArgumentCaptor.forClass(MethodChannel.MethodCallHandler.class); verify(rawChannel).setMethodCallHandler(argumentCaptor.capture()); MethodChannel.Result result = mock(MethodChannel.Result.class); @@ -104,4 +100,3 @@ public void itHoldsOnToDataFromFramework() throws JSONException { assertEquals(restorationChannel.getRestorationData(), data); } } - From d2c407494c92e0da7758dfab9ec4fd80d51568c6 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Fri, 12 Jun 2020 10:47:10 -0700 Subject: [PATCH 12/16] comments --- .../embedding/android/FlutterActivity.java | 2 + .../FlutterActivityAndFragmentDelegate.java | 12 ++--- .../embedding/engine/FlutterEngine.java | 27 ++++++----- .../systemchannels/RestorationChannel.java | 45 +++++++++++-------- 4 files changed, 52 insertions(+), 34 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 348c7c4d7e539..8ec7030c84fe8 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -64,6 +64,7 @@ *

  • Chooses Flutter's initial route. *
  • Renders {@code Activity} transparently, if desired. *
  • Offers hooks for subclasses to provide and configure a {@link FlutterEngine}. +
  • Save and restore instance state, see {@code shouldRestoreAndSaveState()}; * * *

    Dart entrypoint, initial route, and app bundle path @@ -956,6 +957,7 @@ public boolean shouldRestoreAndSaveState() { return getIntent().getBooleanExtra(EXTRA_ENABLE_STATE_RESTORATION, false); } if (getCachedEngineId() != null) { + // Prevent overwriting the existing state in a cached engine with restoration state. return false; } return true; diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index ec9f80650312e..358ab6c504805 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -299,19 +299,19 @@ void onActivityCreated(@Nullable Bundle bundle) { Log.v(TAG, "onActivityCreated. Giving framework and plugins an opportunity to restore state."); ensureAlive(); - Bundle pluginsBundle = null; - byte[] frameworkBundle = null; + Bundle pluginState = null; + byte[] frameworkState = null; if (bundle != null) { - pluginsBundle = bundle.getBundle(PLUGINS_RESTORATION_BUNDLE_KEY); - frameworkBundle = bundle.getByteArray(FRAMEWORK_RESTORATION_BUNDLE_KEY); + pluginState = bundle.getBundle(PLUGINS_RESTORATION_BUNDLE_KEY); + frameworkState = bundle.getByteArray(FRAMEWORK_RESTORATION_BUNDLE_KEY); } if (host.shouldRestoreAndSaveState()) { - flutterEngine.getRestorationChannel().setRestorationData(frameworkBundle); + flutterEngine.getRestorationChannel().setRestorationData(frameworkState); } if (host.shouldAttachEngineToActivity()) { - flutterEngine.getActivityControlSurface().onRestoreInstanceState(pluginsBundle); + flutterEngine.getActivityControlSurface().onRestoreInstanceState(pluginState); } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 2290b13ba6f21..765774841105e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -166,18 +166,25 @@ public FlutterEngine( * Same as {@link #FlutterEngine(Context, String[], boolean)} with added support for configuring * whether the engine will receive restoration data. * - *

    When the engine is configured to receive restoration data {@code - * RestorationChannel.setRestorationData(byte[] data)} must be called to provide the restoration - * data. All requests to get restoration data from the framework will be blocked until that method - * is called. If the engine is configured to wait for restoration data, but it turns out later - * that no restoration data has been provided by the operating system, that method must still be - * called with null as an argument to indicate "no data". + *

    The {@code waitForRestorationData} flag controls whether the engine delays responding to + * requests from the framework for restoration data until that data has been provided to the + * engine via {@code RestorationChannel.setRestorationData(byte[] data)}. If the flag is false, + * the framework may temporarily initialize itself to default values before the restoration data + * has been made available to the engine. Setting {@code waitForRestorationData} to true avoids + * this extra work by delaying initialization until the data is available. + * + *

    When {@code waitForRestorationData} is set, {@code + * RestorationChannel.setRestorationData(byte[] data)} must be called at a later point in time. + * If it later turns out that no restoration data is available to restore the framework from, + * that method must still be called with null as an argument to indicate "no data". + * + *

    If the framework never requests the restoration data, this flag has no effect. */ public FlutterEngine( @NonNull Context context, @Nullable String[] dartVmArgs, boolean automaticallyRegisterPlugins, - boolean willProvideRestorationData) { + boolean waitForRestorationData) { this( context, FlutterLoader.getInstance(), @@ -185,7 +192,7 @@ public FlutterEngine( new PlatformViewsController(), dartVmArgs, automaticallyRegisterPlugins, - willProvideRestorationData); + waitForRestorationData); } /** @@ -252,7 +259,7 @@ public FlutterEngine( @NonNull PlatformViewsController platformViewsController, @Nullable String[] dartVmArgs, boolean automaticallyRegisterPlugins, - boolean willProvideRestorationData) { + boolean waitForRestorationData) { this.flutterJNI = flutterJNI; flutterLoader.startInitialization(context.getApplicationContext()); flutterLoader.ensureInitializationComplete(context, dartVmArgs); @@ -275,7 +282,7 @@ public FlutterEngine( mouseCursorChannel = new MouseCursorChannel(dartExecutor); navigationChannel = new NavigationChannel(dartExecutor); platformChannel = new PlatformChannel(dartExecutor); - restorationChannel = new RestorationChannel(dartExecutor, willProvideRestorationData); + restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData); settingsChannel = new SettingsChannel(dartExecutor); systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java index 90b573840b2d9..36e2ff69b2f02 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java @@ -20,10 +20,10 @@ * *

    The channel can be configured to delay responding to the framework's request for restoration * data via {@code waitForRestorationData} until the engine-side has provided the data. This is - * useful for use cases where the engine is pre-warmed at a point in the application's life cycle - * where the operating system has not been made available to the engine yet. For example, if the - * engine is pre-warmed as part of the Application before an Activity is created, this flag should - * be set to true because Android will only provide the restoration data to the Activity. + * useful when the engine is pre-warmed at a point in the application's life cycle where the + * restoration data is not available yet. For example, if the engine is pre-warmed as part of the + * Application before an Activity is created, this flag should be set to true because Android will + * only provide the restoration data to the Activity during the onCreate callback. * *

    The current restoration data provided by the framework can be read via {@code * getRestorationData()}. @@ -46,20 +46,26 @@ public RestorationChannel( } /** - * Whether {@code setRestorationData} will be called to provide restoration data for the - * framework. + * Whether the channel delays responding to the framework's initial request for restoration data + * until {@code setRestorationData} has been called. * - *

    When this is set to true, the channel will delay answering any requests for restoration data - * by the framework until {@code setRestorationData} has been called. It must be set to false if - * the engine never calls {@code setRestorationData}. If it has been set to true, but it later - * turns out that there is no restoration data, {@code setRestorationData} must be called with - * null. + *

    If the engine never calls {@code setRestorationData} this flag must be set to false. + * If set to true, the engine must call {@code setRestorationData} either with the actual + * restoration data as argument or null if it turns out that there is no restoration data. + * + *

    If the response to the framework's request for restoration data is not delayed until the + * data has been set via {@code setRestorationData}, the framework may intermittently initialize + * itself to default values until the restoration data has been made available. Setting this flag + * to true avoids that extra work. */ public final boolean waitForRestorationData; - private MethodChannel channel; + // Holds the the most current restoration data which may have been provided by the engine + // via "setRestorationData" or by the framework via the method channel. This is the data the + // framework should be restored to in case the app is terminated. private byte[] restorationData; - private MethodChannel.Result pendingResult; + private MethodChannel channel; + private MethodChannel.Result pendingFrameworkRequest; private boolean engineHasProvidedData = false; private boolean frameworkHasRequestedData = false; @@ -71,11 +77,14 @@ public byte[] getRestorationData() { /** Set the restoration data from which the framework will restore its state. */ public void setRestorationData(byte[] data) { engineHasProvidedData = true; - if (pendingResult != null) { - pendingResult.success(data); - pendingResult = null; + if (pendingFrameworkRequest != null) { + // If their is a pending request from the framework, answer it. + pendingFrameworkRequest.success(data); + pendingFrameworkRequest = null; restorationData = data; } else if (frameworkHasRequestedData) { + // If the framework has previously received the engine's restoration data, push the new data + // directly to it. channel.invokeMethod( "push", data, @@ -101,6 +110,7 @@ public void notImplemented() { } }); } else { + // Otherwise, just cache the data until the framework asks for it. restorationData = data; } } @@ -121,7 +131,6 @@ public void clearData() { public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { final String method = call.method; final Object args = call.arguments; - Log.v(TAG, "Received '" + method + "' message."); switch (method) { case "put": restorationData = (byte[]) args; @@ -132,7 +141,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result if (engineHasProvidedData || !waitForRestorationData) { result.success(restorationData); } else { - pendingResult = result; + pendingFrameworkRequest = result; } break; default: From 43b10e097a715d0fa2b80b7002d96c3225c1c73c Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Fri, 12 Jun 2020 12:01:29 -0700 Subject: [PATCH 13/16] format --- .../io/flutter/embedding/android/FlutterActivity.java | 2 +- .../android/io/flutter/embedding/engine/FlutterEngine.java | 6 +++--- .../embedding/engine/systemchannels/RestorationChannel.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 8ec7030c84fe8..a69d681749178 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -64,7 +64,7 @@ *

  • Chooses Flutter's initial route. *
  • Renders {@code Activity} transparently, if desired. *
  • Offers hooks for subclasses to provide and configure a {@link FlutterEngine}. -
  • Save and restore instance state, see {@code shouldRestoreAndSaveState()}; + *
  • Save and restore instance state, see {@code shouldRestoreAndSaveState()}; * * *

    Dart entrypoint, initial route, and app bundle path diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 765774841105e..053e9b92a1eb9 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -174,9 +174,9 @@ public FlutterEngine( * this extra work by delaying initialization until the data is available. * *

    When {@code waitForRestorationData} is set, {@code - * RestorationChannel.setRestorationData(byte[] data)} must be called at a later point in time. - * If it later turns out that no restoration data is available to restore the framework from, - * that method must still be called with null as an argument to indicate "no data". + * RestorationChannel.setRestorationData(byte[] data)} must be called at a later point in time. If + * it later turns out that no restoration data is available to restore the framework from, that + * method must still be called with null as an argument to indicate "no data". * *

    If the framework never requests the restoration data, this flag has no effect. */ diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java index 36e2ff69b2f02..a3e2f0c97cd46 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java @@ -49,9 +49,9 @@ public RestorationChannel( * Whether the channel delays responding to the framework's initial request for restoration data * until {@code setRestorationData} has been called. * - *

    If the engine never calls {@code setRestorationData} this flag must be set to false. - * If set to true, the engine must call {@code setRestorationData} either with the actual - * restoration data as argument or null if it turns out that there is no restoration data. + *

    If the engine never calls {@code setRestorationData} this flag must be set to false. If set + * to true, the engine must call {@code setRestorationData} either with the actual restoration + * data as argument or null if it turns out that there is no restoration data. * *

    If the response to the framework's request for restoration data is not delayed until the * data has been set via {@code setRestorationData}, the framework may intermittently initialize From 463b7e677df8e6d15ce68650c652da8ab3ebe33d Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Fri, 12 Jun 2020 12:59:30 -0700 Subject: [PATCH 14/16] review comments --- .../engine/systemchannels/RestorationChannel.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java index a3e2f0c97cd46..7897bf2b69f9e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java @@ -65,7 +65,7 @@ public RestorationChannel( // framework should be restored to in case the app is terminated. private byte[] restorationData; private MethodChannel channel; - private MethodChannel.Result pendingFrameworkRequest; + private MethodChannel.Result pendingFrameworkRestorationChannelRequest; private boolean engineHasProvidedData = false; private boolean frameworkHasRequestedData = false; @@ -77,14 +77,17 @@ public byte[] getRestorationData() { /** Set the restoration data from which the framework will restore its state. */ public void setRestorationData(byte[] data) { engineHasProvidedData = true; - if (pendingFrameworkRequest != null) { + if (pendingFrameworkRestorationChannelRequest != null) { // If their is a pending request from the framework, answer it. - pendingFrameworkRequest.success(data); - pendingFrameworkRequest = null; + pendingFrameworkRestorationChannelRequest.success(data); + pendingFrameworkRestorationChannelRequest = null; restorationData = data; } else if (frameworkHasRequestedData) { // If the framework has previously received the engine's restoration data, push the new data - // directly to it. + // directly to it. This case can happen when "waitForRestorationData" is false and the + // framework retrieved the restoration state before it was set via this method. + // Experimentally, this can also be used to restore a previously used engine to another state, + // e.g. when the engine is attached to a new activity. channel.invokeMethod( "push", data, @@ -141,7 +144,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result if (engineHasProvidedData || !waitForRestorationData) { result.success(restorationData); } else { - pendingFrameworkRequest = result; + pendingFrameworkRestorationChannelRequest = result; } break; default: From 3971edb365ca120e0e58e56ca585267c1ba5444b Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Fri, 12 Jun 2020 13:26:40 -0700 Subject: [PATCH 15/16] more comments --- .../embedding/engine/systemchannels/RestorationChannel.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java index 7897bf2b69f9e..5e4a57ef41b97 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java @@ -143,6 +143,10 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result frameworkHasRequestedData = true; if (engineHasProvidedData || !waitForRestorationData) { result.success(restorationData); + // Do not delete the restoration data on the engine side after sending it to the + // framework. We may need to hand this data back to the operating system if the + // framework never modifies the data (and thus doesn't send us any + // data back). } else { pendingFrameworkRestorationChannelRequest = result; } From 3fca33b3b9f8f0669e949a31989559931433ecda Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Fri, 12 Jun 2020 13:44:47 -0700 Subject: [PATCH 16/16] format --- .../android/io/flutter/embedding/android/FlutterActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index a69d681749178..acc208a8dc7a3 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -64,7 +64,7 @@ *

  • Chooses Flutter's initial route. *
  • Renders {@code Activity} transparently, if desired. *
  • Offers hooks for subclasses to provide and configure a {@link FlutterEngine}. - *
  • Save and restore instance state, see {@code shouldRestoreAndSaveState()}; + *
  • Save and restore instance state, see {@code #shouldRestoreAndSaveState()}; * * *

    Dart entrypoint, initial route, and app bundle path