您的位置:首页 > 移动开发 > Android开发

android bitmap缓存机制(上)

2015-03-02 00:06 120 查看
android bitmap缓存机制,网上有很多译文http://my.oschina.net/ryanhoo/blog/88153,都是大同小异,我是直接看的译文,然后根据自己的理解,把文章的结构给大家再说明一下

图片加载,首先要解决的问题是图片太大,但是我有时候需要显示的尺寸又不必太大的时候,避免浪费内存的问题

解决方法是,首先获取图片的尺寸,而不是真正的把它加入内存,根据尺寸判断是否需要加载

BitmapFactory提供了几种解码方式(decodeByteArray(), decodeFile(), decodeResource()等等),以便从多种资源中创建一个Bitmap(位图)对象。可以根据你的图片数据来源选择最合适的解码方式。这些方法视图为构造Bitmap对象分配内存,因此很容易导致OutOfMemory(OOM)异常。每一种解码方式都有额外的特征,你可以通过BitmapFactory.Options类指定解码方法。在解码图片的时候设置inJustDecodeBounds属性为true,可以避免内存分配,返回的bitmap对象为null却可以设置outWidth,
outHeight和outMimeType。这项技术允许你在创建Bitmap(并分配内存)之前读取图片的尺寸和类型。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
接着就是把大图片变小,显示我们需要的尺寸

 例如,如果最终只是要在ImageView中显示一张128*96px大小的缩略图,直接加载1024*768px的图片是非常不值得的。

        为了告诉解码器如何对图像进行采样,加载更小版本的图片,需要在BitmapFactory.Options对象中将inSampleSize设置为true。例如,一张分辨率为2048*1536px的图像使用inSampleSize值为4的设置来解码,产生的Bitmap大小约为512*384px。相较于完整图片占用12M的内存,这种方式只需0.75M内存(假设Bitmap配置为ARGB_8888)。这里有一个方法用来计算基于目标高宽的sample size的值:

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) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}


提示:使用2的次幂来设置inSampleSize值可以使解码器执行地更加迅速、更加高效。但是,如果你想在内存或者硬盘上缓存一个调整过大小的图片,通常还是解码到合适的图片尺寸更加节省空间。

        要使用这个方法,首先要使用inJustDecodeBounds为true来解码尺寸信息,将options传递过去使用新的inSampleSize值再次解码并且要将inJustDecodeBounds值设置为false。

public static 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);
}


下面再来解决异步加载问题
解决方法是使用asycntask异步加载

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;

public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}

// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
}

// Once complete, see if ImageView is still around and set bitmap.
@Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
然后是解决多线程的问题,因为如果在来回滑动时,可能会造成有两个asycntask同时在加载同一张图片,这样就造成了浪费

解决方法是,我们再加载图片时候,把图片的resourceId记录在asycntask的属性里面,并且把imageview跟这个asycntask联系起来,那么可以说一个imageview就对应一个asycntask,通过imageview,我们是可以获得替它加载图片的asycntask的

OK,接着如果我们又有一个asycntask来替这个imageview加载图片,那么我们可以判断这个asycntask的resourceId是否跟原来的一样,如果一样,就不加载了,并且把这个asycntask给cancel()掉

而asycntask跟imageview联系的桥梁,译文里面使用了一个
AsyncDrawable


上面就是具体思想,具体代码大家可以看译文
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: