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>
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