Bitmap的加载和Cache

一、Bitmap的高效加载

Bitmap(位图)在Android中指的是一张图片,可以是png格式也可以是jpg等其他常见的图片格式。那么如何加载一张图片呢?BitmapFactory类提供了四类方法,decedeFile,decodeResource,decodeStream,decodeByteArray,分别用于支持从文件系统,资源,输入流以及字节数组中加载出一个Bitmap对象,其中decedeFile和decodeResource又间接调用了decodeStream方法,这四类方法最终在Android的底层实现,对应着BitmapFactory类的几个native方法。

如何高效的加载Bitmap呢?其核心思想很简单,那就是采用BitmapFactory.Options来加载所需尺寸的图片。这里假设通过ImageView来显示图片,很多时候ImageView并没有图片的原始尺寸那么大,这个时候把整个图片加载进来在设给ImageView,这显然是没有这个必要的,因为ImageView并没有办法显示原始的图片,通过BitmapFactory.Options局可以按一定的采样率来加载缩小后的图片,将缩小后的图片在ImageView中显示,这样就能降低内存占用从而在一定程度上避免OOM,提高Bitmap加载时的性能。BitmapFactory提供的加载图片的四类方法都支持BitmapFactory.Options参数,通过他们可以对图片进行采样缩放。

通过BitmapFactory.Options来缩放图片,主要是通过其inSampleSize参数,即采样率。当inSampleSize 为1时,采样图片为原始图片大小,当为2时,采样图片宽高均为原始图片1/2,而像素为原图的四分之一,其占内存也是原图的四分之一,缩放比例要是2的指数,例如:1,2,4,8,16.

如何获得采样率:

  1. 将BitmapFactory.Options的inJustDecodeBounds参数设置为true加载图片
  2. 从BitmapFactory.Options中取出图片的原始宽高信息,他们对应于outWidth,outHeight参数
  3. 根据采样率的规则并结合目标view的所需大小计算出采用率inSampleSize
  4. 将BitmapFactory.Options的inJustDecodeBounds参数设置为false,然后重新加载图片。
public class ImageResizer {
    private static final String TAG = "ImageResizer";

    public ImageResizer() {
    }

    public Bitmap decodeSampledBitmapFromResource(Resources res,
            int resId, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fd, null, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fd, null, options);
    }

    public int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {
        if (reqWidth == 0 || reqHeight == 0) {
            return 1;
        }

        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        Log.d(TAG, "origin, w= " + width + " h=" + height);
        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, "sampleSize:" + inSampleSize);
        return inSampleSize;
    }
}

使用,ImageView所希望大小是100*100像素。

imgeView.setImageBitmap(decodeSampledBitmapFromResource(getResource(),R.id.imgae,100,100));

二、Android中的缓存策略

1.LruCache

LruCache是Android3.1所提供的一个缓存类,通过support-v4可以兼容到早期的2.2版本。LruCache是一个泛型类,它内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,其提供get/put方法来完成缓存的获取和添加操作,当缓存满了,LruCache会移除较早使用的缓存对象,然后再添加新的缓存对象。

  • 强引用:直接的对象引用。
  • 软引用:当一个对象只有软引用时,系统内存不足时此对象会被gc回收。
  • 弱引用:当一个对象只有弱引用存在时,此对象随时会被gc回收。

LruCache还是线程安全的。

2.DiskLruCache

DiskLruCache用于实现存储设备缓存,即磁盘缓存,它通过将缓存对象写入文件系统从而实现缓存效果。

1.DiskLruCache的创建

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
  • directory:磁盘缓存在文件系统中的存储路径,可以是SD卡上的缓存路径,会随着程序的卸载而删除,也可以指定路径,卸载应用后任然保存。
  • appVersion:应用的版本号,版本号发生改变时,DiskLruCache会清空之前的所有缓存文件,一般设为1就好。
  • valueCount:单个节点所对应的数据的个数,一般设置为1就好
  • maxSize:缓存的总大小

下面是一个DiskLruCache创建的典型过程:

private static final long DISK_CACHE_SIZE = 1024*1024*50;
 try {

            File file = getDiskCacheDir(this,"ca");
            if(!file.exists()){
                file.mkdirs();
            }
            DiskLruCache c = DiskLruCache.open(file,1,1,DISK_CACHE_SIZE);
        } catch (IOException e) {
            e.printStackTrace();
        }


 public File getDiskCacheDir(Context context, String uniqueName) {
        boolean externalStorageAvailable = Environment
                .getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
        final String cachePath;
        if (externalStorageAvailable) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }

        return new File(cachePath + File.separator + uniqueName);
    }

2.DiskLruCache的缓存添加

将url转换成key

 private String hashKeyFormUrl(String url) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(url.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

获取Editor 对象

 private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
            throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException("can not visit network from UI Thread.");
        }
        if (mDiskLruCache == null) {
            return null;
        }

        String key = hashKeyFormUrl(url);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
        if (editor != null) {
            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
            if (downloadUrlToStream(url, outputStream)) {
                editor.commit();
            } else {
                editor.abort();
            }
            mDiskLruCache.flush();
        }
        return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
    }

从当前网络下载图片,然后通过文件流写到文件系统中

public boolean downloadUrlToStream(String urlString,
            OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;

        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(),
                    IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);

            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (IOException e) {
            Log.e(TAG, "downloadBitmap failed." + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            MyUtils.close(out);
            MyUtils.close(in);
        }
        return false;
    }

3.DiskLruCache的缓存查找

private Bitmap loadBitmapFromDiskCache(String url, int reqWidth,
            int reqHeight) throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.w(TAG, "load bitmap from UI Thread, it's not recommended!");
        }
        if (mDiskLruCache == null) {
            return null;
        }

        Bitmap bitmap = null;
        String key = hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
        if (snapShot != null) {
            FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
                    reqWidth, reqHeight);
            if (bitmap != null) {
                addBitmapToMemoryCache(key, bitmap);
            }
        }

        return bitmap;
    }

3.ImageLoader的实现

具有以下几个功能

  • 图片的同步加载
  • 图片的异步加载
  • 图片的压缩
  • 内存缓存
  • 磁盘缓存
  • 网络拉取

见书424

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值