Bitmap(四)

  Bitmap(一)  http://www.cnblogs.com/fishbone-lsy/p/4486571.html

  Bitmap(二)    http://www.cnblogs.com/fishbone-lsy/p/4496166.html

  Bitmap(三)    http://www.cnblogs.com/fishbone-lsy/p/4526922.html

  在Bitmap(一)中介绍了,Bitmap如何通过BitmapFactory获得图片资源。在Bitmap(二)中,介绍了将Bitmap进行预加载,缩放,再载入ImageView。Bitmap(三)中,介绍了通过异步任务加载图片,并使用弱引用封装ImageView。

  上面三节在加载单张图片时,已经满足基本要求了。但是,在多个图片的ListView中,那个方法就不适用了。

  上一节最后的代码是这样的

        LoadImageTask task = new LoadImageTask(imageView1 , 100 , 100);
        task.execute(R.drawable.images);

  如果我将上面一段代码放在自定义Adapter里面的getView中,每一个Item都会开启一个新的线程。由于Item在getView中会以viewHolder的形式重用,当用户疯狂地上下滑动ListView时,就会产生N个LoadImageTask在为同一个ImageView工作。这显然是非常不科学的。

  我们希望,每个ImageView只有一个属于自己的线程,如果这个ImageView被重用有了新的任务,那么它前面未加载完成的任务也应该中止掉。因此在这一节中,我们试图将ImageView和LoadImageTask绑定起来,顺便给它一个defaultBitmap用作加载过程中的显示图片。它的最终形式应该是这个样子

 public void loadBitmap(int resId, ImageView imageView , int defaultResId , int reqWidth , int reqHeight)

  使用它时,应该只需要这样子

loadBitmap(R.drawable.images2 , imageView1 , R.drawable.images , 100 , 100);

  好了,开始说怎么做。第一步,就如上面所说,我们要绑定ImageView和LoadImageTask,顺便加上defaultBitmap。所以首先使用了BitmapDrawable

    /**
     * 将Bitmap的加载task封装进BitmapDrawable中,设置默认图,并在加载完新图后换掉
     */
    private class AsyncDrawable extends BitmapDrawable{
        private final WeakReference<LoadImageTask> bitmapWorkTask ;
        public AsyncDrawable(Resources resources, Bitmap bitmap, LoadImageTask bitmapWorkTask){
            super(resources,bitmap);
            this.bitmapWorkTask = new WeakReference<LoadImageTask>(bitmapWorkTask);
        }
        public LoadImageTask getLoadImageTask(){
            return bitmapWorkTask.get();
        }
    }

