[android] Give the shared engine app a chance to take control of application lifecycle state events. (flutter/engine#30208)

This commit is contained in:
Rulong Chen(陈汝龙)
2022-03-04 05:06:11 +08:00
committed by GitHub
parent 40a5c49645
commit 1e2f796278
5 changed files with 93 additions and 4 deletions

View File

@@ -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.
*
* <p>Return {@code false} means the host application dispatches these app lifecycle events, while
* return {@code true} means the engine dispatches these events.
*
* <p>Defaults to {@code true}.
*/
@Override
public boolean shouldDispatchAppLifecycleState() {
return true;
}
@Override
public boolean popSystemNavigator() {
// Hook for subclass. No-op if returns false.

View File

@@ -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.
*
* <p>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:
*
* <p>inactive (from FlutterActivity#1) -> resumed (from FlutterActivity#2) -> paused (from
* FlutterActivity#1)
*
* <p>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.
*
* <p>Return {@code false} means the host application dispatches these app lifecycle events,
* while return {@code true} means the engine dispatches these events.
*/
boolean shouldDispatchAppLifecycleState();
}
}

View File

@@ -1317,6 +1317,19 @@ public class FlutterFragment extends Fragment
}
}
/**
* Give the host application a chance to take control of the app lifecycle events.
*
* <p>Return {@code false} means the host application dispatches these app lifecycle events, while
* return {@code true} means the engine dispatches these events.
*
* <p>Defaults to {@code true}.
*/
@Override
public boolean shouldDispatchAppLifecycleState() {
return true;
}
/**
* {@inheritDoc}
*

View File

@@ -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 ----

View File

@@ -380,6 +380,11 @@ public class FlutterAndroidComponentTest {
return true;
}
@Override
public boolean shouldDispatchAppLifecycleState() {
return true;
}
@Override
public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) {}