Bitmap那些事(1)

文章内容来自(需梯子)

http://developer.android.com/training/displaying-bitmaps

由于将200*200的图片读入内存,显示在20*20的ImageView上并不合算,所以需要在读入内存之前量一下Bitmap的width和height

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;

为了尽量少的使用内存,避免OOM,我们需要一个压缩版的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;
        }
    }

    return inSampleSize;
}

这里的height和width即为bitmap的实际大小,而reqWidth和reqHeight为控件大小,即所需要的大小。通过while循环保证了最后缩小的比例inSampleSize既保证了图片height和width都大于需求,而又尽量的缩到了最小比例。

于是乎,我们可以真正的把图片读入内存了,然后显示在ImageView上面。

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

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

接下来,我们要考虑如何从网络上加载图片,并且显示到正确的ImageView上面,由于ListView的RecycleBin机制(移出的子view会被复用),那么存在这样一种情况:当前子View开启了线程加载图片,而加载过程中,这个View被移出了屏幕,它被重用到最下面新移入的行,这时候会开启新线程加载另一张图片,而之前的线程加载完成之后,会在这个错误的地方显示图片,新线程加载完毕后,又将图片替换成正确图片,这就是图片乱序闪烁的原因。于是乎,我们要处理一个下这个问题。

官方文档引用了这个博客的方法

http://android-developers.blogspot.hk/2010/07/multithreading-for-performance.html

首先看一下加载图片的AsyncTask

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

关注点1:子线程压缩bitmap
关注点2:持有ImageView对象的弱应用,也就是当前ImageView对象如果没有被其他地方引用,那么就可以直接被回收
关注点3:从弱引用直接获取imageView对象,如果当前对象还没有被回收(可见),那么就显示这张图片。

加载Bitmap的代码

public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

为了解决乱序闪烁的问题,po主机智的定义了一个AsyncDrawable来扩展BitmapDrawable

static class AsyncDrawable extends BitmapDrawable {
    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =
            new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
    }

    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}

可以看到,这里同样用弱引用来引用到BitmapWorkerTask,也就是说,当没有其他地方引用到当前task对象,对象可以被系统回收。

在loadBitmap方法中,绑定了imageView对象和BitmapWorkerTask对象,双向弱引用关联。

public void loadBitmap(int resId, ImageView imageView) {
    if (cancelPotentialWork(resId, imageView)) {
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        final AsyncDrawable asyncDrawable =
                new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
        imageView.setImageDrawable(asyncDrawable);
        task.execute(resId);
    }
}

cancelPotentialWork检查当前ImageView是否绑定了其他的task对象,如果绑定了则取消之,如果绑定了同样的task则不再发起家在请求。

public static boolean cancelPotentialWork(int data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

    if (bitmapWorkerTask != null) {
        final int bitmapData = bitmapWorkerTask.data;
        // If bitmapData is not yet set or it differs from the new data
        if (bitmapData == 0 || bitmapData != data) {
            // Cancel previous task
            bitmapWorkerTask.cancel(true);
        } else {
            // The same work is already in progress
            return false;
        }
    }
    // No task associated with the ImageView, or an existing task was cancelled
    return true;
}

无非是判断一下当前是不是绑定了相同的resid,如果已有不同的请求就调一下AsyncTask的cancel方法。

这里是从imageView对象获取绑定的BitmapWorkerTask的方法

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;
}

最后在Task的onPostExecute中,来判断一下自己绑定的imageView是否还绑定着自己这个task(没有发起新请求)

如果一切顺利的话,就把这张图显示出来。

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

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

第一课就到这里,感慨一下机智的歪果仁。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值