Skip to content

Do not render any frames when just initializing Bindings#39535

Merged
goderbauer merged 5 commits intoflutter:masterfrom
goderbauer:fixsplash
Sep 6, 2019
Merged

Do not render any frames when just initializing Bindings#39535
goderbauer merged 5 commits intoflutter:masterfrom
goderbauer:fixsplash

Conversation

@goderbauer
Copy link
Member

@goderbauer goderbauer commented Aug 30, 2019

Description

Prior to this change, initializing the WidgetsFlutterBinding (e.g. via WidgetsFlutterBinding.ensureInitialized) would register the frame callbacks onBeginFrame and onDrawFrame with the engine (this allowed to request the engine to render a frame whenever it wanted) and we would actively ask the engine to request the next frame via Window.scheduleFrame. In certain situations this caused the engine to request a frame (by calling onBeginFrame) when the framework wasn't ready to produce a meaningful frame (e. i. before the root widget was attached via a call to runApp). This would cause the framework to produce an empty frame, which in turn caused the engine to take down any splash screen and show a black empty screen.

With this change, initializing the bindings does not register the frame callbacks with the engine so the engine has no chance of requesting a frame from the framework. Instead, the frame callbacks are lazily registered when the first frame is scheduled by the framework via scheduleFrame. Additionally, initializing the bindings will no longer schedule a frame. The first frame gets scheduled when the root widget is attached and the framework is therefore ready to produce the first meaningful frame (or you can schedule a frame manually by calling scheduleFrame whenever you want).

Related Issues

Fixes #39494.

Tests

I added the following tests:

  • tests to ensure that the binding neither register any callbacks nor schedules a frame when instantiated.

Checklist

Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]). This will ensure a smooth and quick review process.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require Flutter developers to manually update their apps to accommodate your change?

@fluttergithubbot fluttergithubbot added a: tests "flutter test", flutter_test, or one of our tests framework flutter/packages/flutter repository. See also f: labels. labels Aug 30, 2019
@goderbauer goderbauer marked this pull request as ready for review August 30, 2019 17:13
@goderbauer goderbauer requested a review from Hixie August 30, 2019 17:42
@Piinks Piinks added the c: API break Backwards-incompatible API changes label Aug 30, 2019
@dnfield
Copy link
Contributor

dnfield commented Aug 30, 2019

Could we make this less breaking by instead making a different ensureInitialized method that people who just want method channels could call?

My thinking is that here we're breaking some pretty old and established API, whereas the ensureInitialized logic was more recently changed for people who are more likely to be able to respond to breaks. I think it would involve similar changes to what you've done here, but perhaps on ServicesBinding instead of WidgetsBinding.

@dnfield
Copy link
Contributor

dnfield commented Aug 30, 2019

IOW - it seems strange to me to ensure that the widgets binding is initialized but to have to take an extra step to actually render widgets. Whereas I would expect that I could initialize the service bindings without necessarily rendering.

@goderbauer
Copy link
Member Author

goderbauer commented Aug 30, 2019

Adding a new method seems more breaking to me as that will require people to change the API they are calling. This change as-is does not require any changes from most people who are using Flutter in the intended way.

After initializing the bindings you always had to take an extra step (call runApp to attach your root widget) to actually render widgets. This PR is not changing this. This PR is just making sure that no frame gets rendered until this step has been taken.

debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
return true;
}());
if (window.onBeginFrame == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the omission of onDrawFrame on this line hurts

bool frameScheduled = false;
@override
void scheduleFrame() {
if (ui.window.onBeginFrame == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the code duplication here hurts...

renderView.child = offscreen.root;
renderView.scheduleInitialFrame();
renderView.prepareInitialFrame();
renderView.owner.requestVisualUpdate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just pipelineOwner.requestVisualUpdate?

renderView.child = offscreen.root;
renderView.scheduleInitialFrame();
renderView.prepareInitialFrame();
renderView.owner.requestVisualUpdate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

renderView.attach(pipelineOwner);
renderView.scheduleInitialFrame();
renderView.prepareInitialFrame();
renderView.owner.requestVisualUpdate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

window.onBeginFrame = null;
window.onDrawFrame = null;
window.onBeginFrame = (Duration _) {};
window.onDrawFrame = () {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should track if we've registered our callbacks separately from whether they're null, to avoid this kind of breakage...
Also, we could override registerFrameCallbacks here to enable the test harness and leave window entirely alone, thus avoiding this issue.

@goderbauer
Copy link
Member Author

Comments addressed. PTAL @Hixie

Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks good, and I don't think it will interfere with my pr #39079
It will probably be good to run through internal test suite to make sure nothing break

debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
return true;
}());
ensureFrameCallbacksRegistered();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels a little bit hacky to me that we prevent the engine to achieve frame by not attaching listener, but i can't think of a better way, either.


@override
void ensureFrameCallbacksRegistered() {
// Leave Window alone, do nothing.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe assert that window.onBeginFrame and window.onDrawFrame are null...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@Hixie
Copy link
Contributor

Hixie commented Sep 4, 2019

LGTM

@shinyford
Copy link

Hi - I'm currently on Flutter 1.12.13+hotfix.5 stable, and I'm still seeing a frame rendered when WidgetsFlutterBinding.ensureInitialized is called early.

Is this expected? Any info gratefully received. Cheers!

@dnfield
Copy link
Contributor

dnfield commented Jan 7, 2020

@shinyford see #39494

@shinyford
Copy link

@shinyford see #39494

Thanks - yes, I found that. The suggested fix (#39494 (comment)) works for me; but I'll be more comfortable when it's properly part of the Flutter codebase.

@hb0nes
Copy link

hb0nes commented Jan 12, 2020

I have a massive black screen during initialization of the application. Any update on this?

@goderbauer
Copy link
Member Author

@hb0nes I recommend upvoting the issue #39494 and adding details if you have them (e.g. what device).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

a: tests "flutter test", flutter_test, or one of our tests c: API break Backwards-incompatible API changes framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WidgetsFlutterBinding.ensureInitialized() takes down splash screen too early.

9 participants