Memory FYI: Some Problems and Solutions

All,

I was having a problem running up my GREF count and Memory Use, with my Xamarin.Android app, and I thought I would share with you what I found was the cause and what worked well as a solution.

EditText

When inserting text into an EditText, Xamarin.Android creates two GREFs for each character entered:

  • A Point Object
  • A Paint Object

The UI eventually collates the text into a single point/paint object pair, but our automated UI test program (Calabash) was putting text into the emulator faster than the emulator could keep up. We didn't really have a solution for this, but hopefully it helps someone figure out why the emulator is crashing when they're trying to type.

AlertDialog

In our application we have the user enter information into a digital form, and this involved creating lots of AlertDialogs with Lists for them to select items from. Each AlertDialog created dozens if not hundreds of TextView objects (and their associated helper objects). These objects (at least on the Emulator) were not being released even though they were being created by the OS. We found that putting the following (during a button press) greatly assisted in cleaning out the memory.

((Dialog)sender).Dismiss();
GC.Collect();

ListViews

Similar to the AlertDialog issue above, we found that using ListViews extensively throughout our application dramatically increased memory usage (We monitored GetView method, and it was calling for 3-4x the number of Views we were actually displaying by passing in null for ConvertView). As our "rows" used several TextViews, EditTexts, and some Spinners, this ate up a ton of GREFs. Our "out of GREF" messages showed hundreds (500+) of TextViews when only about a dozen were displayed on-screen. We moved our application to a LinearLayout that we managed ourselves through an Adapter & DataObserver.

This didn't completely negate our memory issues, however, and like the AlertDialog adding the following code (from JonP) greatly assisted in cleaning out memory when it wasn't in use anymore.

/// <summary>
/// Cleans up the memory
/// http://stackoverflow.com/questions/8077628/monodroid-free-memory-when-activity-finished
/// </summary>
protected override void OnDestroy ()
{
    base.OnDestroy ();
    GC.Collect ();
}

Summary

So, with the above changes, we went from being only able to complete 1 or 2 Digital Forms on the Emulator (using Calabash) to having it complete a stress test of 999 Digital Forms without error.

Hope this Helps!!

-Stephen Furlani

Posts

  • BryanCostanichBryanCostanich USMember, Xamarin Team Xamurai

    all dialogs, must be explicitly dismissed, or they will leak. in fact, you should dismiss them in OnPause() even if they still need to be displayed in OnResume().

    Dialogs + Alerts

    The pattern then for dialogs/alerts is to keep a class level reference of whether or not you're showing something:

    protected bool showingAlert;
    

    of course if you have more complex UI state, you could persist something more sophisticated.

    Then, in OnCreate(), you should rehydrate your state info from the bundle (this would be in the case of activity destruction due to rotation (Configuration change) or other:

    protected override void OnCreate (Bundle bundle)
    {
        base.OnCreate (bundle);
        ...
    
        // if we've stored the showingProgress bool, set it
        if(bundle != null)
            this.showingAlert = bundle.GetBoolean("showingAlert");
    }
    

    In OnResume(), you can display it depending on state:

    protected override void OnResume ()
    {
        base.OnResume ();
    
        ...
    
        // if we're supposed to be showing the dialog, show it. this could 
        // happen on an application pause
        if(this.showingAlert) {
            Log.Debug (logTag, "MainActivity. showingAlert is true");
            this.ShowAlertDialog();
        }
    }
    

    Inn OnPause(), you should then dismiss():

    protected override void OnPause ()
    {
        base.OnPause ();
    
        ...
    
        // if we don't dismiss this, and it's shown, it'll get leaked because it's 
        // actually tied to a window context, rather than the Activity.
        if (alert != null)
            alert.Dismiss();
    }
    

    Finally, you can persist your UI state in OnSaveInstanceState:

    protected override void OnSaveInstanceState (Bundle outState)
    {
        base.OnSaveInstanceState (outState);
        // save whether or not the alert is shown, so in the case that we're getting rotated 
        // (or other configuration change) and the activity is getting destroyed and recreated,
        // we can make it still shows
        outState.PutBoolean("showingAlert", this.showingAlert);
    }
    

    External Events

    It's worth noting that you should also be careful when subscribing to external events, e.g.:

    App.Current.Updated += updateHandler;
    

    As the external object will keep a reference to your activity, preventing Xamarin.Android from destroying it and reclaiming the memory. However the Activity on the Dalvik side of the house will be destroyed, so you'll keep a ghost reference around.

    So to avoid this, keep a reference at the class level of your handlers, e.g.:

    public class MyActivity : Activity
    {
        // declarations
        protected EventHandler<UpdatingEventArgs> updateHandler;
            ...
    

    And then, in OnResume, wire them up as appropriate:

    protected override void OnResume ()
    {
        base.OnResume ();
        updateHandler = (object s, UpdatingEventArgs args) => {
            Console.WriteLine ("Updated event: " + args.Message);
            this.RunOnUiThread (() => {
                this.updateStatusText.Text = args.Message;
            });
        };
        App.Current.Updated += updateHandler;
    }
    

    And finally, remove them in OnPause():

    protected override void OnPause ()
    {
        base.OnPause ();
        App.Current.Updated -= updateHandler;
    }
    
Sign In or Register to comment.