4

My RecyclerView is creating a memory leak each time that its activity has been recreated. I have google it but i was able to find any solution to that. The activity is being destroyed with the back button and its being creating when into to a button from my mainActivity. I have also post the result of the leak canary. Thank you.

//CommentListAdapater
package com.support.android.designlibdemo.adapters;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.squareup.otto.Subscribe;
import com.support.android.designlibdemo.MyApplication;
import com.support.android.designlibdemo.R;
import com.support.android.designlibdemo.events.CommentsLoadEvent;
import com.support.android.designlibdemo.events.EventFinished;
import com.support.android.designlibdemo.events.NetworkErrorEvent;
import com.support.android.designlibdemo.models.RedditComment;
import com.support.android.designlibdemo.models.RedditObject;
import com.support.android.designlibdemo.models.RedditResponse;
import com.support.android.designlibdemo.models.RedditThread;
import com.support.android.designlibdemo.services.RedditService;

import java.util.ArrayList;
import java.util.List;

import retrofit.Callback;
import retrofit.RetrofitError;
import retrofit.client.Response;

public class CommentListAdapter extends RecyclerView.Adapter<CommentListAdapter.ViewHolder> {
    private List<RedditComment> mValues = new ArrayList();
    private Context mContext;
    private static final String SUBREDDIT_NAME = "subreddit_name";
    private static final String THREAD_ID = "thread_id";
    private static final String THREAD_NAME = "thread_name";

    public CommentListAdapter(Context context) {
        mContext = context;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public View mView;
        public TextView mBody;
        public TextView mAuthor;

        public ViewHolder(View itemView) {
            super(itemView);
            mView = itemView;
            mBody = (TextView) itemView.findViewById(R.id.body);
            mAuthor = (TextView) itemView.findViewById(R.id.author_name);
        }
    }

    @Override
    public void onBindViewHolder(CommentListAdapter.ViewHolder holder, int position) {
        if (mValues.get(position).selftext == null) {
            holder.mBody.setText(mValues.get(position).body);
        } else {
            holder.mBody.setText(mValues.get(position).selftext);
        }
        holder.mAuthor.setText(mValues.get(position).author);
    }

    @Override
    public int getItemCount() {
        if (mValues == null) {
            return 0;
        }
        return mValues.size();
    }

    @Override
    public CommentListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // create a new view
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.activity_comment_holder, parent, false);

        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    @Subscribe
    public void fetchData(final CommentsLoadEvent event) {
        RedditService.Implementation.get().getThreadComments(event.subredditName, event.threadId,
                new Callback<List<RedditResponse<RedditComment>>>() {
                    @Override
                    public void success(List<RedditResponse<RedditComment>> redditResponses, Response response) {
                        mValues.clear();
                        for (RedditResponse rr : redditResponses) {
                            RedditComment rc = (RedditComment) rr.getData();
                            for (RedditObject obj : rc.children) {
                                if (obj instanceof RedditThread) {
                                    RedditThread t = (RedditThread) obj;
                                    RedditComment c = new RedditComment();
                                    c.selftext = t.selftext;
                                    c.author = t.author;
                                    mValues.add(c);
                                } else {
                                    RedditComment c = (RedditComment) obj;
                                    mValues.add(c);
                                }
                                notifyDataSetChanged();
                                MyApplication.getBus().post(new EventFinished());
                            }
                        }
                    }

                    @Override
                    public void failure(RetrofitError error) {
                        MyApplication.getBus().post(new NetworkErrorEvent(error, mContext));
                    }
                }
        );

    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        MyApplication.getBus().register(this);
    }

    @Override
    public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
        super.onDetachedFromRecyclerView(recyclerView);
        MyApplication.getBus().unregister(this);
    }
}

//CommentActivity
package com.support.android.designlibdemo.activities;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;

import com.squareup.otto.Subscribe;
import com.support.android.designlibdemo.MyApplication;
import com.support.android.designlibdemo.R;
import com.support.android.designlibdemo.adapters.CommentListAdapter;
import com.support.android.designlibdemo.events.CommentsLoadEvent;
import com.support.android.designlibdemo.events.EventFinished;

