最后在實現一個無限循環的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傳給別人,要注意內存泄露,具體看我另一篇博客
這里還說明另外一個問題,就是變量,能不用成員變量就盡量不要用,不然,一旦內存泄露,會把成員變量的內存也泄露了,我這里就是成員變量導致的內存泄露