2494天 Mr.贰呆

唯一自信的就是自己的人品。
寻求王者玩家一起开黑净化峡谷环境​​

【Android】实战开发之ViewPager图片回收处理内存溢出完美解决方案(含Fragment)

发布于 / 3775 次围观 / 0 条评论 / Android / 二呆 /

在Android实战开发中,ViewPager使用广泛,但使用ViewPager加载多个图片容易出现内存溢出的问题,解决此类内存溢出,需要主要注意以下2点:

1、是否进行过图片压缩处理;

Options opts=new Options();
opts.inJustDecodeBounds=true;
BitmapFactory.decodeResource(activity.getResources(), imgs[position], opts);
DisplayMetrics outMetrics=new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
int x=opts.outWidth/outMetrics.widthPixels;
int y=opts.outHeight/outMetrics.heightPixels;
if(x>y&&x>1){
    opts.inSampleSize=x;
}else if(y>x&&y>1){
    opts.inSampleSize=y;
}
opts.inJustDecodeBounds=false;
Bitmap bitmap = BitmapFactory.decodeResource(activity.getResources(), imgs[position], opts);

2、是否进行过图片回收处理。

下面针对ViewPager加载多个图片和ViewPager+Fragment加载多个图片两种方式分别说明如何利用图片回收解决内存溢出、内存泄露的完美解决方案。

一、ViewPager+View方式加载图片处理图片回收

1、首先,需要在ViewPager适配器PagerAdapter中重写2个方法destroyItem和instantiateItem,在这2个方法中主要实现如下几个功能:

@Override
public void destroyItem(View container, int position, Object object) {
    //TODO 回收图片
    //移除页面
    ((ViewPager)container).removeView(imageView);
}
@Override
public Object instantiateItem(View container, int position) {
    //TODO 图片压缩
    //TODO 加载图片
    //TODO 加载页面
    ((ViewPager)container).addView(imageView);
    return viewList.get(position);
}

2、然后,再在destroyItem方法中,先将使用完的图片引用释放掉,这里要注意用什么方法设置图片,就需要用对应的方法释放图片,比如:

⑴Drawable:

设置图片

imageView.setImageDrawable(drawable);
或
imageView.setImageResource(R.drawable.icon);
释放图片

imageView.setImageDrawable(null);

⑵Bitmap:

设置图片

imageView.setImageBitmap(bitmap);
释放图片

imageView.setImageBitmap(null);

⑶Background:

设置图片

imageView.setBackgroundDrawable(drawable);
或
imageView.setBackgroundResource(R.drawable.icon);
释放图片

imageView.setBackgroundDrawable(null);
3、最后,在destroyItem方法中,进行释放图片资源的操作,代码如下,调用其方法即可:

public void releaseImageViewResouce(ImageView imageView) {
    if (imageView == null) return;
    Drawable drawable = imageView.getDrawable();
    if (drawable != null && drawable instanceof BitmapDrawable) {
        BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
        Bitmap bitmap = bitmapDrawable.getBitmap();
        if (bitmap != null && !bitmap.isRecycled()) {
            bitmap.recycle();
            bitmap=null;
        }
    }
    System.gc();
}

4、PagerAdapter中的完整代码:

