Leo Leo - 6 months ago 110
Android Question

ViewPager memory leak

I am having a ViewPager which works well, but having a memory leak in it.

I tried to find the leak with the heap analysis and with Eclipse memory analyser.

It turns out I got several ViewPager instances and not released Bitmaps in the heap after several activity recreations.

Where can be the leak reason in my code?

Here is the Fragment, in which I use ViewPager and scroll items after some period of time:

private final int INTERVAL_TIME = 15000;
private ViewPager mViewPager;
private ViewPagerAdapter mAdapter;
private Handler mHandler;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_bottom, container, false);
......

mViewPager = (ViewPager) view.findViewById(R.id.pager);
mAdapter = new ViewPagerAdapter(getActivity(), bottomItems);
mViewPager.setAdapter(mAdapter);

mHandler = new Handler();
mHandler.postDelayed(UpdateTimeThread, INTERVAL_TIME);
return view;
}

private Runnable UpdateTimeThread = new Runnable() {
@Override
public void run() {
int position;
if (mViewPager.getCurrentItem() == mViewPager.getAdapter().getCount() - 1) {
position = 0;
} else {
position = mViewPager.getCurrentItem() + 1;
}
mViewPager.setCurrentItem(position, true);
mHandler.postDelayed(this, INTERVAL_TIME);

}
};


And here is my PagerAdapter which can show simultaneously 3 items on the screen (if their format.equals("1")) or 2 items at a time in case one of them is double sized (format.equals("2"))

public class ViewPagerAdapter extends PagerAdapter {
private final List<JsonParsed.BottomItem> bottomItems;
private Activity activity;

public ViewPagerAdapter(Activity activity, List<JsonParsed.BottomItem> bottomItems) {
this.activity = activity;
this.bottomItems = bottomItems;
}

@Override
public int getCount() {
if (bottomItems.size() < 3) {
return 1;
}
return (int) Math.floor((float) bottomItems.size() / 3f);
}

@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}

@Override
public Object instantiateItem(ViewGroup container, final int position) {
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View itemView = inflater.inflate(R.layout.pager_item, container, false);
ImageView pic = (ImageView) itemView.findViewById(R.id.image_item);
ImageView pic2 = (ImageView) itemView.findViewById(R.id.image_item2);
ImageView pic3 = (ImageView) itemView.findViewById(R.id.image_item3);
pic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
click(bottomItems.get(position * 3));
}
});
pic2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
click(bottomItems.get(position * 3 + 1));
}
});

pic3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
click(bottomItems.get(position * 3 + 2));
}
});
if (bottomItems.get(position * 3) != null) {
pic.setImageBitmap(BitmapFactory.decodeFile(bottomItems.get(position * 3).getPrev(activity).getAbsolutePath()));
if (bottomItems.get(position * 3).format.equals("1")) {
pic.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 2f));
} else if (bottomItems.get(position * 3).format.equals("2")) {
pic.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
}
} else {
pic.setVisibility(View.GONE);
}

if (bottomItems.size() > position * 3 + 1 && bottomItems.get(position * 3 + 1) != null) {
pic2.setImageBitmap(BitmapFactory.decodeFile(bottomItems.get(position * 3 + 1).getPrev(activity).getAbsolutePath()));
if (bottomItems.get(position * 3 + 1).format.equals("1")) {
pic2.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 2f));
} else if (bottomItems.get(position * 3 + 1).format.equals("2")) {
pic2.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
}
} else {
pic2.setVisibility(View.GONE);
}
if (bottomItems.size() > position * 3 + 2 && bottomItems.get(position * 3 + 2) != null) {
pic3.setImageBitmap(BitmapFactory.decodeFile(bottomItems.get(position * 3 + 2).getPrev(activity).getAbsolutePath()));
if (bottomItems.get(position * 3 + 2).format.equals("1")) {
pic3.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 2f));
} else if (bottomItems.get(position * 3 + 2).format.equals("2")) {
pic3.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
}
} else {
pic3.setVisibility(View.GONE);
}

container.addView(itemView);

return itemView;
}

private void click(JsonParsed.BottomItem bottomItem) {
if (bottomItem.url != null && !bottomItem.url.isEmpty()) {
((MainActivity) activity).showWebView(bottomItem.url);
} else if (bottomItem.video != null && !bottomItem.video.isEmpty()) {
((MainActivity) activity).onPlayMedia(bottomItem.getVideo(activity).getAbsolutePath(), bottomItem.id, false);
}
}

@Override
public void destroyItem(ViewGroup container, int position, Object view) {
container.removeView((LinearLayout) view);
}


Many thanks for any piece of advice

EDIT:

Finally solved the leak.

As Egor pointed out, the main leak has been caused by the Handler,

as it kept the reference onto ViewPager instance so instances were not garbage collected.

Finally I just used lazy solution - WeakHandler (open source library).

I guess it does the same, as described in Egors's article,

but it's pretty handy to use it, as you just using it as usual Handler:

WeakHandler().postDelayed(....) and thats it.

Also I used week references for the Bitmaps and all memory leaks disappeared

Answer

Most likely the Handler is the one causing your memory leaks. There's a wonderful article which describes this problem in detail, suggesting that you use a WeakReference to solve the problem.