android列表内存优化
背景
项目中有一个列表,准确的说是图片列表(整个列表都是图片)。可想而知占用的内存会很大。有什么优化的方法呢?
解决办法
对于这种场景,简单分析下。
- 首先图片肯定是通过图片加载框架执行加载的。
- 图片加载框架对于内存有一个最大的上限
- 磁盘缓存暂时不管,只分析内存
那么图片框架是如何和列表结合,实现图片的动态加载的呢?
就是说在不超过图片框架内存上限的前提下,最近使用的图片都会在内存中保存。这个一个限定值,总不能每次都在最大值边缘运行吧,还是要尽可能的优化。
以Picasso为例,Picasso默认会使用设备的15%的内存作为内存图片缓存。遇到上述的图片列表,内存一直占用在15%的上限肯定不行。
解决办法:列表item在不可见的状态下,尽可能的回收内存。
ImageView Bitmap有一个recycle()方法用于内存回收。重写ImageView的onDetachedFromWindow方法,在它从屏幕中消失时回调,去掉drawable引用,能加快内存的回收。
public class RecyclerImageView extends ImageView
{
...
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
setImageDrawable(null);
}
}
踩过的坑
通过上面的方法,好像减少了不少内存的使用(内存使用的最大值减小)。
但是,发现一个新的问题:在Item中存在NetCacheImageView(或者类似框架控件)时,偶尔会出现将一个条目刚刚滑动到看不见,然后将其拉回,其中的图片变为空白。
为什么会这样呢?
为了管理界面显示引用,框架扩展了ImageView中的setImageDrawable方法。在其中对旧的ImageDrawable引用计数减一,新的ImageDrawable计数加一。然后,为了在ImageView退出屏幕时及时回收Bitmap,扩展了onDetachedFromWindow方法,在其中调用setImageDrawable(null),以回收当前显示的Bitmap。至此之前我的关注点一直在图片加载的实现上,理清楚这个机制之后才考虑到是不是回收时机出了问题。
对RecyclerView进行分析之后我发现,当一个item被滑动到刚好看不见的位置时,触发了该item及其子View的onDetachedFromWindow,同样也就调用了setImageDrawable(null)。但是,RecyclerView.Adapter.onViewRecycled方法没有立刻被调用,而要等到继续滑动RecyclerView时才调用。也就是说,RecyclerView没有立即回收已经不在显示区域的item。如果此时将该item拉回,也不会再调用RecyclerView.Adapter.onBindViewHolder,也就是图片消失之后就不会再显示了。
分析:
说简单点就是:recyclerview源码就是最多加载两屏幕的view,就是说当前屏幕的一整屏幕view和列表上下两端的部分view。
但是,onDetachedFromWindow在Item view移出屏幕后就开始执行回收(drawable置为null,使bitmap更容易被回收)
这就是问题所在,当前屏幕上方的view图片引用被清空,可能被回收。如果滑动到此位子,recyclerview不会触发刷新。因此导致图片空白。
解决办法:
已经分析的很清楚了,相信答案也出来了。可以把setImageDrawable放在recyclerview的回收时释放,即onViewRecycled方法中。在adapter中重写此方法。
@Override
public void onViewRecycled(VideoViewHolder holder) {
if (holder != null && holder.getItemViewType() == VideoIndication.TYPE_VIDEO) {
holder.imgThumb.setImageDrawable(null);
}
super.onViewRecycled(holder);
}