在上面的代码中,通过一个弱引用,将LoadImageTask和Bitmap绑定了起来。

  然后,将封装了Bitmap和LoadImageTask的AsyncDrawable,通过imageView.setImageDrawable(asyncDrawable);直接赋给ImageView,最后再开始加载异步任务。

    /**
     * 加载图片
     * @param resId
     * @param imageView
     * @param defaultResId
     * @param reqWidth
     * @param reqHeight
     */
    public void loadBitmap(int resId, ImageView imageView , int defaultResId , int reqWidth , int reqHeight) {
        //判断任务是否正在进行
        if (cancelPotentialWork(resId, imageView)) {
            final LoadImageTask task = new LoadImageTask(imageView , reqWidth , reqHeight );
            AsyncDrawable asyncDrawable = null;
            try{
                asyncDrawable = new AsyncDrawable(getResources(), BitmapFactory.decodeResource(getResources() , defaultResId), task);
            }catch (Exception ex){
                asyncDrawable = new AsyncDrawable(getResources(), BitmapFactory.decodeResource(getResources() , R.mipmap.ic_launcher) , task);
            }
            imageView.setImageDrawable(asyncDrawable);
            task.execute(resId);
        }
    }

   这样,每次赋给ImageView的不仅仅是一张图片,而是一张默认图片、一张目标图片和一个加载目标图片的异步任务。当我们要在ImageView上开一个新任务时,可以在新开任务前判断,我要开始的任务的目标是不是和正在进行的任务目标一致,如果一致就不要新开任务了。

  那么如何进行这样的判断呢?

  首先,我们需要一个方法,能取出ImageView中的LoadImageTask,原理是取出ImageView中的Drawable,然后判断它是不是AsyncDrawable,进而取出它的LoadImageTask。

    /**
     * 获得视图当前任务
     * @param imageView
     * @return
     */
    private LoadImageTask getLoadImageTask(ImageView imageView){
        if(imageView != null ){
            final Drawable drawable = imageView.getDrawable();
            if(drawable instanceof AsyncDrawable){
                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
                return asyncDrawable.getLoadImageTask();
            }
        }
        return null;
    }

 

  然后,我们需要给异步任务类加入能够取出它当前正在进行的任务的目标的方法

    /**
     * 异步加载图像的任务类
     */
    private class LoadImageTask extends AsyncTask<Integer , Void , Bitmap>{
        private final  WeakReference imageViewReference;
        private final int mReqWidth , mReqHeight;
        private int data = 0;
        public LoadImageTask(ImageView imageView , int reqWidth , int reqHeight){
            imageViewReference = new WeakReference<ImageView>(imageView);
            mReqWidth = reqWidth;
            mReqHeight = reqHeight;
        }
        @Override
        protected Bitmap doInBackground(Integer... params) {
            data = params[0];
            return decodeSampledBitmapFromResource(getResources() , params[0],mReqWidth , mReqHeight);
        }
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            //因为目标切换等原因中止了任务
            if (isCancelled()) {
                bitmap = null;
            }
            if(bitmap!=null && imageViewReference !=null){
                final ImageView imageView = (ImageView) imageViewReference.get();
                if(imageView != null){

                    final LoadImageTask loadImageTask = getLoadImageTask(imageView);
                    if (this == loadImageTask){
                        imageView.setImageBitmap(bitmap);
                    }
                }
            }
        }
        public int getData() {
            return data;
        }
    }

值得一提的是,所谓的中止异步任务,并不是说能够在异步任务外的UI线程中,中止它。我们所能做的,只能是将异步任务中的cancel标志位设为true,真正中止它的,只能是在异步任务内,通过检测isCancelled来决定它是否要使自己无效。

  最后,是判断目标任务与正在进行中的任务是否一致的方法:

    /**
     * 通过判断任务中的数据,任务是否已经开始,或者任务目标与当前需求目标不同。
     * @param res
     * @param imageView
     * bitmapData为正在进行中的任务的目标resId
     * @return false:目标任务与正在进行的任务相同
     */
    private boolean cancelPotentialWork(int res , ImageView imageView){
        final LoadImageTask loadImageTask = getLoadImageTask(imageView);
        if (loadImageTask!=null){
            final int bitmapData = loadImageTask.getData();
            // If bitmapData is not yet set or it differs from the new data
            if (bitmapData == 0  ||  bitmapData != res ){
                loadImageTask.cancel(true);
            }
            else{
                return false;
            }
        }
        return true;
    }

该方法只有在没有进行中的任务,或者目标任务与正在进行中的任务不同时,才会返回true。然后结合loadBitmap方法,只有在返回true时,才会加载图片,开启新任务。

  

  虽然过程比较繁琐,但是用起来,就简单了!

    public void createImage(View view){
        loadBitmap(R.drawable.images2 , imageView1 , R.drawable.images , 30 , 30);
    }

  Done

最后附上一套稍微完整一点的代码

import android.app.Activity;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;

import java.lang.ref.WeakReference;

