From 1e2f79627804da0eb48ed523046b67255e548e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rulong=20Chen=EF=BC=88=E9=99=88=E6=B1=9D=E9=BE=99=EF=BC=89?= Date: Fri, 4 Mar 2022 05:06:11 +0800 Subject: [PATCH] [android] Give the shared engine app a chance to take control of application lifecycle state events. (flutter/engine#30208) --- .../embedding/android/FlutterActivity.java | 13 ++++++ .../FlutterActivityAndFragmentDelegate.java | 41 +++++++++++++++++-- .../embedding/android/FlutterFragment.java | 13 ++++++ ...lutterActivityAndFragmentDelegateTest.java | 25 +++++++++++ .../android/FlutterAndroidComponentTest.java | 5 +++ 5 files changed, 93 insertions(+), 4 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 36567d9090..9b6b6eed18 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -1170,6 +1170,19 @@ public class FlutterActivity extends Activity return true; } + /** + * Give the host application a chance to take control of the app lifecycle events. + * + *

Return {@code false} means the host application dispatches these app lifecycle events, while + * return {@code true} means the engine dispatches these events. + * + *

Defaults to {@code true}. + */ + @Override + public boolean shouldDispatchAppLifecycleState() { + return true; + } + @Override public boolean popSystemNavigator() { // Hook for subclass. No-op if returns false. diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 867f442d2d..fb4394aa81 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -513,7 +513,9 @@ import java.util.Arrays; void onResume() { Log.v(TAG, "onResume()"); ensureAlive(); - flutterEngine.getLifecycleChannel().appIsResumed(); + if (host.shouldDispatchAppLifecycleState()) { + flutterEngine.getLifecycleChannel().appIsResumed(); + } } /** @@ -559,7 +561,9 @@ import java.util.Arrays; void onPause() { Log.v(TAG, "onPause()"); ensureAlive(); - flutterEngine.getLifecycleChannel().appIsInactive(); + if (host.shouldDispatchAppLifecycleState()) { + flutterEngine.getLifecycleChannel().appIsInactive(); + } } /** @@ -579,7 +583,11 @@ import java.util.Arrays; void onStop() { Log.v(TAG, "onStop()"); ensureAlive(); - flutterEngine.getLifecycleChannel().appIsPaused(); + + if (host.shouldDispatchAppLifecycleState()) { + flutterEngine.getLifecycleChannel().appIsPaused(); + } + // This is a workaround for a bug on some OnePlus phones. The visibility of the application // window is still true after locking the screen on some OnePlus phones, and shows a black // screen when unlocked. We can work around this by changing the visibility of FlutterView in @@ -681,7 +689,9 @@ import java.util.Arrays; platformPlugin = null; } - flutterEngine.getLifecycleChannel().appIsDetached(); + if (host.shouldDispatchAppLifecycleState()) { + flutterEngine.getLifecycleChannel().appIsDetached(); + } // Destroy our FlutterEngine if we're not set to retain it. if (host.shouldDestroyEngineWithHost()) { @@ -1078,5 +1088,28 @@ import java.util.Arrays; * SplashScreenView#remove}. */ void updateSystemUiOverlays(); + + /** + * Give the host application a chance to take control of the app lifecycle events to avoid + * lifecycle crosstalk. + * + *

In the add-to-app scenario where multiple {@link FlutterActivity} shares the same {@link + * FlutterEngine}, the application lifecycle state will have crosstalk causing the page to + * freeze. For example, we open a new page called FlutterActivity#2 from the previous page + * called FlutterActivity#1. The flow of app lifecycle states received by dart is as follows: + * + *

inactive (from FlutterActivity#1) -> resumed (from FlutterActivity#2) -> paused (from + * FlutterActivity#1) + * + *

On the one hand, the {@code paused} state from FlutterActivity#1 will cause the + * FlutterActivity#2 page to be stuck; On the other hand, these states are not expected from the + * perspective of the entire application lifecycle. If the host application gets the control of + * sending {@link AppLifecycleState}, It will be possible to correctly match the {@link + * AppLifecycleState} with the application-level lifecycle. + * + *

Return {@code false} means the host application dispatches these app lifecycle events, + * while return {@code true} means the engine dispatches these events. + */ + boolean shouldDispatchAppLifecycleState(); } } diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index 83609e5c23..0a4a709433 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -1317,6 +1317,19 @@ public class FlutterFragment extends Fragment } } + /** + * Give the host application a chance to take control of the app lifecycle events. + * + *

Return {@code false} means the host application dispatches these app lifecycle events, while + * return {@code true} means the engine dispatches these events. + * + *

Defaults to {@code true}. + */ + @Override + public boolean shouldDispatchAppLifecycleState() { + return true; + } + /** * {@inheritDoc} * diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index 226a38a2fe..4c84e9bfaf 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -83,6 +83,7 @@ public class FlutterActivityAndFragmentDelegateTest { when(mockHost.shouldAttachEngineToActivity()).thenReturn(true); when(mockHost.shouldHandleDeeplinking()).thenReturn(false); when(mockHost.shouldDestroyEngineWithHost()).thenReturn(true); + when(mockHost.shouldDispatchAppLifecycleState()).thenReturn(true); } @Test @@ -136,6 +137,30 @@ public class FlutterActivityAndFragmentDelegateTest { verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsDetached(); } + @Test + public void itDoesNotSendsLifecycleEventsToFlutter() { + // ---- Test setup ---- + // Create the real object that we're testing. + FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); + + when(mockHost.shouldDispatchAppLifecycleState()).thenReturn(false); + + // We're testing lifecycle behaviors, which require/expect that certain methods have already + // been executed by the time they run. Therefore, we run those expected methods first. + delegate.onAttach(RuntimeEnvironment.application); + delegate.onCreateView(null, null, null, 0, true); + delegate.onStart(); + delegate.onResume(); + delegate.onPause(); + delegate.onStop(); + delegate.onDetach(); + + verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed(); + verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused(); + verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive(); + verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached(); + } + @Test public void itDefersToTheHostToProvideFlutterEngine() { // ---- Test setup ---- diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java index 970eafadf8..729eb484e2 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java @@ -380,6 +380,11 @@ public class FlutterAndroidComponentTest { return true; } + @Override + public boolean shouldDispatchAppLifecycleState() { + return true; + } + @Override public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) {}