Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit f110bc7

Browse files
authored
Revert "Revert "Use preDraw for the Android embedding (#27645)" (#27788)" (#27811)
1 parent bbefba3 commit f110bc7

File tree

8 files changed

+264
-25
lines changed

8 files changed

+264
-25
lines changed

shell/platform/android/io/flutter/embedding/android/FlutterActivity.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,8 @@ private View createFlutterView() {
542542
/* inflater=*/ null,
543543
/* container=*/ null,
544544
/* savedInstanceState=*/ null,
545-
/*flutterViewId=*/ FLUTTER_VIEW_ID);
545+
/*flutterViewId=*/ FLUTTER_VIEW_ID,
546+
/*shouldDelayFirstAndroidViewDraw=*/ getRenderMode() == RenderMode.surface);
546547
}
547548

548549
private void configureStatusBarForFullscreenFlutterExperience() {

shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.view.LayoutInflater;
1616
import android.view.View;
1717
import android.view.ViewGroup;
18+
import android.view.ViewTreeObserver.OnPreDrawListener;
1819
import androidx.annotation.NonNull;
1920
import androidx.annotation.Nullable;
2021
import androidx.annotation.VisibleForTesting;
@@ -67,27 +68,31 @@
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
}

shell/platform/android/io/flutter/embedding/android/FlutterFragment.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ public class FlutterFragment extends Fragment
113113
protected static final String ARG_HANDLE_DEEPLINKING = "handle_deeplinking";
114114
/** Path to Flutter's Dart code. */
115115
protected static final String ARG_APP_BUNDLE_PATH = "app_bundle_path";
116+
/** Whether to delay the Android drawing pass till after the Flutter UI has been displayed. */
117+
protected static final String ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW =
118+
"should_delay_first_android_view_draw";
119+
116120
/** Flutter shell arguments. */
117121
protected static final String ARG_FLUTTER_INITIALIZATION_ARGS = "initialization_args";
118122
/**
@@ -229,6 +233,7 @@ public static class NewEngineFragmentBuilder {
229233
private TransparencyMode transparencyMode = TransparencyMode.transparent;
230234
private boolean shouldAttachEngineToActivity = true;
231235
private boolean shouldAutomaticallyHandleOnBackPressed = false;
236+
private boolean shouldDelayFirstAndroidViewDraw = false;
232237

233238
/**
234239
* Constructs a {@code NewEngineFragmentBuilder} that is configured to construct an instance of
@@ -382,6 +387,18 @@ public NewEngineFragmentBuilder shouldAutomaticallyHandleOnBackPressed(
382387
return this;
383388
}
384389

390+
/**
391+
* Whether to delay the Android drawing pass till after the Flutter UI has been displayed.
392+
*
393+
* <p>See {#link FlutterActivityAndFragmentDelegate#onCreateView} for more details.
394+
*/
395+
@NonNull
396+
public NewEngineFragmentBuilder shouldDelayFirstAndroidViewDraw(
397+
boolean shouldDelayFirstAndroidViewDraw) {
398+
this.shouldDelayFirstAndroidViewDraw = shouldDelayFirstAndroidViewDraw;
399+
return this;
400+
}
401+
385402
/**
386403
* Creates a {@link Bundle} of arguments that are assigned to the new {@code FlutterFragment}.
387404
*
@@ -410,6 +427,7 @@ protected Bundle createArgs() {
410427
args.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, true);
411428
args.putBoolean(
412429
ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, shouldAutomaticallyHandleOnBackPressed);
430+
args.putBoolean(ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW, shouldDelayFirstAndroidViewDraw);
413431
return args;
414432
}
415433

@@ -496,6 +514,7 @@ public static class CachedEngineFragmentBuilder {
496514
private TransparencyMode transparencyMode = TransparencyMode.transparent;
497515
private boolean shouldAttachEngineToActivity = true;
498516
private boolean shouldAutomaticallyHandleOnBackPressed = false;
517+
private boolean shouldDelayFirstAndroidViewDraw = false;
499518

500519
private CachedEngineFragmentBuilder(@NonNull String engineId) {
501520
this(FlutterFragment.class, engineId);
@@ -621,6 +640,18 @@ public CachedEngineFragmentBuilder shouldAutomaticallyHandleOnBackPressed(
621640
return this;
622641
}
623642

643+
/**
644+
* Whether to delay the Android drawing pass till after the Flutter UI has been displayed.
645+
*
646+
* <p>See {#link FlutterActivityAndFragmentDelegate#onCreateView} for more details.
647+
*/
648+
@NonNull
649+
public CachedEngineFragmentBuilder shouldDelayFirstAndroidViewDraw(
650+
@NonNull boolean shouldDelayFirstAndroidViewDraw) {
651+
this.shouldDelayFirstAndroidViewDraw = shouldDelayFirstAndroidViewDraw;
652+
return this;
653+
}
654+
624655
/**
625656
* Creates a {@link Bundle} of arguments that are assigned to the new {@code FlutterFragment}.
626657
*
@@ -642,6 +673,7 @@ protected Bundle createArgs() {
642673
args.putBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY, shouldAttachEngineToActivity);
643674
args.putBoolean(
644675
ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, shouldAutomaticallyHandleOnBackPressed);
676+
args.putBoolean(ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW, shouldDelayFirstAndroidViewDraw);
645677
return args;
646678
}
647679

@@ -727,7 +759,11 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
727759
public View onCreateView(
728760
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
729761
return delegate.onCreateView(
730-
inflater, container, savedInstanceState, /*flutterViewId=*/ FLUTTER_VIEW_ID);
762+
inflater,
763+
container,
764+
savedInstanceState,
765+
/*flutterViewId=*/ FLUTTER_VIEW_ID,
766+
shouldDelayFirstAndroidViewDraw());
731767
}
732768

733769
@Override
@@ -1267,6 +1303,12 @@ public boolean popSystemNavigator() {
12671303
return false;
12681304
}
12691305

1306+
@VisibleForTesting
1307+
@NonNull
1308+
boolean shouldDelayFirstAndroidViewDraw() {
1309+
return getArguments().getBoolean(ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW);
1310+
}
1311+
12701312
private boolean stillAttachedForEvent(String event) {
12711313
if (delegate == null) {
12721314
Log.w(TAG, "FlutterFragment " + hashCode() + " " + event + " called after release.");

shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ protected FlutterFragment createFlutterFragment() {
424424
backgroundMode == BackgroundMode.opaque
425425
? TransparencyMode.opaque
426426
: TransparencyMode.transparent;
427+
final boolean shouldDelayFirstAndroidViewDraw = renderMode == RenderMode.surface;
427428

428429
if (getCachedEngineId() != null) {
429430
Log.v(
@@ -447,6 +448,7 @@ protected FlutterFragment createFlutterFragment() {
447448
.handleDeeplinking(shouldHandleDeeplinking())
448449
.shouldAttachEngineToActivity(shouldAttachEngineToActivity())
449450
.destroyEngineWithFragment(shouldDestroyEngineWithHost())
451+
.shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
450452
.build();
451453
} else {
452454
Log.v(
@@ -476,6 +478,7 @@ protected FlutterFragment createFlutterFragment() {
476478
.renderMode(renderMode)
477479
.transparencyMode(transparencyMode)
478480
.shouldAttachEngineToActivity(shouldAttachEngineToActivity())
481+
.shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
479482
.build();
480483
}
481484
}

0 commit comments

Comments
 (0)