Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,8 @@ private View createFlutterView() {
/* inflater=*/ null,
/* container=*/ null,
/* savedInstanceState=*/ null,
/*flutterViewId=*/ FLUTTER_VIEW_ID);
/*flutterViewId=*/ FLUTTER_VIEW_ID,
/*shouldDelayFirstAndroidViewDraw=*/ getRenderMode() == RenderMode.surface);
}

private void configureStatusBarForFullscreenFlutterExperience() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
Expand Down Expand Up @@ -67,27 +68,31 @@
private static final String TAG = "FlutterActivityAndFragmentDelegate";
private static final String FRAMEWORK_RESTORATION_BUNDLE_KEY = "framework";
private static final String PLUGINS_RESTORATION_BUNDLE_KEY = "plugins";
private static final int FLUTTER_SPLASH_VIEW_FALLBACK_ID = 486947586;

// The FlutterActivity or FlutterFragment that is delegating most of its calls
// to this FlutterActivityAndFragmentDelegate.
@NonNull private Host host;
@Nullable private FlutterEngine flutterEngine;
@Nullable private FlutterSplashView flutterSplashView;
@Nullable private FlutterView flutterView;
@Nullable private PlatformPlugin platformPlugin;
@VisibleForTesting @Nullable OnPreDrawListener activePreDrawListener;
private boolean isFlutterEngineFromHost;
private boolean isFlutterUiDisplayed;

@NonNull
private final FlutterUiDisplayListener flutterUiDisplayListener =
new FlutterUiDisplayListener() {
@Override
public void onFlutterUiDisplayed() {
host.onFlutterUiDisplayed();
isFlutterUiDisplayed = true;
}

@Override
public void onFlutterUiNoLongerDisplayed() {
host.onFlutterUiNoLongerDisplayed();
isFlutterUiDisplayed = false;
}
};

Expand Down Expand Up @@ -254,6 +259,16 @@ void onAttach(@NonNull Context context) {
*
* <p>{@code inflater} and {@code container} may be null when invoked from an {@code Activity}.
*
* <p>{@code shouldDelayFirstAndroidViewDraw} determines whether to set up an {@link
* android.view.ViewTreeObserver.OnPreDrawListener}, which will defer the current drawing pass
* till after the Flutter UI has been displayed. This results in more accurate timings reported
* with Android tools, such as "Displayed" timing printed with `am start`.
*
* <p>Note that it should only be set to true when {@code Host#getRenderMode()} is {@code
* RenderMode.surface}. This parameter is also ignored, disabling the delay should the legacy
* {@code Host#provideSplashScreen()} be non-null. See <a
* href="https://flutter.dev/go/android-splash-migration">Android Splash Migration</a>.
*
* <p>This method:
*
* <ol>
Expand All @@ -269,7 +284,8 @@ View onCreateView(
LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState,
int flutterViewId) {
int flutterViewId,
boolean shouldDelayFirstAndroidViewDraw) {
Log.v(TAG, "Creating FlutterView.");
ensureAlive();

Expand Down Expand Up @@ -298,15 +314,28 @@ View onCreateView(
// Add listener to be notified when Flutter renders its first frame.
flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);

flutterSplashView = new FlutterSplashView(host.getContext());
flutterSplashView.setId(ViewUtils.generateViewId(486947586));
flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());

Log.v(TAG, "Attaching FlutterEngine to FlutterView.");
flutterView.attachToFlutterEngine(flutterEngine);
flutterView.setId(flutterViewId);

return flutterSplashView;
SplashScreen splashScreen = host.provideSplashScreen();

if (splashScreen != null) {
Log.w(
TAG,
"A splash screen was provided to Flutter, but this is deprecated. See"
+ " flutter.dev/go/android-splash-migration for migration steps.");
FlutterSplashView flutterSplashView = new FlutterSplashView(host.getContext());
flutterSplashView.setId(ViewUtils.generateViewId(FLUTTER_SPLASH_VIEW_FALLBACK_ID));
flutterSplashView.displayFlutterViewWithSplash(flutterView, splashScreen);

return flutterSplashView;
}

if (shouldDelayFirstAndroidViewDraw) {
delayFirstAndroidViewDraw(flutterView);
}
return flutterView;
}

void onRestoreInstanceState(@Nullable Bundle bundle) {
Expand Down Expand Up @@ -415,6 +444,38 @@ private String maybeGetInitialRouteFromIntent(Intent intent) {
return null;
}

/**
* Delays the first drawing of the {@code flutterView} until the Flutter first has been displayed.
*/
private void delayFirstAndroidViewDraw(FlutterView flutterView) {
if (host.getRenderMode() != RenderMode.surface) {
// Using a TextureView will cause a deadlock, where the underlying SurfaceTexture is never
// available since it will wait for drawing to be completed first. At the same time, the
// preDraw listener keeps returning false since the Flutter Engine waits for the
// SurfaceTexture to be available.
throw new IllegalArgumentException(
"Cannot delay the first Android view draw when the render mode is not set to"
+ " `RenderMode.surface`.");
}

if (activePreDrawListener != null) {
flutterView.getViewTreeObserver().removeOnPreDrawListener(activePreDrawListener);
}

activePreDrawListener =
new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (isFlutterUiDisplayed && activePreDrawListener != null) {
flutterView.getViewTreeObserver().removeOnPreDrawListener(this);
activePreDrawListener = null;
}
return isFlutterUiDisplayed;
}
};
flutterView.getViewTreeObserver().addOnPreDrawListener(activePreDrawListener);
}

