Search code examples
androidandroid-fragmentsandroid-viewpagerandroid-contentproviderandroid-cursorloader

Performance Issue with Multidirectional ViewPager via ContentProvider and CursorLoader


I am attempting to create a MultiDirectional Paging application that is based off an Activity that contains a Support ViewPager and within that are Fragments that contain VerticalViewPagers. Both ViewPagers are using a FragmentStatePagerAdapter and the data is coming from a ContentProvider that is loaded via CursorLoaders. The vertical fragments contain just a simple TextView that has the name of the horizontal fragment and the vertical fragment position.

The issue that I'm coming across is that when you page horizontally to the end and back to the beginning multiple times, the UI begins to lag. The more that you do this, the more lag occurs and eventually, you are able to page from the beginning to the end without the UI ever updating as it's taking so long. The more you page horizontally, the worse the UI lag becomes.

I've enabled strict mode to see if there's too much work happening on the UI thread, but it isn't. I have also tried to check if LeakCanary can find any memory leaks, but it has yet to find any.

I've just started doing some Tracing via Traceview but as I have just started, I've yet to pinpoint the problem (I'm also not too familiar with Traceview yet, but am working on that).

Here is what the Android Monitor looks like when paging horizontally for a bit and then at the end, paging vertically. You'll notice that paging vertically works fine. Android Monitor Output

The spikes seen here are color coded as VSync Delay. I have also noticed that if you run the app and let it sit there, the memory will slowly climb until the Garbage Collector is run, which clears some memory and the slow climb will continue again.

My initial thought is that either I'm doing the Loaders wrong or there is an issue with Loaders and nested ViewPagers.

Is there something fundamentally wrong with what I'm attempting here or something wrong with the Loaders, etc.? I am going to continue digging into this, but in the meantime, I was hoping to get some thoughts from others as to what my problem may be or some suggestions into how to pinpoint the issue.

I have created a very basic sample app that can be found at https://github.com/hooked82/MultiDirectionalPaging

If you pull down the project and run it, on first app launch you will need to click the "+" button in the toolbar to populate the database.

It is using the VerticalViewPager from https://github.com/castorflex/VerticalViewPager

UPDATE 8/1/2016 Per Joe's answer, the cause is the amount of inflations and destruction of views when swapping out the adapter of the VerticalViewPager. I was unable to implement swapCursor() on my custom FragmentStatePagerAdapter due to the bug that causes the adapter to not update with new data. If I were to implement swapCursor(), I would get good horizontal paging, but my adapter's UI would not update when the Cursor was notified of dataset changed.

I've swapped out the VerticalViewPager for a custom CursorRecyclerView to get the same functionality, but still need to setup proper flinging of the RecyclerView to mimic that of the VerticalViewPager.

Thanks for the help, Joe!


Solution

  • The issue is the number of fragment transactions that occur on a horizontal scroll.

    1) In any view pager, there are at most 3 fragments displayed. For the vertical at position v1 there are 3 fragments v0, v1 and v2. If you scroll from v1 to v2, the v0 fragment is destroyed and v3 is created in anticipation of the next scroll.

    So for each vertical scroll a max of two fragment operations may occur.

    2) Now the same thing exists horizontally. If at position h1, you have fragments h0, h1, h2 created. Now because the vertical frags also are created you have up to 3 vertical fragments for each horizontal one. Now things get interesting. Assuming you are a v1 in each horizontal fragment the following happens (worse case) scrolling from h1 to h2. First h0 is destroyed, but also the v0, v1, v2 frag owned by h0. Then h3 is created along with at min v0/v1. However if you previously scrolled to v1 it remembers that position and also creates v2.

    Therefore for each horizontal scroll a minimum of 6 fragment operations and a max of 8 occur per scroll (ie up to 4x more expensive). When you get into view inflation/cleanup this can multiply really fast causing your performance problem.

    You would be better off using a recycler view for at a minimum to handle the vertical scrolling. You likely would need to intercept the scrolling events to "simulate" the snap to page the view pager performs on partial scrolls.