Skip to content

Commit

Permalink
Use preDraw for the Android embedding (flutter#27645)
Browse files Browse the repository at this point in the history
  • Loading branch information
jiahaog authored Jul 29, 2021
1 parent 74121a4 commit ff770aa
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 25 deletions.
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 @@ -425,6 +425,7 @@ protected FlutterFragment createFlutterFragment() {
backgroundMode == BackgroundMode.opaque
? TransparencyMode.opaque
: TransparencyMode.transparent;
final boolean shouldDelayFirstAndroidViewDraw = renderMode == RenderMode.surface;

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

0 comments on commit ff770aa

Please sign in to comment.