public class MyPagerAdapter extends PagerAdapter{
    private Activity activity;
    private ArrayList<ImageView> viewList;
    private int[] imgs;
    public MyPagerAdapter (Activity activity,ArrayList<ImageView> viewList, int[] imgs){
        this.activity=activity;
        this.viewList=viewList;
        this.imgs=imgs;
    }
    @Override
    public void destroyItem(View container, int position, Object object) {
        //回收图片
        ImageView imageView = viewList.get(position);
        imageView.setImageBitmap(null);
        releaseImageViewResouce(imageView);
        //移除页面
        ((ViewPager)container).removeView(imageView);
    }
    @Override
    public Object instantiateItem(View container, int position) {
        //图片压缩
        Options opts=new Options();
        opts.inJustDecodeBounds=true;
        BitmapFactory.decodeResource(activity.getResources(), imgs[position], opts);
        DisplayMetrics outMetrics=new DisplayMetrics();
        activity.getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
        int x=opts.outWidth/outMetrics.widthPixels;
        int y=opts.outHeight/outMetrics.heightPixels;
        if(x>y&&x>1){
            opts.inSampleSize=x;
        }else if(y>x&&y>1){
            opts.inSampleSize=y;
        }
        opts.inJustDecodeBounds=false;
        Bitmap bitmap = BitmapFactory.decodeResource(activity.getResources(), imgs[position], opts);
        //加载图片
        ImageView imageView = viewList.get(position);
        imageView.setImageBitmap(bitmap);
        //加载页面
        ((ViewPager)container).addView(imageView);
        return viewList.get(position);
    }
    @Override
    public int getCount() {
        return viewList.size();
    }
    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view==object;
    }
    /**
     * 释放图片资源的方法
     * @param imageView
     */
    public void releaseImageViewResouce(ImageView imageView) {
        if (imageView == null) return;
        Drawable drawable = imageView.getDrawable();
        if (drawable != null && drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            Bitmap bitmap = bitmapDrawable.getBitmap();
            if (bitmap != null && !bitmap.isRecycled()) {
                bitmap.recycle();
                bitmap=null;
            }
        }
        System.gc();
    }
}

二、ViewPager+Fragment方式加载图片处理图片回收

1、在Fragment中,进行图片压缩、加载图片(有必要时可以使用缓加载)操作,方法同ViewPager+View方式。

2、在ViewPager+Fragment适配器FragmentPagerAdapter中,重写destroyItem方法,并进行图片回收释放内存操作,方法大致和ViewPager+View方式相同,具体完整代码如下:

public class MyAdapter extends FragmentPagerAdapter{
    private List<MyFragment> list;
    public MyAdapter (FragmentManager fm, List<MyFragment> list) {
        super(fm);
        this.list=list;
    }
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
        //回收图片,释放内存
        ViewPager vpContainer = (ViewPager)container;
        View view = vpContainer.getChildAt(position);
        if(view!=null){
            NotRecycledImageView imageView = (NotRecycledImageView)view.findViewById(R.id.iv_lock_screen_image);
            releaseImageViewResouce(imageView);
        }
    }
    @Override
    public Fragment getItem(int position) {
        return list.get(position);
    }
    @Override
    public int getCount() {
        return list.size();
    }
    /**
     * 释放图片资源的方法
     * @param imageView
     */
    public void releaseImageViewResouce(ImageView imageView) {
        if (imageView == null) return;
        Drawable drawable = imageView.getDrawable();
        if (drawable != null && drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            Bitmap bitmap = bitmapDrawable.getBitmap();
            if (bitmap != null && !bitmap.isRecycled()) {
                bitmap.recycle();
                bitmap=null;
            }
        }
        System.gc();
    }
}
3、这里不同于ViewPager+View方式的是,如果实际开发中在回收图片之前添加了将图片引用赋值为null后,出现图片不再加载的情况的话,就不能使用将图片引用赋值为null的方式了,但这样就会引起Canvas: trying to use a recycled bitmap异常,意思是正在使用一个已经回收过的bitmap,这样是不对的,所以需要在ImageView中的onDraw方法中捕获异常,因此,就需要自定义一个ImageView了,自定义ImageView代码很简单,如下代码只需try{}catch(){}即可。

public class NotRecycledImageView extends ImageView{
    public NotRecycledImageView(Context context) {
        super(context);
    }
    public NotRecycledImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        try {
            super.onDraw(canvas);
        } catch (Exception e) {
            LogUtil.i("NotRecycledImageView.onDraw->exception=Canvas: trying to use a recycled bitmap");
        }
    }
}

三、产生的问题

1、为什么以下代码使用软引用SoftReference不能实现回收图片的功能?

SoftReference<Bitmap> wr = cache.get(position);
if(wr!=null&&wr.get()!=null&&!wr.get().isRecycled()){
    Bitmap bitmap = wr.get();
    bitmap.recycle();
    bitmap=null;
}

2、需不需要使用软引用SoftReference来缓存图片?需要的话如何实现?

sitemap