public class MainActivity extends Activity {
    private ImageView imageView1,imageView2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView1 = (ImageView) findViewById(R.id.image1);
    }

    /**
     * 计算图象的缩放比例
     * @param options   图像的参数设置接口
     * @param reqWidth  要求的宽
     * @param reqHeight 要求的高
     * @return  缩放比例,系数越大缩小的越多
     */
    private 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;
    }

    /**
     * 压缩图片
     * @param res           系统资源
     * @param resId         压缩前的资源ID
     * @param reqWidth      要求的宽
     * @param reqHeight     要求的高
     * @return  压缩后的Bitmap图
     */
    private 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);
    }

    /**
     * 异步加载图像的任务类
     */
    private class LoadImageTask extends AsyncTask<Integer , Void , Bitmap>{
        private final  WeakReference imageViewReference;
        private final int mReqWidth , mReqHeight;
        private int data = 0;
        public LoadImageTask(ImageView imageView , int reqWidth , int reqHeight){
            imageViewReference = new WeakReference<ImageView>(imageView);
            mReqWidth = reqWidth;
            mReqHeight = reqHeight;
        }
        @Override
        protected Bitmap doInBackground(Integer... params) {
            data = params[0];
            return decodeSampledBitmapFromResource(getResources() , params[0],mReqWidth , mReqHeight);
        }
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            //因为目标切换等原因中止了任务
            if (isCancelled()) {
                bitmap = null;
            }
            if(bitmap!=null && imageViewReference !=null){
                final ImageView imageView = (ImageView) imageViewReference.get();
                if(imageView != null){

                    final LoadImageTask loadImageTask = getLoadImageTask(imageView);
                    if (this == loadImageTask){
                        imageView.setImageBitmap(bitmap);
                    }

                }
            }
        }
        public int getData() {
            return data;
        }
    }

    /**
     * 将Bitmap的加载task封装进BitmapDrawable中,设置默认图,并在加载完新图后换掉
     */
    private class AsyncDrawable extends BitmapDrawable{
        private final WeakReference<LoadImageTask> bitmapWorkTask ;
        public AsyncDrawable(Resources resources, Bitmap bitmap, LoadImageTask bitmapWorkTask){
            super(resources,bitmap);
            this.bitmapWorkTask = new WeakReference<LoadImageTask>(bitmapWorkTask);
        }
        public LoadImageTask getLoadImageTask(){
            return bitmapWorkTask.get();
        }
    }

    /**
     * 加载图片
     * @param resId
     * @param imageView
     * @param defaultResId
     * @param reqWidth
     * @param reqHeight
     */
    public void loadBitmap(int resId, ImageView imageView , int defaultResId , int reqWidth , int reqHeight) {
        //判断任务是否正在进行
        if (cancelPotentialWork(resId, imageView)) {
            final LoadImageTask task = new LoadImageTask(imageView , reqWidth , reqHeight );
            AsyncDrawable asyncDrawable = null;
            try{
                asyncDrawable = new AsyncDrawable(getResources(), BitmapFactory.decodeResource(getResources() , defaultResId), task);
            }catch (Exception ex){
                asyncDrawable = new AsyncDrawable(getResources(), BitmapFactory.decodeResource(getResources() , R.mipmap.ic_launcher) , task);
            }
            imageView.setImageDrawable(asyncDrawable);
            task.execute(resId);
        }
    }

    /**
     * 通过判断任务中的数据,任务是否已经开始,或者任务目标与当前需求目标不同。
     * @param res
     * @param imageView
     * bitmapData为正在进行中的任务的目标resId
     * @return false:目标任务与正在进行的任务相同
     */
    private  boolean cancelPotentialWork(int res , ImageView imageView){
        final LoadImageTask loadImageTask = getLoadImageTask(imageView);
        if (loadImageTask!=null){
            final int bitmapData = loadImageTask.getData();
            // If bitmapData is not yet set or it differs from the new data
            if (bitmapData == 0  ||  bitmapData != res ){
                loadImageTask.cancel(true);
            }
            else{
                return false;
            }
        }
        return true;
    }

    /**
     * 获得视图当前任务
     * @param imageView
     * @return
     */
    private  LoadImageTask getLoadImageTask(ImageView imageView){
        if(imageView != null ){
            final Drawable drawable = imageView.getDrawable();
            if(drawable instanceof AsyncDrawable){
                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
                return asyncDrawable.getLoadImageTask();
            }
        }
        return null;
    }

    public void createImage(View view){
        loadBitmap(R.drawable.images2 , imageView1 , R.drawable.images , 30 , 30);
    }
}
时间: 2024-08-29 04:46:41

Bitmap(四)的相关文章

Bitmap四种属性

http://blog.csdn.net/rabbit_in_android/article/details/49967461 Bitmap: (1)     public Bitmap (int width,int height,int stride,     PixelFormat format,IntPtr scan0) 用指定的大小.像素格式和像素数据初始化 Bitmap 类的新实例. (2)     LockBits():,就是把图像的内存区域根据格式锁定,拿到那块内存的首地址.这样就

java.lang.OutOfMemoryError: bitmap size exceeds VM budget解决方法

