Skip to content

[Android] Calling Android.Views.View.Post(Action) on a View that is detached and not then reattached causes the Action and RunnableImplementor to leak. #18757

Closed
dotnet/android
#8900
@sjordanGSS

Description

Description

Context: #15769

When calling Android.Views.View.Post(Action) the passed Action will be wrapped in a RunnableImplementor and passed to Android.Views.View.Post(IRunnable). The constructor for RunnableImplementor adds itself to a static dictionary (presumably to retain a reference to it within .NET), from which it is removed when IRunnable.Run() is called. If Run is never called, the RunnableImplementor is never removed from this dictionary and will persist for the lifetime of the app.
It is possible to enter this scenario when calling Android.Views.View.Post(Action) on a view that is not attached to a window (i.e. isn't part of the view hierarchy), as Android.Views.View.Post(IRunnable) will add the RunnableImplementor to a queue instead of passing it to the Handler: the contents of this queue are passed to the Handler when the View is attached to the view hierarchy, however if it is destroyed instead the queue is deleted and IRunnable.Run() is never called. When this happens, the RunnableImplementor, the Action and anything referenced by the Action will leak.

There is a method View.RemoveCallbacks(Action) to remove the RunnableImplementor associated with the passed Action from the dictionary, however this requires the end user to keep track of posted Actions themselves and prohibits the use of code like

view.Post(() => 
{
    // do something on the UI thread
});

as a reference to the Action must be retained. An Action passed in the above way would be impossible to clean up later.

This issue is especially problematic in views like CollectionView, where child views are constantly being created, detached and recycled as the view is scrolled. If these children use Android.Views.View.Post(Action) without considering their current attach status, there is a risk of the Action leaking.

Steps to Reproduce

I'm working on a proper repro which I will update this post with, but in the mean time here are some steps to reproduce this issue:

  • Instantiate an Android.Views.View or derivative, but do not add it as a child to any other view
  • Call View.Post(Action)
  • Dispose the View, remove any references to it and run the garbage collector
  • The RunnableImplementor, Action and anything referenced within the Action will leak.
  • Calling View.Post(Action) repeatedly (e.g. in a loop, on a timer etc) will eventually cause the application to crash with the message global reference table overflow

Link to public reproduction project repository

No response

Version with bug

7.0.101

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

Tested on Android 7.1 and 11

Did you find any workaround?

Avoid using View.Post if there is the possibility that the View might not be reattached. In my code, I did this by instantiating another Handler in the derived view and Posting to this instead.

Relevant log output

No response

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions