Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
3 changes: 2 additions & 1 deletion ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPluginRegist
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/DrawableSplashScreen.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/ExclusiveAppComponent.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java
Expand All @@ -727,7 +728,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Splas
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/TransparencyMode.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineCache.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEnginePluginRegistry.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterOverlaySurface.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java
Expand Down
5 changes: 3 additions & 2 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ android_java_sources = [
"io/flutter/embedding/android/AndroidKeyProcessor.java",
"io/flutter/embedding/android/AndroidTouchProcessor.java",
"io/flutter/embedding/android/DrawableSplashScreen.java",
"io/flutter/embedding/android/ExclusiveAppComponent.java",
"io/flutter/embedding/android/FlutterActivity.java",
"io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java",
"io/flutter/embedding/android/FlutterActivityLaunchConfigs.java",
Expand All @@ -147,7 +148,7 @@ android_java_sources = [
"io/flutter/embedding/android/TransparencyMode.java",
"io/flutter/embedding/engine/FlutterEngine.java",
"io/flutter/embedding/engine/FlutterEngineCache.java",
"io/flutter/embedding/engine/FlutterEnginePluginRegistry.java",
"io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java",
"io/flutter/embedding/engine/FlutterJNI.java",
"io/flutter/embedding/engine/FlutterOverlaySurface.java",
"io/flutter/embedding/engine/FlutterShellArgs.java",
Expand Down Expand Up @@ -429,7 +430,7 @@ action("robolectric_tests") {
"test/io/flutter/embedding/android/FlutterViewTest.java",
"test/io/flutter/embedding/android/RobolectricFlutterActivity.java",
"test/io/flutter/embedding/engine/FlutterEngineCacheTest.java",
"test/io/flutter/embedding/engine/FlutterEnginePluginRegistryTest.java",
"test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java",
"test/io/flutter/embedding/engine/FlutterEngineTest.java",
"test/io/flutter/embedding/engine/FlutterJNITest.java",
"test/io/flutter/embedding/engine/FlutterShellArgsTest.java",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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.android;

import androidx.annotation.NonNull;

/**
* An Android App Component exclusively attached to a {@link
* io.flutter.embedding.engine.FlutterEngine}.
*
* <p>An exclusive App Component's {@link #detachFromFlutterEngine} is invoked when another App
* Component is becoming attached to the {@link io.flutter.embedding.engine.FlutterEngine}.
*
* <p>The term "App Component" refer to the 4 component types: Activity, Service, Broadcast
* Receiver, and Content Provider, as defined in
* https://developer.android.com/guide/components/fundamentals.
*
* @param <T> The App Component behind this exclusive App Component.
*/
public interface ExclusiveAppComponent<T> {
/**
* Called when another App Component is about to become attached to the {@link
* io.flutter.embedding.engine.FlutterEngine} this App Component is currently attached to.
*
* <p>This App Component's connections to the {@link io.flutter.embedding.engine.FlutterEngine}
* are still valid at the moment of this call.
*/
void detachFromFlutterEngine();

/** Retrieve the App Component behind this exclusive App Component. */
@NonNull
T getAppComponent();
}
Original file line number Diff line number Diff line change
Expand Up @@ -560,56 +560,102 @@ protected void onPause() {
@Override
protected void onStop() {
super.onStop();
delegate.onStop();
if (stillAttachedForEvent("onStop")) {
delegate.onStop();
}
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
}

@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
delegate.onSaveInstanceState(outState);
if (stillAttachedForEvent("onSaveInstanceState")) {
delegate.onSaveInstanceState(outState);
}
}

/**
* Irreversibly release this activity's control of the {@link FlutterEngine} and its
* subcomponents.
*
* <p>Calling will disconnect this activity's view from the Flutter renderer, disconnect this
* activity from plugins' {@link ActivityControlSurface}, and stop system channel messages from
* this activity.
*
* <p>After calling, this activity should be disposed immediately and not be re-used.
*/
private void release() {
delegate.onDestroyView();
delegate.onDetach();
delegate.release();
delegate = null;
}

@Override
public void detachFromFlutterEngine() {
Log.v(
TAG,
"FlutterActivity "
+ this
+ " connection to the engine "
+ getFlutterEngine()
+ " evicted by another attaching activity");
release();
}

@Override
protected void onDestroy() {
super.onDestroy();
delegate.onDestroyView();
delegate.onDetach();
if (stillAttachedForEvent("onDestroy")) {
release();
}
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

We might need to check the delegate nullness in lifecycle methods below as well (onActivityResult and such). At the very least, let's confirm with an Android expert (perhaps clm) that those methods are never called after onPause().

Copy link
Member Author

Choose a reason for hiding this comment

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

Ya, sensible. Ping'ed him on the bug.


@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
delegate.onActivityResult(requestCode, resultCode, data);
if (stillAttachedForEvent("onActivityResult")) {
delegate.onActivityResult(requestCode, resultCode, data);
}
}

@Override
protected void onNewIntent(@NonNull Intent intent) {
// TODO(mattcarroll): change G3 lint rule that forces us to call super
super.onNewIntent(intent);
delegate.onNewIntent(intent);
if (stillAttachedForEvent("onNewIntent")) {
delegate.onNewIntent(intent);
}
}

@Override
public void onBackPressed() {
delegate.onBackPressed();
if (stillAttachedForEvent("onBackPressed")) {
delegate.onBackPressed();
}
}

@Override
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
delegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (stillAttachedForEvent("onRequestPermissionsResult")) {
delegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}

@Override
public void onUserLeaveHint() {
delegate.onUserLeaveHint();
if (stillAttachedForEvent("onUserLeaveHint")) {
delegate.onUserLeaveHint();
}
}

@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
delegate.onTrimMemory(level);
if (stillAttachedForEvent("onTrimMemory")) {
delegate.onTrimMemory(level);
}
}

/**
Expand Down Expand Up @@ -908,7 +954,7 @@ public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) {
* <p>Returning false from this method does not preclude a {@link FlutterEngine} from being
* attaching to a {@code FlutterActivity} - it just prevents the attachment from happening
* automatically. A developer can choose to subclass {@code FlutterActivity} and then invoke
* {@link ActivityControlSurface#attachToActivity(Activity, Lifecycle)} and {@link
* {@link ActivityControlSurface#attachToActivity(ExclusiveAppComponent, Lifecycle)} and {@link
* ActivityControlSurface#detachFromActivity()} at the desired times.
*
* <p>One reason that a developer might choose to manually manage the relationship between the
Expand Down Expand Up @@ -961,4 +1007,12 @@ public boolean shouldRestoreAndSaveState() {
}
return true;
}

private boolean stillAttachedForEvent(String event) {
if (delegate == null) {
Log.v(TAG, "FlutterActivity " + hashCode() + " " + event + " called after release.");
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
* the same form. <strong>Do not use this class as a convenient shortcut for any other
* behavior.</strong>
*/
/* package */ final class FlutterActivityAndFragmentDelegate {
/* package */ class FlutterActivityAndFragmentDelegate implements ExclusiveAppComponent<Activity> {
private static final String TAG = "FlutterActivityAndFragmentDelegate";
private static final String FRAMEWORK_RESTORATION_BUNDLE_KEY = "framework";
private static final String PLUGINS_RESTORATION_BUNDLE_KEY = "plugins";
Expand Down Expand Up @@ -154,14 +154,6 @@ void onAttach(@NonNull Context context) {
setupFlutterEngine();
}

// Regardless of whether or not a FlutterEngine already existed, the PlatformPlugin
// is bound to a specific Activity. Therefore, it needs to be created and configured
// every time this Fragment attaches to a new Activity.
// TODO(mattcarroll): the PlatformPlugin needs to be reimagined because it implicitly takes
// control of the entire window. This is unacceptable for non-fullscreen
// use-cases.
platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);

if (host.shouldAttachEngineToActivity()) {
// Notify any plugins that are currently attached to our FlutterEngine that they
// are now attached to an Activity.
Expand All @@ -172,15 +164,32 @@ void onAttach(@NonNull Context context) {
// which means there shouldn't be any possibility for the Fragment Lifecycle to get out of
// sync with the Activity. We use the Fragment's Lifecycle because it is possible that the
// attached Activity is not a LifecycleOwner.
Log.v(TAG, "Attaching FlutterEngine to the Activity that owns this Fragment.");
flutterEngine
.getActivityControlSurface()
.attachToActivity(host.getActivity(), host.getLifecycle());
Log.v(TAG, "Attaching FlutterEngine to the Activity that owns this delegate.");
flutterEngine.getActivityControlSurface().attachToActivity(this, host.getLifecycle());
}

// Regardless of whether or not a FlutterEngine already existed, the PlatformPlugin
// is bound to a specific Activity. Therefore, it needs to be created and configured
// every time this Fragment attaches to a new Activity.
// TODO(mattcarroll): the PlatformPlugin needs to be reimagined because it implicitly takes
// control of the entire window. This is unacceptable for non-fullscreen
// use-cases.
platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);

host.configureFlutterEngine(flutterEngine);
}

@Override
public @NonNull Activity getAppComponent() {
final Activity activity = host.getActivity();
if (activity == null) {
throw new AssertionError(
"FlutterActivityAndFragmentDelegate's getAppComponent should only "
+ "be queried after onAttach, when the host's activity should always be non-null");
}
return activity;
}

/**
* Obtains a reference to a FlutterEngine to back this delegate and its {@code host}.
*
Expand Down Expand Up @@ -480,6 +489,24 @@ void onSaveInstanceState(@Nullable Bundle bundle) {
}
}

@Override
public void detachFromFlutterEngine() {
if (host.shouldDestroyEngineWithHost()) {
// The host owns the engine and should never have its engine taken by another exclusive
// activity.
throw new AssertionError(
"The internal FlutterEngine created by "
+ host
+ " has been attached to by another activity. To persist a FlutterEngine beyond the "
+ "ownership of this activity, explicitly create a FlutterEngine");
}

// Default, but customizable, behavior is for the host to call {@link #onDetach}
// deterministically as to not mix more events during the lifecycle of the next exclusive
// activity.
host.detachFromFlutterEngine();
}

/**
* Invoke this from {@code Activity#onDestroy()} or {@code Fragment#onDetach()}.
*
Expand Down Expand Up @@ -741,6 +768,15 @@ private void ensureAlive() {
*/
boolean shouldDestroyEngineWithHost();

/**
* Callback called when the {@link FlutterEngine} has been attached to by another activity
* before this activity was destroyed.
*
* <p>The expected behavior is for this activity to synchronously stop using the {@link
* FlutterEngine} to avoid lifecycle crosstalk with the new activity.
*/
void detachFromFlutterEngine();

/** Returns the Dart entrypoint that should run when a new {@link FlutterEngine} is created. */
@NonNull
String getDartEntrypointFunctionName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

package io.flutter.embedding.android;

import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import io.flutter.embedding.engine.FlutterEngine;
Expand All @@ -21,8 +20,8 @@ public interface FlutterEngineConfigurator {
*
* <p>This method is called after the given {@link FlutterEngine} has been attached to the owning
* {@code FragmentActivity}. See {@link
* io.flutter.embedding.engine.plugins.activity.ActivityControlSurface#attachToActivity(Activity,
* Lifecycle)}.
* io.flutter.embedding.engine.plugins.activity.ActivityControlSurface#attachToActivity(
* ExclusiveAppComponent, Lifecycle)}.
*
* <p>It is possible that the owning {@code FragmentActivity} opted not to connect itself as an
* {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface}. In that case, any
Expand Down
Loading