1 BitmapFactory.decodeFile(imageFile); 用BitmapFactory解码一张图片时,有时会遇到该错误.这往往是由于图片过大造成的.要想正常使用,则需要分配更少的内存空间来存储. BitmapFactory.Options.inSampleSize 设置恰当的inSampleSize可以使BitmapFactory分配更少的空间以消除该错误.inSampleSize的具体含义请参考SDK文档.例如: 1 2 3 BitmapFactory.Options op

android 内存泄露调试

一.概述 1 二.Android(Java)中常见的容易引起内存泄漏的不良代码 1 (一) 查询数据库没有关闭游标 2 (二) 构造Adapter时,没有使用缓存的 convertView 3 (三) Bitmap对象不在使用时调用recycle()释放内存 4 (四) 释放对象的引用 4 (五) 其他 5 三.内存监测工具 DDMS --> Heap 5 四.内存分析工具 MAT(Memory Analyzer Tool) 7 (一) 生成.hprof文件 7 (二) 使用MAT导入.hpro

Android Matrix图像变换处理

Canvas类中drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)方法中有个参数类型是Matrix,从字面上理解是矩阵的意思,而实际上它也确实是个3x3的矩阵.Matrix在Android中的主要作用是图像变换,如平移.旋转.缩放.扭曲等. 关于图像如何通过矩阵进行变化可参考这篇文章图像处理-关于像素坐标矩阵变换(平移,旋转,缩放,错切) Matrix内部通过维护一个float[9]的数组来构成3x3矩阵的形式,而实际上所有的变换方法说到底

android View的测量和绘制

本篇内容来源于android 群英传(徐易生著) 我写到这里,是觉得徐易生讲的确实很好, 另外加入了一些自己的理解,便于自己基础的提高. 如果要绘制一个View , 就需要先取测量它,也就是需要知道它的大小和位置. 这样我们就能在屏幕中滑出来它了.这个过程是在onMeasure()方法中完成的. 一.测量模式 测量view的大小时,需要用到MeasureSpec (测量规范)这个类来指定测量模式 ,一共有3种 EXACTLY (精确模式) , 系统默认值. 如果我们指定控件宽高为 xxdp, x

Android 开源项目源码解析(第二期)

Android 开源项目源码解析(第二期) 阅读目录 android-Ultra-Pull-To-Refresh 源码解析 DynamicLoadApk 源码解析 NineOldAnimations 源码解析 SlidingMenu 源码解析 Cling 源码解析 BaseAdapterHelper 源码分析 Side Menu.Android 源码解析 DiscreteSeekBar 源码解析 CalendarListView 源码解析 PagerSlidingTabStrip 源码解析 公共

内存溢出(OOM)and内存泄露---及其解决

那么问题来了 什么是内存溢出out of memory?(OOM) 已有数据超出其分配内存所能存储的范围 比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出. 什么是内存泄露memory leak? 指程序在申请内存后,无法释放已申请的内存空间, 他们到底啥关系? 一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光.memory leak会最终会导致out of memory! 哪些情况经常导致内存溢出??如何解决呢?? 当项目中包含大量图片

各路搜集,分析Android内存溢出

叙述不当之处,欢迎指正. Android主要应用在嵌入式设备当中,而嵌入式设备由于一些众所周知的条件限制,通常都不会有很高的配置,特别是内存是比较有限的.如果我们编写的代 码当中有太多的对内存使用不当的地方,难免会使得我们的设备运行缓慢,甚至是死机.为了能够使得Android应用程序安全且快速的运行,Android 的每个应用程序都会使用一个专有的Dalvik虚拟机实例来运行,它是由Zygote服务进程孵化出来的,也就是说每个应用程序都是在属于自己的进程中运行的.一方面,如果程序在运行过程中出现

Xutils 源码解析【转】

原文:http://my.oschina.net/u/1538627/blog/395098 目录[-] 1. 功能介绍 2. 详细设计 2.1 View模块 2.1.1 总体设计 2.1.2 流程图 2.1.3 核心类功能介绍 请先了解注解 ,动态代理 可以帮助到您, 如果已经了解请忽略. 1.ViewUtils.java (1)主要函数 2.ViewFinder.java (1)主要函数 3.ResLoader.java 4.EventListenerManager.java 5.注解类 2