LruCache:从网络加载图片缓存实例

Posted 阿Hai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LruCache:从网络加载图片缓存实例相关的知识,希望对你有一定的参考价值。

OOM异常

堆内存用于存储实例对象,当程序不断创建对象,并且对象都有引用指向,那么垃圾回收机制就不会清理这些对象,当对象多到挤满堆内存的上限后,就产生OOM异常。android系统为每个应用程序使用的内存设置了一个上限。这个上限值可以用下面的方法取得: long maxSize = Runtime.getRuntime().maxMemory();

OOM异常通常分为下面几种情况:
1.内存泄漏导致的OOM:new出来的很多对象已经不需要了,但仍然有引用指向,所以垃圾回收机制无法回收。
    其场景类似于:创建了一个Handler,并且执行了一个Delay的任务没有完成,此时拥有此Handler对象的宿主对象亦不能被回收。
    或者,static的方法或成员太多,被外部使用,而外部的牵引对象没有对其进行释放,那么整个static的类都不会被释放,也就造成内存泄漏。
2.内存溢出:new出来的对象都是需要的,但堆内存太小装不下了。
  如在一个GridView中显示图片,如果不压缩图片,采用为1920*1080的32位图片,每个将占用1920*1080*8=15MB内存,假设系统为应用程序分配96M内存,再假令全部用于存储图片,也只能存96/15,超出即可导致内存溢出。
3.关于 Bitmap 引起的泄漏,网上还有另一种说法:
  一个进程的内存可以由2个部分组成:java使用内存,C使用内存,这两个内存的和必须小于16M(假设为这么多),不然就会出现大家熟悉的OOM,这个就是第一种OOM的情况。
  一旦内存分配给Java后,以后这块内存即使释放后,也只能给Java的使用,这个估计和java虚拟机里把内存分成好几块进行缓存的原因有关。
  所以如果Java突然占用了一个大块内存,即使很快释放了,C代码也无法使用,而Bitmap的生成是通过malloc进行内存分配的,占用的是C的内存,如果Bitmap需要的内存大于C可用内存也会导致OOM。

避免OOM

对于java中不再使用的资源需要尽快的释放,即设置成null。
尽量少用static方法和static成员。
对于不再使用的bitmap应该手动调用recycle方法,并且设置成null。图片还应尽量使用软引用方式,这样可以加快垃圾回收。


LruCache

内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache,这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:

1.设备可以为每个应用程序可分配多大的内存。
2.屏幕上一次最多需要显示多少个图片,有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上。
3.屏幕大小和分辨率。一个超高分辨率的设备比起一个较低分辨率的设备,在持有相同数量图片的时候,需要更大的缓存空间。
4.图片的尺寸和大小,还有每张图片会占据多少内存空间。
5.图片被访问的频率有多高。如某些图片的访问频率比其它图片要高,应该使用多个 LruCache 对象来区分不同组的图片。
6.维持好数量和质量之间的平衡。存储多个低像素的图片,而在后台线程加载高像素的图片会更有效。

并没有一个指定的缓存大小可以满足所有的应用程序,应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。
一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则更有可能会引起 java.lang.OutOfMemory 的异常,因为 LruCache 中的强引用不被翻放,而程序又需要内存。

使用LruCache,需要重写sizeOf方法,返回占用的内存大小。下面是一个图片缓存的实现:

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.util.Log;

import java.util.concurrent.locks.ReentrantLock;

public class BitmapCache {

    private static final String TAG = "debug";

    private LruCache<String, Bitmap> mBitmapCache;

    private ReentrantLock mLock = new ReentrantLock();

    public BitmapCache() {

        // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
        long maxSize = Runtime.getRuntime().maxMemory();

        Log.d(TAG, "maxMemory size = " + toMB(maxSize));

        // LruCache 使用的缓存值,使用系统分配给应用程序大小的 1/8
        maxSize = maxSize >> 3;
        // maxSize = 1 << 1024 << 1024;
        Log.d(TAG, "cache used maxSize = " + toMB(maxSize));

        mBitmapCache = new LruCache<String, Bitmap>((int) maxSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
    }

    public void add(String key, Bitmap value) {
        mLock.lock();
        try {
            mBitmapCache.put(key, value);
        } finally {
            mLock.unlock();
        }
    }

    public void remove(String key) {
        mLock.lock();
        try {
            mBitmapCache.remove(key);
        } finally {
            mLock.unlock();
        }
    }

    public Bitmap get(String key) {
        mLock.lock();
        try {
            return mBitmapCache.get(key);
        } finally {
            mLock.unlock();
        }
    }

    public boolean containsKey(String key) {
        mLock.lock();
        try {
            return mBitmapCache.get(key) != null;
        } finally {
            mLock.unlock();
        }
    }

    public static long toMB(long byteOfSize) {
        return byteOfSize >> 20;
    }
}

 

使用缓存技术从网络下载图片显示的实例类

BitmapDownloadTask 类从网络下载图片,并将其解析为适配ImageView大小的格式,以减少对内存的占用,并取得一个良好的显示效果。

import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;

import java.lang.ref.SoftReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;

public class BitmapDownloadTask extends AsyncTask<Void, Void, Bitmap> {

