-
Notifications
You must be signed in to change notification settings - Fork 6k
null check added to avoid NPE while calling FlutterView.detachFromFlutterEngine() #41082
Conversation
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!). If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
Thank you for such a well documented and researched patch! We are normally pretty strict about not accepting pull requests without tests. That said, I am not sure what of our testing infrastructure should be used to recreate the reproduction conditions you lay out. I have an open question out in hackers-engine on discord. If we decide it is ok to take this pr without a test we will need approval from Hixie. See https://github.com/flutter/flutter/wiki/Tree-hygiene#tests for more information on our policies. |
shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
Show resolved
Hide resolved
…ivityAndFragmentDelegate.java Co-authored-by: Reid Baker <[email protected]>
I checked your Discord message and looks like there is one 👍 and not any replies yet. I guess I just need to wait for people to get some free time to look at the PR then right? |
Break comment into 2 lines.
flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener); | ||
|
||
// flutterView can be null in instances where a delegate.onDestroyView is called without | ||
// onCreateView being called. See https://github.com/flutter/engine/pull/41082 for more detail. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: in general the code should stand in isolation; there's no guarantee that the issue database will last as long as the code comments. so if there is more useful data to include it would be good to just describe it all here rather than linking to the issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree which is why there is a description before the link. That said this link has way more information than I think could (should?) fit in a comment include screenshots and statements about androids observed behavior. All in if someone wanted to understand why how onDestroyView can be called without onCreateView then the link is the best place to learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general if we think more documentation is needed, then I would recommend putting it in a README.md, or, at the extreme, in a wiki page. We shouldn't consider PR descriptions to be canonical documentation sources.
is calling onDestroyView without calling onCreateView considered in-spec? |
(reid answered "no" on discord) given that as far as we can tell the OS is giving us out-of-spec messages, which is not something we can really repro: |
The next flutter contributor to approve please add auto-submit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks for the investigation and patch!
…achFromFlutterEngine() (flutter/engine#41082)
…125001) flutter/engine@b2d0738...20034a8 2023-04-17 [email protected] Reland "Migrate mac_host_engine to engine v2 builds." (flutter/engine#41279) 2023-04-17 [email protected] [rotation_distortion] Use "delayed swap" solution to reduce rotation distortion (flutter/engine#40730) 2023-04-17 [email protected] null check added to avoid NPE while calling FlutterView.detachFromFlutterEngine() (flutter/engine#41082) 2023-04-17 [email protected] [Impeller] Make `DoMakeRasterSnapshot` output timeline event. (flutter/engine#41197) 2023-04-17 [email protected] [Impeller] Remove ContentContextOptions declarations from AnonymousContents (flutter/engine#41256) 2023-04-17 [email protected] Roll Dart SDK from 786a70d8ef6b to a335e6724332 (1 revision) (flutter/engine#41278) 2023-04-17 [email protected] Roll Fuchsia Linux SDK from atix5Ek_OOxH-uoPA... to Cy5LG4U2InaFLkJGz... (flutter/engine#41275) Also rolling transitive DEPS: fuchsia/sdk/core/linux-amd64 from atix5Ek_OOxH to Cy5LG4U2InaF If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-engine-flutter-autoroll Please CC [email protected],[email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bastionkid - would you consider adding a test, like the ones in https://github.com/flutter/engine/blob/eadb540b8d1b3cac7458c1b7bba8761a96cdb2a5/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java for this?
This code should be pretty easily testable if I'm reading it right.
Issue fixed by this PR: flutter/flutter#110138
This PR fixes an issue which is causing app crash when User tries to navigate to a new instance FlutterFragment (whose old instance is already present in the fragment backstack) in an Activity (which was restored to saved state after being killed in background due to memory pressure).
Detailed case to reproduce the crash and identify its' root cause:
Setup: Let's say we've an Activity1 which has a bottom nav bar with 3 tabs. Each of this 3 tabs are FlutterFragment i.e. Fragment1, Fragment2 & Fragment3 and all of them are using separate FlutterEngine but all of them will be cached. e.g. Multiple instances of Fragment1 will be going to use same cached FlutterEngine1.
FlutterFragment.onAttach() -> FlutterActivityAndFragmentDelegate.onAttach(). There is a one-to-one mapping between FlutterFragment <-> FlutterActivityAndFragmentDelegate



FlutterActivityAndFragmentDelegate.onAttach() -> FlutterEngineConnectionRegistry.attachToActivity(). There is a one-to-one mapping between FlutterEngine <-> FlutterEngineConnectionRegistry. NOTE: THIS IS VERY IMPORTANT POINT TO KEEP IN MIND TO UNDERSTAND THE ROOT CAAUSE OF THIS CRASH.
Since all the 3 FlutterFragment were just instantiated on activity restore exclusiveActivity will be null and exclusiveActivity will be assigned the host FlutterActivityAndFragmentDelegate.
6. Then FlutterFragment.onCreateView() will be called only for Fragment3 and it will be visible without an issue as its' state gets restored properly.
7. Then if User tries to navigate to Fragment2 via instantiating new instance of it (this means that now there will be two instances of Fragment2 in the backstack), then there will be crash as it'll go through following function calls.
FlutterFragment.onAttach() -> FlutterActivityAndFragmentDelegate.onAttach().






FlutterActivityAndFragmentDelegate.onAttach() -> FlutterEngineConnectionRegistry.attachToActivity().
THIS IS WHERE THE CRASH STARTS. Since this is the second instance of Fragment2 and both instances are going to use the same cached FlutterEngine and hence same FlutterEngineConnectionRegistry, this time around exclusiveActivity will be non-null as it was assigned during step 5. And since exclusiveActivity will be be non null it'll try to detach from the old ExclusiveAppComponent via calling exclusiveActivity.detachFromFlutterEngine().
FlutterActivityAndFragmentDelegate.detachFromFlutterEngine() -> FlutterFragment.detachFromFlutterEngine()
FlutterFragment.detachFromFlutterEngine() -> FlutterActivityAndFragmentDelegate.onDestroyView(). This ideally should not be called if the hosts' FlutterFragments.onCreateView() was not called in the first place. Also since the previous author has added // Redundant calls are ok. comment, I'm guessing that this is just a fallback cleanup.
THIS IS WHERE THE CRASH HAPPENS. FlutterActivityAndFragmentDelegate.onDestroyView() -> FlutterView.detachFromFlutterEngine(). Since the lifecycle of older instance of this FlutterFragment2 was capped at onAttach(), it's FlutterView property will be null and calling FlutterView.detachFromFlutterEngine() will throw NPE.
Pre-launch Checklist
///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.