• 高效地加载图片(二) 在UI线程外处理图片


    在使用BitmapFactory.decode*方法解析图片时,如果要读取的图片在SD卡上或者网络位置(或者任何内存意外的位置),则该过程不能在主线程中执行.
         因为这个过程所耗费的时间是不确定的,这个时间跟多种因素有关(从磁盘或者网络读取数据的速度,图片的大小,CPU的工作效率等).如果这其中的某一项阻塞了UI线程的执行,则就会出现ANR异常.

    使用异步任务处理图片

    AsyncTask为我们提供了在后台线程进行处理工作,并将处理的结果发布到UI线程的方法.

    要使用AsyncTask,需要创建它的子类并覆写其中的方法.这里有一个使用AsyncTask处理图片的异步任务:

    class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
        private final WeakReference<ImageView> imageViewReference;
        private int data = 0;
    
        public BitmapWorkerTask(ImageView imageView) {
    		// 此处使用弱引用,以保证这个ImageView可以被垃圾回收器回收
            imageViewReference = new WeakReference<ImageView>(imageView);
        }
    
    	// 在后台解析图片
        @Override
        protected Bitmap doInBackground(Integer... params) {
            data = params[0];
            return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
        }
    
    	// 在解析图片成功后,检查ImageView是否依然存在
    	// 如果存在,则将解析得到的Bitmap设置到ImageView中显示
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (imageViewReference != null && bitmap != null) {
                final ImageView imageView = imageViewReference.get();
                if (imageView != null) {
                    imageView.setImageBitmap(bitmap);
                }
            }
        }
    }

    要异步加载图片,只需要创建一个异步任务并且开启该任务:

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

    处理并发

    当ListView和GridView等组件与上述使用AsyncTask进行图片加载结合使用时,会出现另外一个问题.为了保证高效地使用内存,这些组件会在滑动时不断将内部的View进行复用.如果这些组件中的每个子View都开启了一个异步任务,我们不能保证在异步任务完成时,与之关联的View依然存在,这个View可能已经被复用作了其他的子View.另外,我们也不能确定异步任务完成的顺序与开启的顺序一致.

    创建一个专用的Drawable的子类,用于存放一个异步任务的引用.这样,在异步任务执行过程中,ImageView中会显示一个占位符.

    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之前,需要创建一个AsyncDrawable并且将它绑定到目标ImageView上.

    public void loadBitmap(int resId, ImageView imageView) {
    	// 检查是否已经有与当前ImageView绑定的任务存在并且执行了
    	// 如果已经有任务与当前ImageView绑定,则中断原先绑定的任务
    	// 并且将新的任务绑定到ImageView
        if (cancelPotentialWork(resId, imageView)) {
    		// 创建新的BitmapWorkerTask
            final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    		// 创建占位用的AsyncDrawable 
            final AsyncDrawable asyncDrawable =
                    new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
    		// 将占位用的AsyncDrawable设置到ImageView中
            imageView.setImageDrawable(asyncDrawable);
    		// 开始执行任务
            task.execute(resId);
        }
    }

    cancelPotentialWork方法是为了检查是否已经有异步任务与当前ImageView绑定并且执行了,如果确实有异步任务已经与当前ImageView绑定了,则调用cancel()方法结束那个异步任务(因为这个异步任务对应的ImageView已经被复用作了其他的子View,则这个异步任务获取到的Bitmap应该被废弃).

    而在少数情况下,新的任务需要加载的图片可能与原先的任务相同,则不开启新的任务也不中断原先的任务,让原先的任务继续执行.

    public static boolean cancelPotentialWork(int data, ImageView imageView) {
        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
    
        if (bitmapWorkerTask != null) {
    		// 获取到原BitmapWorkerTask的data值
            final int bitmapData = bitmapWorkerTask.data;
    		// 如果原异步任务中的data值没有设置或者与当前任务的data不同
    		// 则中断原异步任务
            if (bitmapData == 0 || bitmapData != data) {
                bitmapWorkerTask.cancel(true);
            } else {
    			// 如果原异步任务的data值与当前任务的data值相同
    			// 则返回false,此时不开启新的任务
                return false;
            }
        }
        // 如果bitmapWorkerTask为空,则原先没有异步任务与当前ImageView绑定
    	// 返回true,开启新的异步任务加载图片
        return true;
    }

    getBitmapWorkerTask()用于根据特定的ImageView来获取对应的异步任务.

    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
       if (imageView != null) {
    		//从ImageView获取Drawable
           final Drawable drawable = imageView.getDrawable();
           if (drawable instanceof AsyncDrawable) {
    			//如果获取到的Drawable类型是AsyncDrawable,则强赚
               final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
    			//从AsyncDrawable获取到绑定的异步任务
               return asyncDrawable.getBitmapWorkerTask();
           }
        }
        return null;
    }
    最后一步是在BitmapWorkerTask的onPostExecute()方法中更新UI,此时需要检查异步任务是否被中止了,并且只有在当前任务与ImageView绑定的任务匹配时才设置获取到的Bitmap.
    class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
        ...
    
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (isCancelled()) {
    			// 如果异步任务被中止了,则获取到的Bitmap废弃
                bitmap = null;
            }
    
            if (imageViewReference != null && bitmap != null) {
    			// 如果ImageView的弱引用不为空,并且获取到了Bitmap
    			// 则从弱引用获取到ImageView
                final ImageView imageView = imageViewReference.get();
    			// 根据ImageView获取异步任务
                final BitmapWorkerTask bitmapWorkerTask =
                        getBitmapWorkerTask(imageView);
    			// 将当前任务和ImageView绑定的任务对比
                if (this == bitmapWorkerTask && imageView != null) {
    				//当前任务与绑定的任务相同时,才向ImageView中设置Bitmap
                    imageView.setImageBitmap(bitmap);
                }
            }
        }
    }
  • 相关阅读:
    20145总结
    2014515 总结
    2014514 总结
    20148总结
    20147总结
    20146总结
    20149总结
    2014512 总结
    2014513 总结
    【Visual Lisp】驱动器、目录、文件和注册表
  • 原文地址:https://www.cnblogs.com/chenchong/p/3693107.html
Copyright © 2020-2023  润新知