    private static final String TAG = "debug";

    private String mImgUrl;
    private SoftReference<ImageView> mImageViewSoftReference;
    private BitmapCache mBitmapCache;
    private List<BitmapDownloadTask> mTaskList;
    private int mReqWidth;
    private int mReqHeight;

    public BitmapDownloadTask(BitmapCache bitmapCache, List<BitmapDownloadTask> tasks, ImageView imgView, String url) {
        mBitmapCache = bitmapCache;
        mTaskList = tasks;
        mImageViewSoftReference = new SoftReference<>(imgView);
        mImgUrl = url;
        mReqWidth = imgView.getMeasuredWidth();
        mReqHeight = imgView.getMeasuredHeight();
    }

    @Override
    protected Bitmap doInBackground(Void... params) {
        Bitmap bitmap;
        HttpURLConnection conn = null;
        try {
            URL url = new URL(mImgUrl);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);
            bitmap = BitmapTools.decodeSampledBitmapFromInputStream(conn.getInputStream(), mReqWidth, mReqHeight);
            Log.d(TAG, "bitmap size: " + bitmap.getWidth() + "/" + bitmap.getHeight() + ", " + bitmap.getConfig()
                    .name() + ", " + format(bitmap.getByteCount()));
        } catch (Exception e) {
            Log.e(TAG, "", e);
            bitmap = null;
        } finally {
            if (conn != null)
                conn.disconnect();
        }

        return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {

        mTaskList.remove(this);

        if (bitmap != null) {
            mBitmapCache.add(mImgUrl, bitmap);
        }

        ImageView imgView = mImageViewSoftReference.get();
        if (isCancelled() || imgView == null || imgView.getTag() != mImgUrl) {
            bitmap = null;
        }

        if (bitmap != null) {
            imgView.setImageBitmap(bitmap);
        }

        super.onPostExecute(bitmap);
    }

    private String format(long byteOfSize) {
        long KB = byteOfSize >> 10;
        long MB = KB >> 10;
        if (MB != 0) {
            return MB + " MB";
        }
        return KB + " KB";
    }
}
public class BitmapTools {

    private static final String TAG = "debug";public static Bitmap decodeSampledBitmapFromPath(String pathName, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(pathName, opts);
        // Calculate inSampleSize
        opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);
        // Decode bitmap with inSampleSize set
        opts.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(pathName, opts);
    }

    public static Bitmap decodeSampledBitmapFromData(byte[] data, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(data, 0, data.length, opts);
        // Calculate inSampleSize
        opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);
        // Decode bitmap with inSampleSize set
        opts.inJustDecodeBounds = false;
        return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
    }

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int id, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, id, opts);
        // Calculate inSampleSize
        opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);
        // Decode bitmap with inSampleSize set
        opts.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, id, opts);
    }

    public static Bitmap decodeSampledBitmapFromInputStream(InputStream is, int reqWidth, int reqHeight) {
        Bitmap bitmap = BitmapFactory.decodeStream(is);
        if (bitmap == null)
            return null;
        // Log.d(TAG, "req size: " + reqWidth + ", " + reqHeight);
        // Log.d(TAG, "bitmap size: " + bitmap.getWidth() + ", " + bitmap.getHeight());
        if (bitmap.getWidth() > reqWidth || bitmap.getHeight() > reqHeight) {
            int newWidth;
            int newHeight;
            if (bitmap.getWidth() > bitmap.getHeight()) {
                newWidth = reqWidth;
                newHeight = newWidth * bitmap.getHeight() / bitmap.getWidth();
            } else {
                newHeight = reqHeight;
                newWidth = newHeight * bitmap.getWidth() / bitmap.getHeight();
            }
            bitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, false);
        }
        return bitmap;
    }

    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
            // Calculate the largest inSampleSize value that is a power of 2 and
            // keeps both height and width larger than the requested height and
            // width.
            while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        Log.d(TAG, "inSampleSize=" + inSampleSize);
        return inSampleSize;
    }
}

 

Activity类,在滑动时取消所有的任务,没有滑动时自动加载可见范围内的所有图片,以避免加载时滑动产生卡顿的问题。

public class LruAcy extends AppCompatActivity {

    private static final String TAG = "debug";

    private BitmapCache mBitmapCache;
    private List<BitmapDownloadTask> mBitmapDownloadTasks;

    private GridView mImageWall;
    private BaseAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.acy_lru);

        mBitmapCache = new BitmapCache();
        mBitmapDownloadTasks = new ArrayList<>();

        mImageWall = (GridView) findViewById(R.id.image_wall);

        mAdapter = new BaseAdapter(this);
        mImageWall.setOnScrollListener(mOnScrollListener);
        mImageWall.setAdapter(mAdapter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        cancelAll();
    }

    private void setImageView(String url, ImageView imgView) {
        Bitmap bitmap = mBitmapCache.get(url);
        if (bitmap == null) {
            imgView.setImageResource(R.mipmap.ic_launcher);
        } else {
            imgView.setImageBitmap(bitmap);
        }
    }

    private void cancelAll() {
        for (BitmapDownloadTask task : mBitmapDownloadTasks) {
            task.cancel(true);
        }
        mBitmapDownloadTasks.clear();
    }

    private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {
        try {
            for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
                String imageUrl = ImageUrls.IMAGE_URLS[i];
                Bitmap bitmap = mBitmapCache.get(imageUrl);
                ImageView imageView = (ImageView) mImageWall.findViewWithTag(imageUrl);
                if (imageView == null)
                    continue;
                // Log.d(TAG, "bitmap=" + bitmap + ", imageView=" + imageView);
                if (bitmap == null) {
                    BitmapDownloadTask task = new BitmapDownloadTask(mBitmapCache, mBitmapDownloadTasks, imageView,
                            imageUrl);
                    mBitmapDownloadTasks.add(task);
                    task.execute();
                } else {
                    imageView.setImageBitmap(bitmap);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private AbsListView.OnScrollListener mOnScrollListener = new AbsListView.OnScrollListener() {

        private boolean mIsFirstRun = true;
        private int firstVisibleItem;
        private int visibleItemCount;

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if (scrollState == SCROLL_STATE_IDLE) {
                loadBitmaps(firstVisibleItem, visibleItemCount);
            } else {
                cancelAll();
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            this.firstVisibleItem = firstVisibleItem;
            this.visibleItemCount = visibleItemCount;
            if (mIsFirstRun && totalItemCount > 0) {
                mIsFirstRun = false;
                loadBitmaps(firstVisibleItem, visibleItemCount);
            }
        }
    };

    private class BaseAdapter extends android.widget.BaseAdapter {

        private LayoutInflater mInflater;

        public BaseAdapter(Context context) {
            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

        @Override
        public int getCount() {
            return ImageUrls.IMAGE_URLS.length;
        }

        @Override
        public String getItem(int position) {
            return ImageUrls.IMAGE_URLS[position];
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.image_layout, parent, false);
            }

            String url = getItem(position);
            ImageView img = (ImageView) convertView.findViewById(R.id.img);
            img.setTag(url);
            setImageView(url, img);

            return convertView;
        }
    }
}

 

界面布局 acy_lru.xml image_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.john.webapp.LruAcy">

    <GridView
        android:id="@+id/image_wall"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:horizontalSpacing="5dp"
        android:numColumns="4"
        android:verticalSpacing="5dp"/>

</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <ImageView
        android:id="@+id/img"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:scaleType="fitXY"/>

</LinearLayout>

 

测试使用的Url

public class ImageUrls {

    public final static String[] IMAGE_URLS = new String[]{
            "http://img.my.csdn.net/uploads/201309/01/1378037235_3453.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037235_9280.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037234_3539.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037234_6318.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037194_2965.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037193_1687.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037193_1286.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037192_8379.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037178_9374.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037177_1254.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037177_6203.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037152_6352.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037151_9565.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037151_7904.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037148_7104.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037129_8825.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037128_5291.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037128_3531.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037127_1085.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037095_7515.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037094_8001.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037093_7168.jpg",
            "http://img.my.csdn.net/uploads/201309/01/1378037091_4950.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949643_6410.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949642_6939.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949630_4505.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949630_4593.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949629_7309.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949629_8247.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949615_1986.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949614_8482.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949614_3743.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949614_4199.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949599_3416.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949599_5269.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949598_7858.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949598_9982.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949578_2770.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949578_8744.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949577_5210.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949577_1998.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949482_8813.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949481_6577.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949480_4490.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949455_6792.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949455_6345.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949442_4553.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949441_8987.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949441_5454.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949454_6367.jpg",
            "http://img.my.csdn.net/uploads/201308/31/1377949442_4562.jpg"
    };
}

 

以上是关于LruCache:从网络加载图片缓存实例的主要内容,如果未能解决你的问题,请参考以下文章

Android 加载图片优化 LruCache DiskLruCache

Android DiskLruCache完全解析,硬盘缓存的最佳方案

Android软引用(SoftReference)与LruCache

android缓存

DiskLruCache 硬盘缓存 使用简介

让App中增加LruCache缓存,轻松解决图片过多造成的OOM