/**
* Invoke this from {@code Activity#onResume()} or {@code Fragment#onResume()}.
*
Expand Down Expand Up @@ -496,6 +557,10 @@ void onDestroyView() {
Log.v(TAG, "onDestroyView()");
ensureAlive();

if (activePreDrawListener != null) {
flutterView.getViewTreeObserver().removeOnPreDrawListener(activePreDrawListener);
activePreDrawListener = null;
}
flutterView.detachFromFlutterEngine();
flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ public class FlutterFragment extends Fragment
protected static final String ARG_HANDLE_DEEPLINKING = "handle_deeplinking";
/** Path to Flutter's Dart code. */
protected static final String ARG_APP_BUNDLE_PATH = "app_bundle_path";
/** Whether to delay the Android drawing pass till after the Flutter UI has been displayed. */
protected static final String ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW =
"should_delay_first_android_view_draw";

/** Flutter shell arguments. */
protected static final String ARG_FLUTTER_INITIALIZATION_ARGS = "initialization_args";
/**
Expand Down Expand Up @@ -229,6 +233,7 @@ public static class NewEngineFragmentBuilder {
private TransparencyMode transparencyMode = TransparencyMode.transparent;
private boolean shouldAttachEngineToActivity = true;
private boolean shouldAutomaticallyHandleOnBackPressed = false;
private boolean shouldDelayFirstAndroidViewDraw = false;

/**
* Constructs a {@code NewEngineFragmentBuilder} that is configured to construct an instance of
Expand Down Expand Up @@ -382,6 +387,18 @@ public NewEngineFragmentBuilder shouldAutomaticallyHandleOnBackPressed(
return this;
}

/**
* Whether to delay the Android drawing pass till after the Flutter UI has been displayed.
*
* <p>See {#link FlutterActivityAndFragmentDelegate#onCreateView} for more details.
*/
@NonNull
public NewEngineFragmentBuilder shouldDelayFirstAndroidViewDraw(
boolean shouldDelayFirstAndroidViewDraw) {
this.shouldDelayFirstAndroidViewDraw = shouldDelayFirstAndroidViewDraw;
return this;
}

