I have an Android app that displays weather data (I can give you the name of the app privately if you want to test the problem). The user can browse from one day to another to see the weather of a particular day.
Application architecture
My application uses fragments (the only MainActivity with a navigation box that calls specific fragments).
DayPagerFragment uses a ViewPager with an unlimited number of pages (dynamic fragments). The page is a day.
Daypagerfragment
public class DayPagerFragment extends Fragment { private ViewPager mViewPager; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_day, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mViewPager = (ViewPager) view.findViewById(R.id.pager); mViewPager.setOffscreenPageLimit(1); mViewPager.setAdapter(new DayAdapter(getChildFragmentManager())); } private static class DayAdapter extends FragmentStatePagerAdapter { public DayAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return DayFragment.newInstance(null); } @Override public int getCount() {
First optimization: it is controlled using the FragmentStatePagerAdapter , because the FragmentPagerAdapter not suitable for using / dynamic fragments (saves all fragments in memory).
The second option: I set the number of pages to be saved on both sides of the current page using setOffscreenPageLimit(1) .
Dayfragment
public class DayFragment extends Fragment { private TextView mDay; private TextView mMonth; private Button mPrevDay; private Button mNextDay; private ImageView mCenter; private ImageView mLeft; private ImageView mRight; ... private DayRepository dayRepository; private Day currentDay; private Day prevDay; private Day nextDay; private DayUtil dayUtil; private DayUtil dayUtilPrev; private DayUtil dayUtilNext; private Calendar cal; private Calendar calPrev; private Calendar calNext; public static DayFragment newInstance(Calendar calendar) { DayFragment dayFragment = new DayFragment(); Bundle args = new Bundle(); args.putInt("year", calendar.get(Calendar.YEAR)); args.putInt("month", calendar.get(Calendar.MONTH)); args.putInt("day", calendar.get(Calendar.DAY_OF_MONTH)); dayFragment.setArguments(args); return dayFragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_day_nested, container, false); mDay = (TextView) view.findViewById(R.id.textView_day); mMonth = (TextView) view.findViewById(R.id.textView_month); mCenter = (ImageView) view.findViewById(R.id.imageView_center);
Problem:
My application is very graphical because on every page it displays:
- 10 TextBox (inner text changes depending on the day)
- 4 ImageView (weather symbol D-1, D-Day, D + 1) + another ImageView
I quickly get OutOfMemoryError (after +/- 30 pages) when I view ViewPager pages.
It is like fragments are not freed from memory. The garbage collector does not work as I expected (I think this is because something is referencing old fragments).
Logcat
04-06 20:01:21.683 27008-27008/com.example D/dalvikvm﹕ GC_BEFORE_OOM freed 348K, 2% free 194444K/196608K, paused 93ms, total 93ms 04-06 20:01:21.683 27008-27008/com.example E/dalvikvm-heap﹕ Out of memory on a 1790260-byte allocation. 04-06 20:01:21.693 27008-27008/com.example E/AndroidRuntime﹕ FATAL EXCEPTION: main Process: com.example, PID: 27008 java.lang.OutOfMemoryError at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:587) at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:422) at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:840) at android.content.res.Resources.loadDrawable(Resources.java:2110) at android.content.res.Resources.getDrawable(Resources.java:700) at android.widget.ImageView.resolveUri(ImageView.java:638) at android.widget.ImageView.setImageResource(ImageView.java:367) at com.example.ui.DayFragment.onCreateView(DayFragment.java:126) //...mLeft.setImageResource() at android.support.v4.app.Fragment.performCreateView(Fragment.java:1500) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:927) at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1104) at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682) at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1467) at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:440) at android.os.Handler.handleCallback(Handler.java:733) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5017) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) at dalvik.system.NativeStart.main(Native Method) 04-06 20:01:21.773 27008-27008/com.example I/dalvikvm-heap﹕ Clamp target GC heap from 197.480MB to 192.000MB 04-06 20:01:21.773 27008-27008/com.example D/dalvikvm﹕ GC_FOR_ALLOC freed 565K, 2% free 193932K/196608K, paused 73ms, total 73ms
I have a memory leak, but I do not know why and where. I used the Eclipse MAT (Memory Analyzer), but I do not know where to look.
Can you help me?
Edit: to load Typeface, I use the following code:
Dayfragment.java
Myapplication.java
public Typeface getTrebuchet() { if (trebuchet == null){ trebuchet = Typeface.createFromAsset(getAssets(), Consts.PATH_TYPEFACE_TREBUCHET); } return trebuchet; }
My DDMS show a memory leak:

Edit 2: IMPORTANT!
I use the navigation box in my application, which is only processed by my MainActivity . The navigation box uses fragments (rather than actions).
This is why DayPagerFragment continues from a Fragment (and not from FragmentActivity or Activity ).
To rush between days, the user must touch two buttons (prev / next). I use setOnClickListener on this button in DayFragment (see My updated code).
The problem is that I am ((MainActivity)getActivity()).viewDay(calPrev);
Mainactivity
public class MainActivity extends ActionBarActivity implements NavigationDrawerFragment.NavigationDrawerCallbacks { private NavigationDrawerFragment mNavigationDrawerFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ...
So ... I think that since I create a new fragment every time, the ViewPager cannot do its job! And MainActivity keeps a reference to each fragment: this is why the garbage collector does not free memory.
Now:
- I do not know if my theory is correct.
- How to fix it? How to call
setCurrentPagerItemPrev and setCurrentPagerItemNext methods from setOnClickListener (see My updated code in DayPagerFragment )?
NB: I use mAdapterViewPager.getRegisteredFragment() instead of mViewPager.setCurrentItem , because my DayAdapter continues from SmartFragmentStatePagerAdapter , but it is the same.