Search code examples
androidandroid-fragmentsandroid-recyclerviewandroid-viewpager

RecyclerView within viewpager lag when calling onResume


I have an activity which contains a fragment that contains a tab layout alongside a viewpager. Each viewpager has a fragment and each fragment has a recyclerview.

To summarise: mainactivity (contains) -> mainFragment (contains) -> viewpager (contains) -> fragments (contains) -> recyclerview.

When the app launch, the recyclerview scrolls smoothly, no lags or whatsoever. And even when I do a GPU profiling, the GPU bars generated on app launch are acceptable.

The issue only happens when I go to another activity and go back to the viewpager (onResume) that the recyclerview becomes lag.

To show you the issue at hand, here are some pictures on the GPU profiling:

The image on the left is the initial app launch GPU profile when scrolled.

The image in the right is the app GPU profile after invoking onResume 10 times and scrolled.

As you can see, the adapter is not loading any images or doing any CPU/GPU consuming task in the UI thread - the adapter only loads an empty list hence not doing any expensive tasks.

the onResume code for all the fragments is this:

@Override
public void onResume() {
    super.onResume();
}

Not even the recycler view is redrawn during onResume, the adapter is only "resumed" as as its last state by Android.

So I'm at a lost. I was thinking if this has anything to do with memory leak, but after looking for days, I could not pinpoint the actual issue.

Do advice. Thanks in advance!

Edit 1: - Tried removing the viewpager and only loading the fragment containing the recyclerview. After doing the same thing by triggering OnResume 10 times, the app still has the overdraw problem, i.e: performance lag.

Edit 2: To clarify things futher, this are my implementations:

The main fragment is inflated alongside the MainActivity via a method in MainActivity:

private void swapFragment(Class fragmentClass, String fragmentTag) {
    FragmentManager fm = getSupportFragmentManager();
    FragmentTransaction ft = fm.beginTransaction();
    Fragment fragment = fm.findFragmentByTag(fragmentTag);

    Fragment currentFragment = fm.getPrimaryNavigationFragment();
    if (currentFragment != null) {
        ft.detach(currentFragment);
    }

    try {
        if (fragment == null) {
            fragment = (Fragment) fragmentClass.newInstance();
            ft.add(R.id.flContent, fragment, fragmentTag);
        } else {
            ft.attach(fragment);
        }

    } catch (Exception e) {
        /*not in used
        empty*/
    }

    ft.setPrimaryNavigationFragment(fragment);
    ft.setReorderingAllowed(true);
    ft.commitNowAllowingStateLoss();
}

After inflation, the MainFragment will inflate a Viewpager with a tab layout. The Viewpager uses a custom adapter:

public class NewsPagerAdapter extends FragmentStatePagerAdapter {

private RealmResults<Section> catIds;

public NewsPagerAdapter(FragmentManager fm, RealmResults<Section> catIds) {
    super(fm, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
    this.catIds = catIds;
}

@Override
public Fragment getItem(int position) {
        return ArticleListFragment.newInstance(catIds.get(position).getSectionName(), true);
}

@Override
public int getCount() {
    return catIds.size();
}
}

As you can see, the db that I am using is Realm, and during viewpager initiation, we query the db for "Section" results and pass the section name into an "ArticleListFragment" for inflation.

Within ArticleListFragment is the recycler view. Inflation of the recycler view is such:

ArticleListAdapter articleAdapter = new ArticleListAdapter(articles, getContext(), this, realm);
rvArticle.setLayoutManager(new LinearLayoutManager(getContext()));
rvArticle.setAdapter(articleAdapter);

The recycler view adapter is such:

public class ArticleListAdapter extends RealmRecyclerViewAdapter<ArticleNewCMS, ArticleListAdapter.ArticleHolder> {
private Context context;
private Realm realm;
private Fragment fragment;
private RealmResults<ArticleNewCMS> articles;

public ArticleListAdapter(@Nullable RealmResults<ArticleNewCMS> articles, Context context, @Nullable Fragment fragment, Realm realm) {
    super(articles, true);
    this.context = context;
    this.fragment = fragment;
    this.realm = realm;
    this.articles = articles;
}

@NonNull
@Override
public ArticleHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
    View view;
    switch (viewType) {
        case 0:
            view = LayoutInflater.from(context).inflate(!RealmHelper.NightmodeHelper.isNightMode(context) ? R.layout.item_rv_article_headline : R.layout.item_rv_article_headline_nightmode, viewGroup, false);
            break;
        case 7:
            view = LayoutInflater.from(context).inflate(R.layout.item_rv_article_lifestyle_headline, viewGroup, false);
            break;
        default:
            view = LayoutInflater.from(context).inflate(!RealmHelper.NightmodeHelper.isNightMode(context) ? R.layout.item_rv_article_compact : R.layout.item_rv_article_compact_nightmode, viewGroup, false);
    }
    return new ArticleHolder(view);
}

@Override
public void onBindViewHolder(@NonNull final ArticleHolder holder, int position) {

}

public class ArticleHolder extends RecyclerView.ViewHolder {
    ArticleHolder(View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
    }
}
}

Of course, the adapter implementation was stripped down for simplicity. But it still poses the same issue: the scrolling lag still appears even when I remove all the views and bindings.


Solution

  • Not sure how many are encountering this but the way I solve this issue (or at lease improve the circumstance of this issue) is by replacing AppCompatActivity with FragmentActivity.

    Basically:

    problematic class:

    public class MyFragmentContainerActivity extends AppCompatActivity 
    

    to

    public class MyFragmentContainerActivity extends FragmentActivity
    

    The scrolling lag was significantly improve!

    I suppose this has something to do with nesting fragments. Though with this solution come the issue with losing layout styling (e.g: TextView, etc) but that can be easily solved by using AppCompat elements (e.g: AppCompatTextView).