1515import android .view .LayoutInflater ;
1616import android .view .View ;
1717import android .view .ViewGroup ;
18+ import android .view .ViewTreeObserver .OnPreDrawListener ;
1819import androidx .annotation .NonNull ;
1920import androidx .annotation .Nullable ;
2021import androidx .annotation .VisibleForTesting ;
6768 private static final String TAG = "FlutterActivityAndFragmentDelegate" ;
6869 private static final String FRAMEWORK_RESTORATION_BUNDLE_KEY = "framework" ;
6970 private static final String PLUGINS_RESTORATION_BUNDLE_KEY = "plugins" ;
71+ private static final int FLUTTER_SPLASH_VIEW_FALLBACK_ID = 486947586 ;
7072
7173 // The FlutterActivity or FlutterFragment that is delegating most of its calls
7274 // to this FlutterActivityAndFragmentDelegate.
7375 @ NonNull private Host host ;
7476 @ Nullable private FlutterEngine flutterEngine ;
75- @ Nullable private FlutterSplashView flutterSplashView ;
7677 @ Nullable private FlutterView flutterView ;
7778 @ Nullable private PlatformPlugin platformPlugin ;
79+ @ VisibleForTesting @ Nullable OnPreDrawListener activePreDrawListener ;
7880 private boolean isFlutterEngineFromHost ;
81+ private boolean isFlutterUiDisplayed ;
7982
8083 @ NonNull
8184 private final FlutterUiDisplayListener flutterUiDisplayListener =
8285 new FlutterUiDisplayListener () {
8386 @ Override
8487 public void onFlutterUiDisplayed () {
8588 host .onFlutterUiDisplayed ();
89+ isFlutterUiDisplayed = true ;
8690 }
8791
8892 @ Override
8993 public void onFlutterUiNoLongerDisplayed () {
9094 host .onFlutterUiNoLongerDisplayed ();
95+ isFlutterUiDisplayed = false ;
9196 }
9297 };
9398
@@ -254,6 +259,16 @@ void onAttach(@NonNull Context context) {
254259 *
255260 * <p>{@code inflater} and {@code container} may be null when invoked from an {@code Activity}.
256261 *
262+ * <p>{@code shouldDelayFirstAndroidViewDraw} determines whether to set up an {@link
263+ * android.view.ViewTreeObserver.OnPreDrawListener}, which will defer the current drawing pass
264+ * till after the Flutter UI has been displayed. This results in more accurate timings reported
265+ * with Android tools, such as "Displayed" timing printed with `am start`.
266+ *
267+ * <p>Note that it should only be set to true when {@code Host#getRenderMode()} is {@code
268+ * RenderMode.surface}. This parameter is also ignored, disabling the delay should the legacy
269+ * {@code Host#provideSplashScreen()} be non-null. See <a
270+ * href="https://flutter.dev/go/android-splash-migration">Android Splash Migration</a>.
271+ *
257272 * <p>This method:
258273 *
259274 * <ol>
@@ -269,7 +284,8 @@ View onCreateView(
269284 LayoutInflater inflater ,
270285 @ Nullable ViewGroup container ,
271286 @ Nullable Bundle savedInstanceState ,
272- int flutterViewId ) {
287+ int flutterViewId ,
288+ boolean shouldDelayFirstAndroidViewDraw ) {
273289 Log .v (TAG , "Creating FlutterView." );
274290 ensureAlive ();
275291
@@ -298,15 +314,28 @@ View onCreateView(
298314 // Add listener to be notified when Flutter renders its first frame.
299315 flutterView .addOnFirstFrameRenderedListener (flutterUiDisplayListener );
300316
301- flutterSplashView = new FlutterSplashView (host .getContext ());
302- flutterSplashView .setId (ViewUtils .generateViewId (486947586 ));
303- flutterSplashView .displayFlutterViewWithSplash (flutterView , host .provideSplashScreen ());
304-
305317 Log .v (TAG , "Attaching FlutterEngine to FlutterView." );
306318 flutterView .attachToFlutterEngine (flutterEngine );
307319 flutterView .setId (flutterViewId );
308320
309- return flutterSplashView ;
321+ SplashScreen splashScreen = host .provideSplashScreen ();
322+
323+ if (splashScreen != null ) {
324+ Log .w (
325+ TAG ,
326+ "A splash screen was provided to Flutter, but this is deprecated. See"
327+ + " flutter.dev/go/android-splash-migration for migration steps." );
328+ FlutterSplashView flutterSplashView = new FlutterSplashView (host .getContext ());
329+ flutterSplashView .setId (ViewUtils .generateViewId (FLUTTER_SPLASH_VIEW_FALLBACK_ID ));
330+ flutterSplashView .displayFlutterViewWithSplash (flutterView , splashScreen );
331+
332+ return flutterSplashView ;
333+ }
334+
335+ if (shouldDelayFirstAndroidViewDraw ) {
336+ delayFirstAndroidViewDraw (flutterView );
337+ }
338+ return flutterView ;
310339 }
311340
312341 void onRestoreInstanceState (@ Nullable Bundle bundle ) {
@@ -415,6 +444,38 @@ private String maybeGetInitialRouteFromIntent(Intent intent) {
415444 return null ;
416445 }
417446
447+ /**
448+ * Delays the first drawing of the {@code flutterView} until the Flutter first has been displayed.
449+ */
450+ private void delayFirstAndroidViewDraw (FlutterView flutterView ) {
451+ if (host .getRenderMode () != RenderMode .surface ) {
452+ // Using a TextureView will cause a deadlock, where the underlying SurfaceTexture is never
453+ // available since it will wait for drawing to be completed first. At the same time, the
454+ // preDraw listener keeps returning false since the Flutter Engine waits for the
455+ // SurfaceTexture to be available.
456+ throw new IllegalArgumentException (
457+ "Cannot delay the first Android view draw when the render mode is not set to"
458+ + " `RenderMode.surface`." );
459+ }
460+
461+ if (activePreDrawListener != null ) {
462+ flutterView .getViewTreeObserver ().removeOnPreDrawListener (activePreDrawListener );
463+ }
464+
465+ activePreDrawListener =
466+ new OnPreDrawListener () {
467+ @ Override
468+ public boolean onPreDraw () {
469+ if (isFlutterUiDisplayed && activePreDrawListener != null ) {
470+ flutterView .getViewTreeObserver ().removeOnPreDrawListener (this );
471+ activePreDrawListener = null ;
472+ }
473+ return isFlutterUiDisplayed ;
474+ }
475+ };
476+ flutterView .getViewTreeObserver ().addOnPreDrawListener (activePreDrawListener );
477+ }
478+
418479 /**
419480 * Invoke this from {@code Activity#onResume()} or {@code Fragment#onResume()}.
420481 *
@@ -496,6 +557,10 @@ void onDestroyView() {
496557 Log .v (TAG , "onDestroyView()" );
497558 ensureAlive ();
498559
560+ if (activePreDrawListener != null ) {
561+ flutterView .getViewTreeObserver ().removeOnPreDrawListener (activePreDrawListener );
562+ activePreDrawListener = null ;
563+ }
499564 flutterView .detachFromFlutterEngine ();
500565 flutterView .removeOnFirstFrameRenderedListener (flutterUiDisplayListener );
501566 }
0 commit comments