Skip to content

Commit f7d241f

Browse files
authored
Wire up channel for restoration data (flutter#18042)
1 parent 983b6e1 commit f7d241f

File tree

12 files changed

+415
-7
lines changed

12 files changed

+415
-7
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/system
736736
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/NavigationChannel.java
737737
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java
738738
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java
739+
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java
739740
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java
740741
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SystemChannel.java
741742
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java

shell/platform/android/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ android_java_sources = [
184184
"io/flutter/embedding/engine/systemchannels/NavigationChannel.java",
185185
"io/flutter/embedding/engine/systemchannels/PlatformChannel.java",
186186
"io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java",
187+
"io/flutter/embedding/engine/systemchannels/RestorationChannel.java",
187188
"io/flutter/embedding/engine/systemchannels/SettingsChannel.java",
188189
"io/flutter/embedding/engine/systemchannels/SystemChannel.java",
189190
"io/flutter/embedding/engine/systemchannels/TextInputChannel.java",
@@ -443,6 +444,7 @@ action("robolectric_tests") {
443444
"test/io/flutter/embedding/engine/dart/DartExecutorTest.java",
444445
"test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java",
445446
"test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java",
447+
"test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java",
446448
"test/io/flutter/external/FlutterLaunchTests.java",
447449
"test/io/flutter/plugin/common/StandardMessageCodecTest.java",
448450
"test/io/flutter/plugin/common/StandardMethodCodecTest.java",

shell/platform/android/io/flutter/embedding/android/FlutterActivity.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_BACKGROUND_MODE;
1212
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID;
1313
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY;
14+
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_ENABLE_STATE_RESTORATION;
1415
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_INITIAL_ROUTE;
1516
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY;
1617
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY;
@@ -63,6 +64,7 @@
6364
* <li>Chooses Flutter's initial route.
6465
* <li>Renders {@code Activity} transparently, if desired.
6566
* <li>Offers hooks for subclasses to provide and configure a {@link FlutterEngine}.
67+
* <li>Save and restore instance state, see {@code #shouldRestoreAndSaveState()};
6668
* </ul>
6769
*
6870
* <p><strong>Dart entrypoint, initial route, and app bundle path</strong>
@@ -949,6 +951,18 @@ public void onFlutterUiNoLongerDisplayed() {
949951
// no-op
950952
}
951953

954+
@Override
955+
public boolean shouldRestoreAndSaveState() {
956+
if (getIntent().hasExtra(EXTRA_ENABLE_STATE_RESTORATION)) {
957+
return getIntent().getBooleanExtra(EXTRA_ENABLE_STATE_RESTORATION, false);
958+
}
959+
if (getCachedEngineId() != null) {
960+
// Prevent overwriting the existing state in a cached engine with restoration state.
961+
return false;
962+
}
963+
return true;
964+
}
965+
952966
/**
953967
* Registers all plugins that an app lists in its pubspec.yaml.
954968
*

shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
*/
6464
/* package */ final class FlutterActivityAndFragmentDelegate {
6565
private static final String TAG = "FlutterActivityAndFragmentDelegate";
66+
private static final String FRAMEWORK_RESTORATION_BUNDLE_KEY = "framework";
67+
private static final String PLUGINS_RESTORATION_BUNDLE_KEY = "plugins";
6668

6769
// The FlutterActivity or FlutterFragment that is delegating most of its calls
6870
// to this FlutterActivityAndFragmentDelegate.
@@ -227,7 +229,8 @@ void onAttach(@NonNull Context context) {
227229
new FlutterEngine(
228230
host.getContext(),
229231
host.getFlutterShellArgs().toArray(),
230-
/*automaticallyRegisterPlugins=*/ false);
232+
/*automaticallyRegisterPlugins=*/ false,
233+
/*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
231234
isFlutterEngineFromHost = false;
232235
}
233236

@@ -293,11 +296,22 @@ View onCreateView(
293296
}
294297

295298
void onActivityCreated(@Nullable Bundle bundle) {
296-
Log.v(TAG, "onActivityCreated. Giving plugins an opportunity to restore state.");
299+
Log.v(TAG, "onActivityCreated. Giving framework and plugins an opportunity to restore state.");
297300
ensureAlive();
298301

302+
Bundle pluginState = null;
303+
byte[] frameworkState = null;
304+
if (bundle != null) {
305+
pluginState = bundle.getBundle(PLUGINS_RESTORATION_BUNDLE_KEY);
306+
frameworkState = bundle.getByteArray(FRAMEWORK_RESTORATION_BUNDLE_KEY);
307+
}
308+
309+
if (host.shouldRestoreAndSaveState()) {
310+
flutterEngine.getRestorationChannel().setRestorationData(frameworkState);
311+
}
312+
299313
if (host.shouldAttachEngineToActivity()) {
300-
flutterEngine.getActivityControlSurface().onRestoreInstanceState(bundle);
314+
flutterEngine.getActivityControlSurface().onRestoreInstanceState(pluginState);
301315
}
302316
}
303317

@@ -444,11 +458,19 @@ void onDestroyView() {
444458
}
445459

446460
void onSaveInstanceState(@Nullable Bundle bundle) {
447-
Log.v(TAG, "onSaveInstanceState. Giving plugins an opportunity to save state.");
461+
Log.v(TAG, "onSaveInstanceState. Giving framework and plugins an opportunity to save state.");
448462
ensureAlive();
449463

464+
if (host.shouldRestoreAndSaveState()) {
465+
bundle.putByteArray(
466+
FRAMEWORK_RESTORATION_BUNDLE_KEY,
467+
flutterEngine.getRestorationChannel().getRestorationData());
468+
}
469+
450470
if (host.shouldAttachEngineToActivity()) {
451-
flutterEngine.getActivityControlSurface().onSaveInstanceState(bundle);
471+
final Bundle plugins = new Bundle();
472+
flutterEngine.getActivityControlSurface().onSaveInstanceState(plugins);
473+
bundle.putBundle(PLUGINS_RESTORATION_BUNDLE_KEY, plugins);
452474
}
453475
}
454476

@@ -808,5 +830,17 @@ PlatformPlugin providePlatformPlugin(
808830

809831
/** Invoked by this delegate when its {@link FlutterView} stops painting pixels. */
810832
void onFlutterUiNoLongerDisplayed();
833+
834+
/**
835+
* Whether state restoration is enabled.
836+
*
837+
* <p>When this returns true, the instance state provided to {@code onActivityCreated(Bundle)}
838+
* will be forwarded to the framework via the {@code RestorationChannel} and during {@code
839+
* onSaveInstanceState(Bundle)} the current framework instance state obtained from {@code
840+
* RestorationChannel} will be stored in the provided bundle.
841+
*
842+
* <p>This defaults to true, unless a cached engine is used.
843+
*/
844+
boolean shouldRestoreAndSaveState();
811845
}
812846
}

shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class FlutterActivityLaunchConfigs {
2323
/* package */ static final String EXTRA_CACHED_ENGINE_ID = "cached_engine_id";
2424
/* package */ static final String EXTRA_DESTROY_ENGINE_WITH_ACTIVITY =
2525
"destroy_engine_with_activity";
26+
/* package */ static final String EXTRA_ENABLE_STATE_RESTORATION = "enable_state_restoration";
2627

2728
// Default configuration.
2829
/* package */ static final String DEFAULT_DART_ENTRYPOINT = "main";

shell/platform/android/io/flutter/embedding/android/FlutterFragment.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
113113
* outlive the {@code FlutterFragment}.
114114
*/
115115
protected static final String ARG_DESTROY_ENGINE_WITH_FRAGMENT = "destroy_engine_with_fragment";
116+
/**
117+
* True if the framework state in the engine attached to this engine should be stored and restored
118+
* when this fragment is created and destroyed.
119+
*/
120+
protected static final String ARG_ENABLE_STATE_RESTORATION = "enable_state_restoration";
116121

117122
/**
118123
* Creates a {@code FlutterFragment} with a default configuration.
@@ -1018,6 +1023,17 @@ public void onFlutterUiNoLongerDisplayed() {
10181023
}
10191024
}
10201025

1026+
@Override
1027+
public boolean shouldRestoreAndSaveState() {
1028+
if (getArguments().containsKey(ARG_ENABLE_STATE_RESTORATION)) {
1029+
return getArguments().getBoolean(ARG_ENABLE_STATE_RESTORATION);
1030+
}
1031+
if (getCachedEngineId() != null) {
1032+
return false;
1033+
}
1034+
return true;
1035+
}
1036+
10211037
/**
10221038
* Annotates methods in {@code FlutterFragment} that must be called by the containing {@code
10231039
* Activity}.

shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
2525
import io.flutter.embedding.engine.systemchannels.NavigationChannel;
2626
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
27+
import io.flutter.embedding.engine.systemchannels.RestorationChannel;
2728
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
2829
import io.flutter.embedding.engine.systemchannels.SystemChannel;
2930
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
@@ -80,6 +81,7 @@ public class FlutterEngine {
8081
@NonNull private final LocalizationChannel localizationChannel;
8182
@NonNull private final MouseCursorChannel mouseCursorChannel;
8283
@NonNull private final NavigationChannel navigationChannel;
84+
@NonNull private final RestorationChannel restorationChannel;
8385
@NonNull private final PlatformChannel platformChannel;
8486
@NonNull private final SettingsChannel settingsChannel;
8587
@NonNull private final SystemChannel systemChannel;
@@ -102,6 +104,7 @@ public void onPreEngineRestart() {
102104
}
103105

104106
platformViewsController.onPreEngineRestart();
107+
restorationChannel.clearData();
105108
}
106109
};
107110

@@ -159,6 +162,39 @@ public FlutterEngine(
159162
automaticallyRegisterPlugins);
160163
}
161164

165+
/**
166+
* Same as {@link #FlutterEngine(Context, String[], boolean)} with added support for configuring
167+
* whether the engine will receive restoration data.
168+
*
169+
* <p>The {@code waitForRestorationData} flag controls whether the engine delays responding to
170+
* requests from the framework for restoration data until that data has been provided to the
171+
* engine via {@code RestorationChannel.setRestorationData(byte[] data)}. If the flag is false,
172+
* the framework may temporarily initialize itself to default values before the restoration data
173+
* has been made available to the engine. Setting {@code waitForRestorationData} to true avoids
174+
* this extra work by delaying initialization until the data is available.
175+
*
176+
* <p>When {@code waitForRestorationData} is set, {@code
177+
* RestorationChannel.setRestorationData(byte[] data)} must be called at a later point in time. If
178+
* it later turns out that no restoration data is available to restore the framework from, that
179+
* method must still be called with null as an argument to indicate "no data".
180+
*
181+
* <p>If the framework never requests the restoration data, this flag has no effect.
182+
*/
183+
public FlutterEngine(
184+
@NonNull Context context,
185+
@Nullable String[] dartVmArgs,
186+
boolean automaticallyRegisterPlugins,
187+
boolean waitForRestorationData) {
188+
this(
189+
context,
190+
FlutterLoader.getInstance(),
191+
new FlutterJNI(),
192+
new PlatformViewsController(),
193+
dartVmArgs,
194+
automaticallyRegisterPlugins,
195+
waitForRestorationData);
196+
}
197+
162198
/**
163199
* Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[])} but with no Dart
164200
* VM flags.
@@ -194,14 +230,36 @@ public FlutterEngine(
194230
automaticallyRegisterPlugins);
195231
}
196232

197-
/** Fully configurable {@code FlutterEngine} constructor. */
233+
/**
234+
* Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[], boolean)}, plus the
235+
* ability to provide a custom {@code PlatformViewsController}.
236+
*/
198237
public FlutterEngine(
199238
@NonNull Context context,
200239
@NonNull FlutterLoader flutterLoader,
201240
@NonNull FlutterJNI flutterJNI,
202241
@NonNull PlatformViewsController platformViewsController,
203242
@Nullable String[] dartVmArgs,
204243
boolean automaticallyRegisterPlugins) {
244+
this(
245+
context,
246+
flutterLoader,
247+
flutterJNI,
248+
platformViewsController,
249+
dartVmArgs,
250+
automaticallyRegisterPlugins,
251+
false);
252+
}
253+
254+
/** Fully configurable {@code FlutterEngine} constructor. */
255+
public FlutterEngine(
256+
@NonNull Context context,
257+
@NonNull FlutterLoader flutterLoader,
258+
@NonNull FlutterJNI flutterJNI,
259+
@NonNull PlatformViewsController platformViewsController,
260+
@Nullable String[] dartVmArgs,
261+
boolean automaticallyRegisterPlugins,
262+
boolean waitForRestorationData) {
205263
this.flutterJNI = flutterJNI;
206264
flutterLoader.startInitialization(context.getApplicationContext());
207265
flutterLoader.ensureInitializationComplete(context, dartVmArgs);
@@ -224,6 +282,7 @@ public FlutterEngine(
224282
mouseCursorChannel = new MouseCursorChannel(dartExecutor);
225283
navigationChannel = new NavigationChannel(dartExecutor);
226284
platformChannel = new PlatformChannel(dartExecutor);
285+
restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData);
227286
settingsChannel = new SettingsChannel(dartExecutor);
228287
systemChannel = new SystemChannel(dartExecutor);
229288
textInputChannel = new TextInputChannel(dartExecutor);
@@ -380,6 +439,18 @@ public PlatformChannel getPlatformChannel() {
380439
return platformChannel;
381440
}
382441

442+
/**
443+
* System channel to exchange restoration data between framework and engine.
444+
*
445+
* <p>The engine can obtain the current restoration data from the framework via this channel to
446+
* store it on disk and - when the app is relaunched - provide the stored data back to the
447+
* framework to recreate the original state of the app.
448+
*/
449+
@NonNull
450+
public RestorationChannel getRestorationChannel() {
451+
return restorationChannel;
452+
}
453+
383454
/**
384455
* System channel that sends platform/user settings from Android to Flutter, e.g., time format,
385456
* scale factor, etc.

0 commit comments

Comments
 (0)