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

Conversation

@xster
Copy link
Member

@xster xster commented Sep 18, 2020

Fixes flutter/flutter#66192.

The fix is pretty simple. It just lets the activity and fragment detach themselves when another activity or fragment is attaching to the engine. There's some added ceremony since it's such a central piece that I wanted to make it a bit harder to drift accidentally in the future.

Alternatives considered:

  • cl/332356164. Statics are leaky.
  • Creating an object in the FlutterActivityAndFragmentDelegate that tracks other FlutterActivityAndFragmentDelegate. This is conceptually appropriate but we already have a path that's meant to be rigorous in doing exactly that with the control surface mechanism. I wanted to avoid creating yet another parallel bookkeeping track and make a 4th activity<->engine linkage (Non exclusive UI components attached to the FlutterEngine causes event crosstalk flutter#66192) rather than reducing it down to 2.
  • Just disconnecting directly in the FlutterActivityAndFragmentDelegate. It's a package private implementation detail. Letting activity/fragment being able to somehow customize or be signaled about an eviction seems useful.
  • Extracting the control surface bookkeeping out of plugins related code. This is probably cleaner. Though that abstraction hasn't been super complete. Considering the control surface implementation FlutterEnginePluginRegistry is a sibling to FlutterEngine. The readability benefit of less indirections seems ok.

Deprecation doc flutter/website#4640

@xster xster changed the title Let FlutterActivity subclasses detach from engine Enforce exclusivity for activity and fragments attached to the FlutterEngine Sep 20, 2020
super.onDestroy();
delegate.onDestroyView();
delegate.onDetach();
Log.e("meh", "Activity " + this + " onDestroy");
Copy link
Contributor

Choose a reason for hiding this comment

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

Debugging log.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, thanks!

Log.v(TAG, "FlutterActivity " + this + " onDestroy called after release.");
}
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

We might need to check the delegate nullness in lifecycle methods below as well (onActivityResult and such). At the very least, let's confirm with an Android expert (perhaps clm) that those methods are never called after onPause().

Copy link
Member Author

Choose a reason for hiding this comment

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

Ya, sensible. Ping'ed him on the bug.

"FlutterActivityAndFragmentDelegate's getAppComponent should only "
+ "be queried after onAttach, when the host's activity should always be non-null");
}
return host.getActivity();
Copy link
Contributor

Choose a reason for hiding this comment

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

Just return activity.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you just mean don't do assertions?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, I mean you are doing final activity = host.getActivity() above. Why not return the local var?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah thanks

// -------- Start ActivityControlSurface -------
private boolean isAttachedToActivity() {
return activity != null;
return activity != null || exclusiveActivity != null;
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the difference between activity and exclusiveActivity? As far as plugins are concerned, isn't there always one activity? It would be dangerous to have different plugins be attached to different activity instances.

Copy link
Member Author

@xster xster Sep 20, 2020

Choose a reason for hiding this comment

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

There is only one. This is just extra fluff since I didn't want to make the deprecation too hard breaking.

// If we were already attached to an Android component, detach from it.
detachFromAndroidComponent();
if (this.exclusiveActivity != null) {
this.exclusiveActivity.detachFromFlutterEngine();
Copy link
Contributor

Choose a reason for hiding this comment

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

It is odd that activity/engine relationship is being managed in plugin registry..

Copy link
Member Author

Choose a reason for hiding this comment

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

Ya, that's the last piece I noted in the PR summary above. I think the place to put it is right (e.g. consolidating the place where we attach/detach, (e.g. if a person makes a custom activity, there should be burden to attach it to the engine in just one API instead of 3 or 4)).

The name is not right. The easiest solution is to just name this FlutterEngineConnectionRegistry since this file here is sibling here with the engine rather than in the plugins package. For super maintenance, we can also split this into 2 files to make it shorter than 800 LOC. But I don't think we need to do that right now.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've renamed the package private class FlutterEngineConnectionRegistry to reflect its role.

* avoid situations where multiple activities are driving the FlutterEngine simultaneously.
* See https://github.com/flutter/flutter/issues/66192.
*/
@Deprecated
Copy link
Contributor

Choose a reason for hiding this comment

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

I am surprised this is public API?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't have any proof that anyone's using it but it is public API. The theoretical usage I think would be fore someone who make a custom activity that holds a FlutterView. This public interface is the APIs that lets that FlutterView's owner tell that FlutterView's FlutterEngine's plugins when an activity is available / unavailable.

That's why there's that activity/exclusiveActivity thing above. It's just transitional so we have a not-too-rough breaking change.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's fair.

So, there will be an announcement and a deprecation phase so we can clean it up?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, writing website PR at the moment.

protected void onNewIntent(@NonNull Intent intent) {
// TODO(mattcarroll): change G3 lint rule that forces us to call super
super.onNewIntent(intent);
ensureAlive();
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is needed since these methods would not normally be called by user code. They are entirely controlled by Android lifecycle itself and Android guarantees not calling these methods once activity is destroyed.

ensureAlive type thing made some sense in delegate because delegate could be used in many objects (including fragment and activity) it had to assert its own internal state.

Copy link
Member Author

Choose a reason for hiding this comment

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

makes sense. Thanks

Copy link
Contributor

@mehmetf mehmetf left a comment

Choose a reason for hiding this comment

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

LGTM

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Non exclusive UI components attached to the FlutterEngine causes event crosstalk

3 participants