Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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);
    }

  }
}
like image 676
QWill Avatar asked Mar 04 '15 19:03

QWill


1 Answers

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.
}
like image 123
matthewrdev Avatar answered Nov 15 '22 10:11

matthewrdev