public class CommentActivity extends BaseActivity {
    public static final String SUBREDDIT_NAME = "subreddit_name";
    public static final String THREAD_ID = "thread_id";
    public static final String THREAD_NAME = "thread_name";

    private final String TAG = getClass().getSimpleName();
    private RecyclerView mRecyclerView;
    private SwipeRefreshLayout mSwipeRefreshLayout;
    private CommentListAdapter mAdapter;
    private RecyclerView.LayoutManager mLayoutManager;
    private String mSubredditName;
    private String mThreadName;
    private String mThreadId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        Intent intent = getIntent();
        mSubredditName = intent.getStringExtra(SUBREDDIT_NAME);
        mThreadName = intent.getStringExtra(THREAD_NAME);
        mThreadId = intent.getStringExtra(THREAD_ID);

        toolbar.setSubtitle(mThreadName);
        toolbar.setTitle(mSubredditName);
        setToolbar();
        setupRecyclerView();
    }

    @Override
    protected boolean hasCustomIcon() {
        return false;
    }

    @Override
    protected int getLayout() {
        return R.layout.activity_comment;
    }

    private void setupRecyclerView() {
        if (mRecyclerView == null) {

            mRecyclerView = (RecyclerView) findViewById(R.id.comment_recycler);
            mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
        }

        if (mLayoutManager == null) {
            // use a linear layout manager
            mLayoutManager = new LinearLayoutManager(this);
            mRecyclerView.setLayoutManager(mLayoutManager);
            mSwipeRefreshLayout.setColorSchemeColors(R.color.colorAccent,
                                                     R.color.color_primary_dark,
                                                     R.color.frame_background);
            mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    MyApplication.getBus().post(new CommentsLoadEvent(
                                    mSubredditName, mThreadId)
                    );
                }
            });
        }

        if (mAdapter == null) {
            mAdapter = new CommentListAdapter(this);
            mRecyclerView.setAdapter(mAdapter);
            mSwipeRefreshLayout.setRefreshing(true);
            MyApplication.getBus().post(new CommentsLoadEvent(
                            mSubredditName, mThreadId)
            );
        }
    }

    @Subscribe
    public void stopSwipe(final EventFinished event) {
        mSwipeRefreshLayout.setRefreshing(false);
    }

    @Override
    public void onPause() {
        super.onPause();
        MyApplication.getBus().unregister(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        MyApplication.getBus().register(this);
    }
}

//MyApplication
package com.support.android.designlibdemo;

import android.app.Application;

import com.crashlytics.android.Crashlytics;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.otto.Bus;

import io.fabric.sdk.android.Fabric;

public class MyApplication extends Application {
    private static Bus _bus;

    @Override public void onCreate() {
        super.onCreate();
        Fabric.with(this, new Crashlytics());
        LeakCanary.install(this);
    }

    public static Bus getBus() {
        if (_bus == null) {
            _bus = new Bus();
        }
        return _bus;
    }
}

leak image 1

leak image 2

kokeroulis
  • 115
  • 1
  • 8

2 Answers2

3

When your Activity is destroyed, your adapter is not automatically detached from the RecyclerView, thus the adapter is still registered in Otto and it leaks. You need to manually unregister your adapter when the Activity is destroyed.

BladeCoder
  • 12,779
  • 3
  • 59
  • 51
  • 1
    That's right! I just set null in my adapter when Fragment is onStop() or onDestroyView() method and leaks dissapear! – Andy Nov 11 '20 at 12:20
1

private static Bus _bus; This is retaining instances from deallocation. CommentListAdapter is accessing this field, which you have an instance in CommentActivity. That makes CommentActivity not to be able to be deallocated (leaked).

I am not familiar with Otto but you may not use it properly.

If you want to communicate from activity to adapter, why don't you make public method in adapter and call adapter.method() from activity ?

Emma
  • 8,518
  • 1
  • 18
  • 35