javaandroidandroid-fragmentsmemory-leaksotto

Leakcanary report of memory leak using Otto


Following up on my last question, here is the second memory leak I can not get rid of..

I read that I need to register and unregister my static Otto bus according to the Activity and Fragment lifecycle, so I added the register and unregister calls to onStop and onStart... Having a button that triggers an event that is then received by some Fragments inside a Viewpager gives me the following memory leak:

D/LeakCanary﹕ In com.doesnthaveadomain.leo.calendartracker:1.0:1.
D/LeakCanary﹕ * com.doesnthaveadomain.leo.calendartracker.MyFragment has leaked:
D/LeakCanary﹕ * GC ROOT static com.doesnthaveadomain.leo.calendartracker.MyBus.BUS
D/LeakCanary﹕ * references com.squareup.otto.Bus.handlersByType
D/LeakCanary﹕ * references java.util.concurrent.ConcurrentHashMap.table
D/LeakCanary﹕ * references array java.util.concurrent.ConcurrentHashMap$Node[].[3]
D/LeakCanary﹕ * references java.util.concurrent.ConcurrentHashMap$Node.val
D/LeakCanary﹕ * references java.util.concurrent.CopyOnWriteArraySet.al
D/LeakCanary﹕ * references java.util.concurrent.CopyOnWriteArrayList.elements
D/LeakCanary﹕ * references array java.lang.Object[].[0]
D/LeakCanary﹕ * references com.squareup.otto.EventHandler.target
D/LeakCanary﹕ * leaks com.doesnthaveadomain.leo.calendartracker.MyFragment instance

My code..

MainActivity

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

import java.lang.ref.WeakReference;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ViewPager mViewPager = (ViewPager) findViewById(R.id.viewpager);
        MyAdapter myAdapter = new MyAdapter(getSupportFragmentManager(), this);
        mViewPager.setAdapter(myAdapter);
        mViewPager.setCurrentItem(5);

        Button button = (Button) findViewById(R.id.do_something);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyBus.getInstance().post(new SomeEvent("Something"));
            }
        });
    }

    static class SomeEvent {
        String mString;
        SomeEvent(String string) {
            mString = string;
        }
        public String getString() {
            return mString;
        }
    }

}

class MyAdapter extends FragmentStatePagerAdapter {

    private int mNumberOfViews;
    private final WeakReference<AppCompatActivity> mActivityWeakRef;

    public MyAdapter(FragmentManager fm,
                     AppCompatActivity activity) {
        super(fm);

        mActivityWeakRef = new WeakReference<AppCompatActivity>(activity);
        mNumberOfViews = 10;
    }

    @Override
    public Fragment getItem(int position) {
        MyFragment myFragment = new MyFragment();
        AppCompatActivity activity = mActivityWeakRef.get();
        if (activity != null) {
            MyApp.getRefWatcher(mActivityWeakRef.get()).watch(myFragment);
        }
        return myFragment;
    }

    @Override
    public int getCount() {
        return mNumberOfViews;
    }
}

MyFragment

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.squareup.otto.Subscribe;

/**
 * A placeholder fragment containing a simple view.
 */
public class MyFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_test, container, false);
    }

    @Override
    public void onStart() {
        super.onStart();

        MyBus.getInstance().register(this);
    }

    @Override
    public void onStop() {
        super.onStop();

        MyBus.getInstance().unregister(this);
    }

    @Subscribe
    public void onEvent(MainActivity.SomeEvent event) {

    }

}

Layouts, just in case.. MainActivity:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <android.support.design.widget.AppBarLayout
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/toolbar_appbar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true" >

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:theme="@style/AppTheme.Toolbar"
            app:popupTheme="@style/AppTheme.Toolbar.Popup">

        </android.support.v7.widget.Toolbar>

    </android.support.design.widget.AppBarLayout>

    <Button
        android:id="@+id/do_something"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="DO"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Fragment:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingLeft="@dimen/activity_vertical_margin"
    android:paddingStart="@dimen/activity_vertical_margin"
    android:paddingRight="@dimen/activity_vertical_margin"
    android:paddingEnd="@dimen/activity_vertical_margin"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">

</LinearLayout>

Solution

  • You are using watching the reference at the wrong time. You should only call watch(thing) when you are absolutely certain that thing will be garbage collected. For your Activities and Fragments, you will want something like this:

    @Override public void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
        refWatcher.watch(this);
    }
    

    That is from the LeakCanary FAQ "How do I use it?" section