记一次内存泄露排查
最后在实现一个无限循环的ViewPager,展示图片,功能实现了,但是运行一段时间之后会挂掉。
多亏了AndroidStudio的Memory Monitor,发现了内存一直在增长。
怎么触发gc内存都不会减少,确定了内存泄露了,但是不知哪里出问题了。
一时想到的排查内存泄露的工具,就是MAT了,但是没找到AndroidStudio的MAT插件。
只能先把java heap dump出来先,如下图所示
dump出来之后,hprof文件会保存在项目下captures目录,之前一直不知到,找了很久。。。
如果切换到Captures这个tab,是可以直接看到HeapSnapshot的,如下图所示
但是这个hprof文件mat不认,需要转换一下,点击hprof文件右键,转成标准.hprof文件即可
然后到eclipse用mat插件打开,如果没有安装mat插件请自行搜索解决
window->open perspective->memory analysis
在memory analysis界面下
File->Open Heap Dump->选中上面Android Studio转换之后的.hprof文件即可
在OverView下面点击Top Consumers, 如下图。byte占了大头,内存基本都给它用了
byte占用这么多内存,一想到的就是bitmap没被释放了。
回去研究我的代码
PagerAdapter代码如下
1 public class SlidePicPagerAdapter extends PagerAdapter { 2 3 private List<SlidePicModel> mItems; 4 private int itemSize; 5 6 public void setItems(List<SlidePicModel> items) { 7 mItems = items; 8 itemSize = items.size(); 9 } 10 11 @Override 12 public int getCount() { 13 return ListUtils.isEmpty(mItems) ? 0 : Integer.MAX_VALUE; 14 } 15 16 @Override 17 public boolean isViewFromObject(View view, Object object) { 18 return view == object; 19 } 20 21 @Override 22 public Object instantiateItem(ViewGroup container, int position) { 23 Context context = container.getContext(); 24 SlidePicModel item = mItems.get(getCurPos(position)); 25 ImageView iv = item.getImageView(); 26 if(iv == null){ 27 iv = new ImageView(context); 28 item.setImageView(iv); 29 } 30 final String imgUrl = ImageUrlExtends.getImageUrl(item.getUrl()); 31 Picasso.with(context).load(imgUrl).into(iv); 32 container.addView(iv); 33 return iv; 34 } 35 36 @Override 37 public void destroyItem(ViewGroup container, int position, Object object) { 38 View imageView = (View) object; 39 container.removeView(imageView); 40 } 41 42 43 private int getCurPos(int pos){ 44 return pos % mItems.size(); 45 } 46 }
我用的是Picasso去加载图片。首先把picasso加载图片给屏蔽了,发现内存正常了,擦,以为我发现了个bug,先去给Picasso提Bug去呢。
再看看我代码,第28行,我把ImageView给缓存到数据源SlidePicModel的成员变量里了。
SlidePicModel代码如下
1 public class SlidePicModel { 2 3 private ImageView imageView; 4 private String url; 5 6 public SlidePicModel(String url) { 7 this.url = url; 8 } 9 10 public ImageView getImageView() { 11 return imageView; 12 } 13 14 public void setImageView(ImageView imageView) { 15 this.imageView = imageView; 16 } 17 18 public String getUrl() { 19 return url; 20 } 21 22 public void setUrl(String url) { 23 this.url = url; 24 } 25 }
要想想,我这个数据源mItems里面代表好几百张图片,而我的应用是无限循环显示图片,也即是我的数据库mItems是不会回收的。
本来显示完图片,ImageView是要被回收的,但是,却被我数据源里面的model引用着,几百张图片的bitmap被引用着,不能回收,app肯定内存溢出。
解决方法如下,把ImageView这个成员变量设置成弱引用,当内存不足时,ImageView可以被内存回收。问题解决,代码如下
1 public class SlidePicModel { 2 3 4 private String url; 5 private WeakReference<ImageView> mImageViewRef; 6 public SlidePicModel(String url) { 7 this.url = url; 8 } 9 10 public ImageView getImageView() { 11 return mImageViewRef == null ? null : mImageViewRef.get(); 12 } 13 14 public void setImageView(ImageView imageView) { 15 // this.imageView = imageView; 16 mImageViewRef = new WeakReference<ImageView>(imageView); 17 } 18 19 public String getUrl() { 20 return url; 21 } 22 23 public void setUrl(String url) { 24 this.url = url; 25 } 26 }
内存回收正常。图片如下
当然了,更好的办法就是,这个ImageView成员变量完全没有存在的必要,但是当时一时把代码写错,出刚好让我研究了一下内存泄露,对内存泄露有更深的认识。
总结:
之前就有看到一些文章在建议,不要把Activity当成静态成员变量。
其实从我上面出现的问题里就可以发现一样的道理。上面是因为数据源一直存在,没有被内存回收,所以引用的ImageView也没有被回收,导致内存溢出。
同理,如果把Activity当成静态成员变量。那么它的生命周期就会和app的生命周期一样,Activity里所引用的对象都不会被释放,即使activity已经结束了,这就会导致内存举出。
使用context的时候,能使用ApplicationContext就使用AplicationContext,如果把Activity当成context传给别人,要注意内存泄露,具体看我另一篇博客
这里还说明另外一个问题,就是变量,能不用成员变量就尽量不要用,不然,一旦内存泄露,会把成员变量的内存也泄露了,我这里就是成员变量导致的内存泄露