Android ImageView高效加载大图

时间:2023-02-01 21:08:35

官方demo摘录:

计算inSampleSize的方法

 /**
* 计算inSampleSize,例如, 一个分辨率为2048x1536的图片,如果设置 inSampleSize 为4,那么会产出一个大约512x384大小的Bitmap。
* @param options
* @param reqWidth 想要压缩到的宽度
* @param reqHeight 想要压缩到的高度
* @return
*/

private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {

//图片资源文件的高
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;

//计算最大inSampleSize值是2的幂,并保持高度和宽度大于所要求的高度和宽度。
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth
) {
inSampleSize *= 2;
}
}
return inSampleSize;
}

获取Bitmap

/**
* 获取Bitmap, 是根据屏幕宽高来压缩的,可自行修改
* @param imageId 图片资源id
* @return
*/

private Bitmap decodeSampledBitmapFromResourse(int imageId) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;

//不加载图片,只获取图片的宽高和type
BitmapFactory.decodeResource(getResources(), imageId, options);

//获取屏幕宽高
DisplayMetrics metric = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metric);
int width = metric.widthPixels; // 屏幕宽度(像素)
int height = metric.heightPixels; // 屏幕高度(像素)

options.inSampleSize = calculateInSampleSize(options, width ,height);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(getResources(), imageId, options);
}

使用

  Bitmap bitmap = decodeSampledBitmapFromResourse(R.drawable.zhizhuxia);
imageView.setImageBitmap(bitmap);

当然这些处理不应该放在ui线程中

是用那个AsyncTask异步处理耗时任务:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap>{

/**
* 为ImageView使用WeakReference确保了AsyncTask所引用的资源可以被垃圾回收器回收。
* 由于当任务结束时不能确保ImageView仍然存在,因此我们必须在onPostExecute()里面对引用进行检查。
* 该ImageView在有些情况下可能已经不存在了,例如,在任务结束之前用户使用了回退操作,或者是配置发生了改变(如旋转屏幕等)。
*/

private final WeakReference<ImageView> imageViewWeakReference;
private int imageId;

public BitmapWorkerTask(ImageView imageView){
imageViewWeakReference = new WeakReference<ImageView>(imageView);
}

@Override
protected Bitmap doInBackground(Integer... params) {
imageId = params[0];

return decodeSampledBitmapFromResourse(imageId);
}

@Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewWeakReference != null && bitmap != null){
final ImageView imageView = imageViewWeakReference.get();
if (imageView != null){
imageView.setImageBitmap(bitmap);
}
}
}
}

如下使用:

BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(imageId);

为了适应多种并发操作,可做如下操作

 /**
* 异步任务为 imageView加载大图, 适用 ListView 和 gridView ,所有方法已封装,在适当的位置调用该方法即可
* @param imageId
* @param imageView
*/

public void loadBitmap(int imageId, ImageView imageView){

if (cancelPotentialWork(imageId, imageView)){
final BitmapWorkerTask bitmapWorkerTask = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable = new AsyncDrawable(bitmapWorkerTask);
imageView.setImageDrawable(asyncDrawable);
bitmapWorkerTask.execute(imageId);
}
}

/**
* 用来存储 BitmapWorkerTask 与之相应的 imageView 关联, 使用 {@link #getBitmapWorkerTask(ImageView)} 来获取任务
*/

static class AsyncDrawable extends BitmapDrawable{

private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(BitmapWorkerTask bitmapWorkerTask) {
bitmapWorkerTaskReference = new WeakReference(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask(){
return bitmapWorkerTaskReference.get();
}
}

/**
* 判定当前与imageView绑定的任务
* @param imageId
* @param imageView
* @return
*/

public static boolean cancelPotentialWork(int imageId, ImageView imageView) {

final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

if (bitmapWorkerTask != null){
final int bitmapId = bitmapWorkerTask.imageId;
if (bitmapId == 0 || bitmapId != imageId){

// 当前任务不是加载 imageId资源的异步任务,取消该任务
bitmapWorkerTask.cancel(true);
}else{

// 当前任务正在加载 imageId资源,没有其他任务占用
return false;
}
}

//没有任务与 imageView 绑定
return true;
}

/**
* 获取与ImageView相关联的 BitmapWorkerTask
* @param imageView
* @return
*/

private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView){
if (imageView != null){
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable){
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}

/**
* 异步任务,为ImageView加载大图
*/

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap>{

/**
* 为ImageView使用WeakReference确保了AsyncTask所引用的资源可以被垃圾回收器回收。
* 由于当任务结束时不能确保ImageView仍然存在,因此我们必须在onPostExecute()里面对引用进行检查。
* 该ImageView在有些情况下可能已经不存在了,例如,在任务结束之前用户使用了回退操作,或者是配置发生了改变(如旋转屏幕等)。
*/

private final WeakReference<ImageView> imageViewWeakReference;
private int imageId;

public BitmapWorkerTask(ImageView imageView){
imageViewWeakReference = new WeakReference<ImageView>(imageView);
}

@Override
protected Bitmap doInBackground(Integer... params) {
imageId = params[0];

return decodeSampledBitmapFromResourse(imageId);
}

@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()){
bitmap = null;
}
if (imageViewWeakReference != null && bitmap != null){
final ImageView imageView = imageViewWeakReference.get();
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask && imageView != null){
imageView.setImageBitmap(bitmap);
}
}
}
}

使用内存缓存存储已处理的图片(Api 12及以上 )

private LruCache<String, Bitmap> mMemoryCache;

//在onCreate中获取缓存空间
/** 内存缓存 **/
//获取最大可用的vm内存,超过这个值就会抛出内存溢出异常。
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

//使用1/8 maxMemory来作为缓存空间
final int cacheSize = maxMemory / 8;

mMemoryCache = new LruCache<String, Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {

//最低api 12,以kb为单位
return value.getByteCount() / 1024;
}

};

从缓存添加和获取bitmap方法:

/**
* 添加 bitmap到缓存中
* @param key
* @param bitmap
*/

public void addBitmapToMemoryCache(String key, Bitmap bitmap){

if (getBitmapFromLruMemCache(key) == null){
mMemoryCache.put(key, bitmap);
}
}


/**
* 从缓存中获取 Bitmap
* @param key
* @return
*/

public Bitmap getBitmapFromLruMemCache(String key){
return mMemoryCache.get(key);
}

修改BitmapWorkerTask

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...

@Override
protected Bitmap doInBackground(Integer... params) {
imageId = params[0];
final Bitmap bitmap = decodeSampledBitmapFromResourse(imageId);

//将处理后的bitmap添加到缓存中
addBitmapToMemoryCache(String.valueOf(imageId), bitmap);
return bitmap;
}
...
}

改进loadBitmap方法:

 /**
* 异步任务为 imageView加载大图, 适用 ListView 和 gridView
* @param imageId
* @param imageView
*/

public void loadBitmap(int imageId, ImageView imageView){

//首先在缓存中查找
final String imgKey = String.valueOf(imageId);
final Bitmap bitmap = getBitmapFromLruMemCache(imgKey);
if (bitmap != null){
imageView.setImageBitmap(bitmap);
}else if (cancelPotentialWork(imageId, imageView)){
final BitmapWorkerTask bitmapWorkerTask = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable = new AsyncDrawable(bitmapWorkerTask);
imageView.setImageDrawable(asyncDrawable);
bitmapWorkerTask.execute(imageId);
}
}

缓存bitmap是个重点,有待继续研究。