使用超大图片和加载大量图片

2016-07-19  本文已影响256人  李俊的博客

当使用超大的Bitmap图片或加载大量图片时很容易出现OOM(Out Of Memory)的现象
一般有两种解决的方法:
1、使用超大的Bitmap图片时采取压缩图片的方法
2、加载大量图片时使用LruCache缓存

使用超大图片

BitmapFactory.Options有个inJustDecodeBounds属性,将inJustDecodeBounds设置为true时,这样就可以在不给图片分配内存时读取图片的基本信息,读取并设置之后,再把该值改为false,然后再进行图片解析,这就是压缩图片的原理。

计算inSampleSize

图片压缩时高和宽都按inSampleSize比例进行压缩,比如inSampleSize为4时就表示高和宽都为原来的1/4,图片整体压缩到原来的1/16。

// 计算inSampleSize
public static int calculateInSampleSize(BitmapFactory.Options options,
                                        int reqWidth, int reqHeight) {
    // 源图片的高度和宽度
    final int height = options.outHeight;
    final int width = options.outWidth;
    // inSampleSize不能小于1
    int inSampleSize = 1;
    // 源图片的高或宽大于要求的
    if (height > reqHeight || width > reqWidth) {
        // 计算出实际宽高和目标宽高的比率
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);
        // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
        // 一定都会大于等于目标的宽和高。
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }
    return inSampleSize;
}
从资源中解析图片
// 解析图片
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
                                                     int reqWidth, int reqHeight) {
    // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);
    // 调用上面定义的方法计算inSampleSize值
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    // 使用获取到的inSampleSize值再次解析图片
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

这里从资源中解析图片,所以需要传入Resources和图片的资源id;另外我们希望将图片压缩成多少尺寸就需要传入相应的宽和高。

向ImageView中加载图片
mImageView.setImageBitmap(
        decodeSampledBitmapFromResource(
                getResources(), R.drawable.image, 100, 100));

加载大量图片

LruCache缓存主要算法原理是把最近使用的对象用强引用存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值即将达到预设定值之前从内存中移除。

实例化缓存对象
// 每个应用程序最高可用内存,单位KB。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024));

// 设置缓存空间的大小为可用最大内存的1/8
int cacheSize = maxMemory / 8;

// 传入缓存大小实例化缓存对象
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        // 每张图片的大小单位是KB
        return bitmap.getByteCount() / 1024;
    }
};
将图片加入缓存
// 将图片加入缓存
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}
从缓存中获取图片
// 从缓存中获取图片
public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}
加载图片的异步任务
// 加载图片的异步任务
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {

    private ImageView imageView;

    public BitmapWorkerTask(ImageView imageView) {
        this.imageView = imageView;
    }

    // 在后台加载图片。
    @Override
    protected Bitmap doInBackground(Integer... params) {
        // 从资源中解析图片
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100);
        // 将图片加入缓存
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        // 返回解析出来的图片
        return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        // 设置ImageView的图片
        imageView.setImageBitmap(bitmap);
    }
}
加载图片
// 加载图片
public void loadBitmap(int resId, ImageView imageView) {
    // 将图片资源id作为Key
    final String imageKey = String.valueOf(resId);
    // 从缓存中加载图片
    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    // 如果缓存中有的话
    if (bitmap != null) {
        // 设置ImageView的图片
        imageView.setImageBitmap(bitmap);
    } else {// 如果缓存中没有
        imageView.setImageResource(R.drawable.image);
        // 传入ImageView实例化加载图片的异步任务
        BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        // 开启异步任务从资源中加载
        task.execute(resId);
    }
}

参考

Android高效加载大图、多图解决方案,有效避免程序OOM

上一篇下一篇

猜你喜欢

热点阅读