Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -443,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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -63,6 +64,7 @@
* <li>Chooses Flutter's initial route.
* <li>Renders {@code Activity} transparently, if desired.
* <li>Offers hooks for subclasses to provide and configure a {@link FlutterEngine}.
* <li>Save and restore instance state, see {@code #shouldRestoreAndSaveState()};
* </ul>
*
* <p><strong>Dart entrypoint, initial route, and app bundle path</strong>
Expand Down Expand Up @@ -949,6 +951,18 @@ public void onFlutterUiNoLongerDisplayed() {
// no-op
}

@Override
public boolean shouldRestoreAndSaveState() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document this in a doc section in the class doc

if (getIntent().hasExtra(EXTRA_ENABLE_STATE_RESTORATION)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sprinkle some comments here for while some values were chosen

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;
}

/**
* Registers all plugins that an app lists in its pubspec.yaml.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
*/
/* package */ final class FlutterActivityAndFragmentDelegate {
private static final String TAG = "FlutterActivityAndFragmentDelegate";
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.
Expand Down Expand Up @@ -227,7 +229,8 @@ void onAttach(@NonNull Context context) {
new FlutterEngine(
host.getContext(),
host.getFlutterShellArgs().toArray(),
/*automaticallyRegisterPlugins=*/ false);
/*automaticallyRegisterPlugins=*/ false,
/*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
isFlutterEngineFromHost = false;
}

Expand Down Expand Up @@ -293,11 +296,22 @@ 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 pluginState = null;
byte[] frameworkState = null;
if (bundle != null) {
pluginState = bundle.getBundle(PLUGINS_RESTORATION_BUNDLE_KEY);
frameworkState = bundle.getByteArray(FRAMEWORK_RESTORATION_BUNDLE_KEY);
}

if (host.shouldRestoreAndSaveState()) {
flutterEngine.getRestorationChannel().setRestorationData(frameworkState);
}

if (host.shouldAttachEngineToActivity()) {
flutterEngine.getActivityControlSurface().onRestoreInstanceState(bundle);
flutterEngine.getActivityControlSurface().onRestoreInstanceState(pluginState);
}
}

Expand Down Expand Up @@ -444,11 +458,19 @@ 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();

if (host.shouldRestoreAndSaveState()) {
bundle.putByteArray(
FRAMEWORK_RESTORATION_BUNDLE_KEY,
flutterEngine.getRestorationChannel().getRestorationData());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really remember. We may have talked about this. Can't we clear the data now that we read this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we delete the data, engine and framework are getting out of sync which may lead to strange behavior and will make it harder to reason about restoration state. In general, framework and engine should always agree on what the current restoration state is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate with a specific example? Perhaps write this down somewhere here for future archeologists too. I get the impression that we'll never run into a case where framework writes, engine reads and deletes, then the framework tries to read again? Is that right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave a longer answer to this on the other comment thread. In reality, the sequence of events that you describe will of course not happen. However, in reality, the only moment where we could delete the engine's copy of the restoration state is after we have handed it to the operating system. At that point, our app is moments away from getting killed anyways (thus freeing up all the memory). So why add the extra complexity of allowing framework and engine restoration state to go out of sync in this situation?


if (host.shouldAttachEngineToActivity()) {
flutterEngine.getActivityControlSurface().onSaveInstanceState(bundle);
final Bundle plugins = new Bundle();
flutterEngine.getActivityControlSurface().onSaveInstanceState(plugins);
bundle.putBundle(PLUGINS_RESTORATION_BUNDLE_KEY, plugins);
}
}

Expand Down Expand Up @@ -804,5 +826,17 @@ PlatformPlugin providePlatformPlugin(

/** Invoked by this delegate when its {@link FlutterView} stops painting pixels. */
void onFlutterUiNoLongerDisplayed();

/**
* Whether state restoration is enabled.
*
* <p>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.
*
* <p>This defaults to true, unless a cached engine is used.
*/
boolean shouldRestoreAndSaveState();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -102,6 +104,7 @@ public void onPreEngineRestart() {
}

platformViewsController.onPreEngineRestart();
restorationChannel.clearData();
}
};

Expand Down Expand Up @@ -159,6 +162,39 @@ public FlutterEngine(
automaticallyRegisterPlugins);
}

/**
* Same as {@link #FlutterEngine(Context, String[], boolean)} with added support for configuring
* whether the engine will receive restoration data.
*
* <p>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.
*
* <p>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".
*
* <p>If the framework never requests the restoration data, this flag has no effect.
*/
public FlutterEngine(
@NonNull Context context,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins,
boolean waitForRestorationData) {
this(
context,
FlutterLoader.getInstance(),
new FlutterJNI(),
new PlatformViewsController(),
dartVmArgs,
automaticallyRegisterPlugins,
waitForRestorationData);
}

/**
* Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[])} but with no Dart
* VM flags.
Expand Down Expand Up @@ -194,14 +230,36 @@ public FlutterEngine(
automaticallyRegisterPlugins);
}

/** Fully configurable {@code FlutterEngine} constructor. */
/**
* 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,
@NonNull FlutterLoader flutterLoader,
@NonNull FlutterJNI flutterJNI,
@NonNull PlatformViewsController platformViewsController,
@Nullable String[] dartVmArgs,
boolean automaticallyRegisterPlugins,
boolean waitForRestorationData) {
this.flutterJNI = flutterJNI;
flutterLoader.startInitialization(context.getApplicationContext());
flutterLoader.ensureInitializationComplete(context, dartVmArgs);
Expand All @@ -224,6 +282,7 @@ public FlutterEngine(
mouseCursorChannel = new MouseCursorChannel(dartExecutor);
navigationChannel = new NavigationChannel(dartExecutor);
platformChannel = new PlatformChannel(dartExecutor);
restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData);
settingsChannel = new SettingsChannel(dartExecutor);
systemChannel = new SystemChannel(dartExecutor);
textInputChannel = new TextInputChannel(dartExecutor);
Expand Down Expand Up @@ -380,6 +439,18 @@ public PlatformChannel getPlatformChannel() {
return platformChannel;
}

/**
* System channel to exchange restoration data between framework and engine.
*
* <p>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.
Expand Down
Loading