From 75ac0d39577712c253b5410ac53e55dfd2e5a409 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?= <26625149+0xZOne@users.noreply.github.com> Date: Wed, 16 Aug 2023 10:34:04 +0800 Subject: [PATCH] Enabling the host application to control the timing of attaching the |FlutterView| to the engine (flutter/engine#43595) In the add-to-app scenario where multiple FlutterViews share the same FlutterEngine, the host application desires to determine the timing of attaching the FlutterView to the engine, for example, during the `onResume` instead of the `onCreateView`. As an example, consider the following scenario: A native page contains multiple tabs, and each tab is a FlutterFragment. During initialization, FlutterFragments of different tabs are created almost simultaneously, but only the one that needs to be displayed currently requires attachment to the engine, while the others need to be attached only when they receive the |onResume| callback. Partial fix: https://github.com/flutter/flutter/issues/130235 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- .../embedding/android/FlutterActivity.java | 13 +++++++++ .../FlutterActivityAndFragmentDelegate.java | 18 ++++++++++-- .../embedding/android/FlutterFragment.java | 13 +++++++++ ...lutterActivityAndFragmentDelegateTest.java | 28 +++++++++++++++++++ .../android/FlutterAndroidComponentTest.java | 5 ++++ 5 files changed, 75 insertions(+), 2 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 5ec47c5831..acdfc1ca3a 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 @@ -1407,6 +1407,19 @@ public class FlutterActivity extends Activity return true; } + /** + * Whether to automatically attach the {@link FlutterView} to the engine. + * + *

Returning {@code false} means that the task of attaching the {@link FlutterView} to the + * engine will be taken over by the host application. + * + *

Defaults to {@code true}. + */ + @Override + public boolean attachToEngineAutomatically() { + 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 1e59b74e03..9596dc6cc4 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 @@ -390,8 +390,10 @@ import java.util.List; // Add listener to be notified when Flutter renders its first frame. flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener); - Log.v(TAG, "Attaching FlutterEngine to FlutterView."); - flutterView.attachToFlutterEngine(flutterEngine); + if (host.attachToEngineAutomatically()) { + Log.v(TAG, "Attaching FlutterEngine to FlutterView."); + flutterView.attachToFlutterEngine(flutterEngine); + } flutterView.setId(flutterViewId); if (shouldDelayFirstAndroidViewDraw) { @@ -1171,5 +1173,17 @@ import java.util.List; * while return {@code true} means the engine dispatches these events. */ boolean shouldDispatchAppLifecycleState(); + + /** + * Whether to automatically attach the {@link FlutterView} to the engine. + * + *

In the add-to-app scenario where multiple {@link FlutterView} share the same {@link + * FlutterEngine}, the host application desires to determine the timing of attaching the {@link + * FlutterView} to the engine, for example, during the {@code onResume} instead of the {@code + * onCreateView}. + * + *

Defaults to {@code true}. + */ + boolean attachToEngineAutomatically(); } } 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 b521c73882..586d60c9c6 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 @@ -1645,6 +1645,19 @@ public class FlutterFragment extends Fragment return true; } + /** + * Whether to automatically attach the {@link FlutterView} to the engine. + * + *

Returning {@code false} means that the task of attaching the {@link FlutterView} to the + * engine will be taken over by the host application. + * + *

Defaults to {@code true}. + */ + @Override + public boolean attachToEngineAutomatically() { + 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 3cb5d2dd66..86b5acfac0 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 @@ -2,9 +2,11 @@ package io.flutter.embedding.android; import static android.content.ComponentCallbacks2.*; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNotNull; @@ -88,6 +90,7 @@ public class FlutterActivityAndFragmentDelegateTest { when(mockHost.shouldHandleDeeplinking()).thenReturn(false); when(mockHost.shouldDestroyEngineWithHost()).thenReturn(true); when(mockHost.shouldDispatchAppLifecycleState()).thenReturn(true); + when(mockHost.attachToEngineAutomatically()).thenReturn(true); } @Test @@ -1241,6 +1244,31 @@ public class FlutterActivityAndFragmentDelegateTest { assertEquals(engineUnderTest, mockFlutterEngine); } + @Test + public void itDoesAttachFlutterViewToEngine() { + // ---- Test setup ---- + // Create the real object that we're testing. + FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); + delegate.onAttach(ctx); + delegate.onCreateView(null, null, null, 0, true); + + // --- Execute the behavior under test --- + assertTrue(delegate.flutterView.isAttachedToFlutterEngine()); + } + + @Test + public void itDoesNotAttachFlutterViewToEngine() { + // ---- Test setup ---- + // Create the real object that we're testing. + when(mockHost.attachToEngineAutomatically()).thenReturn(false); + FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); + delegate.onAttach(ctx); + delegate.onCreateView(null, null, null, 0, true); + + // --- Execute the behavior under test --- + assertFalse(delegate.flutterView.isAttachedToFlutterEngine()); + } + /** * Creates a mock {@link io.flutter.embedding.engine.FlutterEngine}. * 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 d569c64c36..64cf15d040 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 @@ -389,6 +389,11 @@ public class FlutterAndroidComponentTest { return true; } + @Override + public boolean attachToEngineAutomatically() { + return true; + } + @Override public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) {}