/**
* Creates a {@link Bundle} of arguments that are assigned to the new {@code FlutterFragment}.
*
Expand Down Expand Up @@ -410,6 +427,7 @@ protected Bundle createArgs() {
args.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, true);
args.putBoolean(
ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, shouldAutomaticallyHandleOnBackPressed);
args.putBoolean(ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW, shouldDelayFirstAndroidViewDraw);
return args;
}

Expand Down Expand Up @@ -496,6 +514,7 @@ public static class CachedEngineFragmentBuilder {
private TransparencyMode transparencyMode = TransparencyMode.transparent;
private boolean shouldAttachEngineToActivity = true;
private boolean shouldAutomaticallyHandleOnBackPressed = false;
private boolean shouldDelayFirstAndroidViewDraw = false;

private CachedEngineFragmentBuilder(@NonNull String engineId) {
this(FlutterFragment.class, engineId);
Expand Down Expand Up @@ -621,6 +640,18 @@ public CachedEngineFragmentBuilder shouldAutomaticallyHandleOnBackPressed(
return this;
}

/**
* Whether to delay the Android drawing pass till after the Flutter UI has been displayed.
*
* <p>See {#link FlutterActivityAndFragmentDelegate#onCreateView} for more details.
*/
@NonNull
public CachedEngineFragmentBuilder shouldDelayFirstAndroidViewDraw(
@NonNull boolean shouldDelayFirstAndroidViewDraw) {
this.shouldDelayFirstAndroidViewDraw = shouldDelayFirstAndroidViewDraw;
return this;
}

/**
* Creates a {@link Bundle} of arguments that are assigned to the new {@code FlutterFragment}.
*
Expand All @@ -642,6 +673,7 @@ protected Bundle createArgs() {
args.putBoolean(ARG_SHOULD_ATTACH_ENGINE_TO_ACTIVITY, shouldAttachEngineToActivity);
args.putBoolean(
ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, shouldAutomaticallyHandleOnBackPressed);
args.putBoolean(ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW, shouldDelayFirstAndroidViewDraw);
return args;
}

Expand Down Expand Up @@ -727,7 +759,11 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return delegate.onCreateView(
inflater, container, savedInstanceState, /*flutterViewId=*/ FLUTTER_VIEW_ID);
inflater,
container,
savedInstanceState,
/*flutterViewId=*/ FLUTTER_VIEW_ID,
shouldDelayFirstAndroidViewDraw());
}

@Override
Expand Down Expand Up @@ -1267,6 +1303,12 @@ public boolean popSystemNavigator() {
return false;
}

@VisibleForTesting
@NonNull
boolean shouldDelayFirstAndroidViewDraw() {
return getArguments().getBoolean(ARG_SHOULD_DELAY_FIRST_ANDROID_VIEW_DRAW);
}

private boolean stillAttachedForEvent(String event) {
if (delegate == null) {
Log.w(TAG, "FlutterFragment " + hashCode() + " " + event + " called after release.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ protected FlutterFragment createFlutterFragment() {
backgroundMode == BackgroundMode.opaque
? TransparencyMode.opaque
: TransparencyMode.transparent;
final boolean shouldDelayFirstAndroidViewDraw = renderMode == RenderMode.surface;

if (getCachedEngineId() != null) {
Log.v(
Expand All @@ -447,6 +448,7 @@ protected FlutterFragment createFlutterFragment() {
.handleDeeplinking(shouldHandleDeeplinking())
.shouldAttachEngineToActivity(shouldAttachEngineToActivity())
.destroyEngineWithFragment(shouldDestroyEngineWithHost())
.shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
.build();
} else {
Log.v(
Expand Down Expand Up @@ -476,6 +478,7 @@ protected FlutterFragment createFlutterFragment() {
.renderMode(renderMode)
.transparencyMode(transparencyMode)
.shouldAttachEngineToActivity(shouldAttachEngineToActivity())
.shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
.build();
}
}
Expand Down
Loading