Xamarin Android Finalizer not getting called when leaving the activity to go to another Activity
Asked Answered
S

1

8

The Finalizer is never called after leaving the activity. Does that mean the activity is still alive even though I moved on to the next activity.

namespace XamarinTest {
[Activity(Label = "XamarinTest", Icon = "@drawable/icon")]
public class MainActivity : Activity {
    private int count = 1;

    private TextView density;

    protected override void OnCreate(Bundle bundle) {
        base.OnCreate(bundle);
        // Set our view from the "main" layout resource
        SetContentView(Resource.Layout.ScreenData);
        density = FindViewById<TextView>(Resource.Id.Density);

        var pendingInent = new Intent();
        pendingInent.SetFlags(ActivityFlags.ClearTop);
        pendingInent.SetClass(this, typeof(TestActivity));
        StartActivity(pendingInent);
        Finish();
    }


    ~MainActivity() {

        Console.WriteLine("Finalizer called");
    }

  protected override void Dispose(bool disposing){
        if (disposing) {
            density.Dispose();
            density = null;
        }
        base.Dispose(disposing);
    }

  }
}
Selfpronouncing answered 4/3, 2015 at 19:22 Comment(0)
S
29

This is actually remarkably complicated; the short answer, regarding the activity still being alive, is yes and no. Providing you have cleaned up the resources for your Activity correctly, your activity will be cleaned up (eventually) by the garbage collector.

Regarding cleanup, it's import to know that Xamarin discourages (slide 44 onwards) using finalizers. Here's why:

  • They are not guaranteed to run within any deadline.
  • They don't run in a specific sequence.
  • They make objects live longer.
  • The GC doesn't know about unmanaged resources.

Therefore, using a finalizer to perform cleanup is the wrong way of doing things... If you want to ensure that MainActivity is destroyed, manually dispose the Activity in it's OnDestroy callback:

protected override void OnDestroy ()
{
    base.OnDestroy ();
    this.Dispose (); // Sever java binding.
}

This will cause Mono to break the peer object connection and destroy the activity during the next garbage collection cycle (GC.Collect(GC.MaxGeneration)). From the docs:

To shorten object lifetime, Java.Lang.Object.Dispose() should be invoked. This will manually "sever" the connection on the object between the two VMs by freeing the global reference, thus allowing the objects to be collected faster.

Note the call order there, this.Dispose() must be called after any code that invokes back into Android land. Why? All the connections between Java and .NET are now broken to allow Android to reclaim resources so any code that uses Android-land objects (Fragment, Activity, Adapter) will fail.

Now, onto some debugging techniques for Activity leaks. To verify that your activity is being cleaned up, add the following code to the OnCreate method of your apps entry Activity:

var vmPolicy = new StrictMode.VmPolicy.Builder ();
StrictMode.SetVmPolicy (vmPolicy.DetectActivityLeaks().PenaltyLog().Build ());

This enables StrictMode, a useful debugging tool that happily informs you when you've leaked resources. When one of your apps activities isn't released correctly, it will dump something like this to the output stream:

[StrictMode] class activitydispose.LeakyActivity; instances=2; limit=1
[StrictMode] android.os.StrictMode$InstanceCountViolation: class activitydispose.LeakyActivity; instances=2; limit=1
[StrictMode]    at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)

Combining this with the Dispose() call, you can check that activities are being released. Here is how you'd typically an Activity and its resources in Xamarin.Android:

protected override void Dispose (bool disposing)
{
    // TODO: Dispose logic here.
    base.Dispose (disposing);
    GC.Collect(GC.MaxGeneration); // Will force cleanup but not recommended.
}

protected override void OnDestroy ()
{
    if (density != null) { // Release Java objects (buttons, adapters etc) here
        density.Dispose ();
        density = null;
    }
    base.OnDestroy ();
    this.Dispose (); // Sever java binding.
}
Stumpf answered 5/3, 2015 at 1:46 Comment(3)
"This is actually remarkably complicated" <------any answer that starts with this is going to be a good one.Heptachord
This info is very useful. StrictMode rocksHangeron
The StrictMode seem to be working only with Activities, is there anyway that we can do it for Fragments as well? Because it does like StrictMode does not display any leaks information regarding Fragments.Incorporeity

© 2022 - 2024 — McMap. All rights reserved.