App slows down over the period on Android (Memory leak?) How to fix it

RahulPPRahulPP USMember
edited April 2015 in Xamarin.Forms

Hi there,

We are building one app with two screens and master detail page

  1. Categories in Master Detail page – We have 6 categories in hamburger menu.. Clicking on each category will show below page
  2. List view which loads 5 objects of type article (having title, body, datestamp, author) in that particular category.
  3. Carousel view – Clicking on any article will show the same article with carousel view so if user swipes left or right, it loads previous or next article respectively.

The app works well and is very snappy for first few user interactions (say first 10-20 swipes, touches and navigations in between list views and category switches). However the app starts becoming slower and slower over the period. At some point, it becomes unusable. This happens only on Android devices and the app works quite flawlessly on iOS and WP.

You can observe the memory is being loaded and not being free everytime I change the screen, or change the article....

Here are few logcats I could get from my test devices.

  1. OnePlus One (Android 5.0.2 with ART) – http://pastebin.com/kdsLDV7d
  2. Lenovo A6000 (Android Kitkat 4.4.4) - http://pastebin.com/Daagdrjq

Little help would be great.

Tagged:

Answers

  • PaulDistonPaulDiston ✭✭✭✭ USUniversity ✭✭✭✭

    Hi,

    Do you have a sample you could share which reproduces this?

    Thanks

    Paul Diston

  • RahulPPRahulPP USMember

    Hi @PaulDiston thanks for the reply.

    The problem I am facing is while navigating, the content is not being disposed and hence everything is in memory and it is not getting free. There is no dispose method in ContentPage..

    I tried referring some of the threads here.

    Few solutions I tried were

    GC.Collect() while leaving the page..

    I tried using this.Content = null while leaving the page. But while returning to the page showed me blank page so I had to reinitialize the components (InitializeComponents()) in OnAppearing method. Which was freeing up the memory but the performance wasn't so great..

    I am stuck now. How do we free up memory and clear the objects while leaving the page?

  • PeterMajor.5288PeterMajor.5288 ✭✭ GBMember, University ✭✭

    There's a disputed bug about this...

    https://bugzilla.xamarin.com/show_bug.cgi?id=25404

    I've also found that pages (and their associated models) are not released.

    I found this because my MessagingCenter subscriptions for a view model were firing multiple times, once per instance of page ever created.

    I had to manually unsubscribe the subscription when the page disappears. Because MessagingCenter uses weak references, the view model should have been garbage collected and the subscription ignored.

  • RahulPRahulP ✭✭ USMember ✭✭

    Thanks @PeterMajor for the reply. Could you please guide me with unsubscribe method? A code snippet may be?

  • PeterMajor.5288PeterMajor.5288 ✭✭ GBMember, University ✭✭

    Hard to provide code, but the gist is:

    if you're using MessagingCenter.Subscribe, as described here:

    http://developer.xamarin.com/guides/cross-platform/xamarin-forms/messaging-center/

    make sure you have a symmetrical call to MessagingCenter.Unsubscribe.

    This doesn't help with the memory leaks, but it will prevent your events for firing on closed pages / view models.

    I call Unsubscribe in the OnDisappearing event of my pages.

  • RahulPPRahulPP USMember

    I do not see use of MessagingCenter.Subscribe in my project. :worried: It's getting harder to work on with this memory leak :disappointed:

  • a.gustkea.gustke DEMember

    Hello,

    i'm facing the same issue - On the root is a MasterDetailPage. When i click on an item in the Master, i change the Detail, e.g.:

    Detail = new NavigationPage(...)

    If i switch multiple times the pages with this method, it doesn't seems to be that the memory is released properly.

  • RahulPPRahulPP USMember

    So @PeterMajor is there NO SOLUTION to the memory leaks with MasterDetail? I haven't seen any workaround, even with custom renderer for this issue. This is a big blocker in the app I am working on. Without solving it, we can not proceed further.

  • GeorgePrinsGeorgePrins ZAUniversity

    Hi.

    I have also encountered the same problem with regard to memory leaks with MasterDetail class. If anyone knows about a solution can you please reply to this post.

  • IvanOliverIvanOliver ✭✭ ESMember ✭✭

    Me too feel the same .. any solution?

  • BennyMagraftaBennyMagrafta ILMember

    Hi, we are also facing this issue. Both with Xamarin Forms and Xamarin Native.
    For starter, we've made a simple example of a page with 50 labels and 50 Edit Boxes.
    The page has 2 buttons:

    • The first one opens a new page with the same type
    • The second one goes back to the previous page

    So by this way, we can tile the same page over and over again on itself.

    We see that while in Xamarin Native, it takes about 0.5 to 1 second for the page (Activity) to load, it takes in Xamarin Forms about 2-3 seconds. This is also becoming slower as we continue stacking and un-stacking pages.

    On our actual application, the situation is worse. It takes 3-4 seconds on a page that contains much less elements.

    Another thing that we saw, is that the amount of ram used by both example (Forms and Native), is increasing while stacking, and doesn't decrease when un-stacking (or decreases by only small ammout of memory).

    I'm attacking the example projects.

  • abid22abid22 USMember

    Any update or solution for this problem?

  • ChristianFalchChristianFalch ✭✭✭ NODeveloper Group Leader ✭✭✭

    I've seen this a few times, and it often has its root cause in view models and/or views not being freed up correctly when navigating away from them. Setting Content-properties to null or recreating views will probably not help you out.

    The first thing to look for is the following type of code:

    button.Click += (sender, e) => { ... }

    The problem is that you can't dereference the lambda, since you don't have a reference to it (how do you do -= on the above expression?).

    If the lambda contains references to your view model or view, a circular reference might occur causing your views or view models never to be freed. Calling GC.Collect() won't help, since the garbage collector sees references even though it was not your intention.

    What I suggest is:
    - Add/remove events in OnAppearing/OnDisappear to make sure you free up references correctly
    - Use delegate methods in your class, not anonymous lambdas
    - Look out for subscriptions like the ones @PeterMajor experienced
    - In your debug build add code for showing that dispose methods are called when you navigate away from your views.

    To implement the last suggestion you should have base classes for your app's views and view models. Implement the disposable pattern and add code (using #if DEBUG) in the Dispose() method to show a local notification or another UI overlay. Remember that dispose is not always called immediately so you might need to do some counting here.

    Remember that finding memory leaks often takes some time and involves some thinking. A systematic approach always pays of :)

  • TorbenKruseTorbenKruse ✭✭✭ DEMember ✭✭✭
    edited August 2015

    I have this little trick to cleanup pages and ViewModels. I use Commands instead of event handlers and I use MessagingCenter to communicate between ViewModels.

    Create two interfaces:

    /// <summary>
    /// Implement function to cleanup the page reference
    /// </summary>
    public interface ICleanup
    {
        /// <summary>
        /// "Cleans" the page
        /// </summary>
        void Cleanup();
    }
    
    /// <summary>
    /// Implement function to unregister a ViewModel from <c>MessagingCenter</c>
    /// </summary>
    public interface IUnsubscribe
    {
        /// <summary>
        /// Unsubscribe from <c>MessagingCenter</c>
        /// </summary>
        void Unsubscribe();
    }
    

    I implement the ICleanup on every Page I create. On a normal ContentPageit looks like this:

    /// <summary>
    /// Sample ContentPage
    /// </summary>
    public class SampleContentPage : ContentPage, ICleanup
    {
        ...
    
        /// <summary>
        /// <inheritdoc/>
        /// </summary>
        public void Cleanup()
        {
            this.Content = null;
            this.BindingContext = null;
        }
    }
    

    I implement IUnsubscribe on every ViewModel which is registering a function on the MessagingCenter. This normally looks like this:

    /// <summary>
    /// <inheritdoc/>
    /// </summary>
    public void Unsubscribe()
    {
        MessagingCenter.Unsubscribe<SampleContentPageViewModel>(this, "UpdateRequired");
    }
    

    Now the tricky part. On my NavigationPage I store references to each Page which gets pushed onto the NavigationStack. Note that we don't want to stop the GC from possibly collecting these pages, so we use a WeakReference.

    List<WeakReference<Page>> _pages;

    Then I register for the following events of the NavigationPage to add my WeakReference when a page gets pushed and cleanup the pages once they get popped.

    /// <summary>
    /// Add a weak reference of the page
    /// </summary>
    /// <param name="sender">Event Sender</param>
    /// <param name="e">Event Argumente</param>
    private void NavigationView_Pushed(object sender, NavigationEventArgs e)
    {
        this._pages.Add(new WeakReference<Page>(e.Page));
    }
    
    /// <summary>
    /// Cleanup when a single page gets popped
    /// </summary>
    /// <param name="sender">Event Sender</param>
    /// <param name="e">Event Argumente</param>
    private void NavigationView_Popped(object sender, NavigationEventArgs e)
    {
        this.CleanupPage(e.Page);
    
        Page weakPage;
        var pageRef = this._pages.SingleOrDefault(i => i.TryGetTarget(out weakPage) && weakPage.Equals(e.Page));
    
        if (pageRef != null)
        {
            this._pages.Remove(pageRef);
        }
    }
    
    /// <summary>
    /// Cleanup all pages when we pop to root
    /// </summary>
    /// <param name="sender">Event Sender</param>
    /// <param name="e">Event Argumente</param>
    private void NavigationView_PoppedToRoot(object sender, NavigationEventArgs e)
    {
        foreach (var i in this._pages)
        {
            Page p;
            if (i.TryGetTarget(out p))
            {
                this.CleanupPage(p);
            }
        }
        this._pages.Clear();
        this._pages.Add(new WeakReference<Page>(e.Page));
    }
    

    And here is the simple CleanupPage routine:

    /// <summary>
    /// "Cleans up" a page
    /// </summary>
    /// <param name="p">The page reference</param>
    private void CleanupPage(Page p)
    {
        var context = p.BindingContext as IUnsubscribe;
        if (context != null)
        {
            context.Unsubscribe();
        }
        var page = p as ICleanup;
        if (page != null)
        {
            page.Cleanup();
        }
    }
    
  • MalikKhanMalikKhan USMember, University

    Hi @TorbenKruse, maybe this help other people who will use your approach. If you have subscription on the parent page like say DashboardPage/HomePage and you are publishing some stuff from any child pages. then in that case, the subscription code in the HomePage/DashboardPage does not execute as you have already made the page object to null. One other way to make sure you have only one page and model instance in the application is to use any DI like Autofac, TinyIoC framework and register the page and model to have a single instance that way you can control the instances of objects being multiplied when you move out of the pages.

  • ballieballie ✭✭ BHMember ✭✭

    Switch off your phone.

    Turn on by press (POWER button)+(volume +)+(volume down) simultaneously.

    Once your phone vibrating release (POWER button).

    BINGO you are in recovery mode.

    select apply update/zip from sdcard.Choose the file you have copy earlier. (move the selection with volume button)

    once selected press POWER button to select.

    finally select reboot system now.
    Hope this will work, otherwise check Lenovo A6000 Manual.

Sign In or Register to comment.