转自:http://my.oschina.net/rengwuxian/blog/183802

为什么要在后台加载Bitmap?

在Android中,使用BitmapFactory.decodeResource(), BitmapFactory.decodeStream() 等方法可以把图片加载到Bitmap中。但由于这些方法是耗时的,所以多数情况下,这些方法应该放在非UI线程中,否则将有可能导致界面的卡顿,甚至是触 发ANR。

一般情况下,网络图片的加载必须放在后台线程中;而本地图片就可以根据实际情况自行决定了,如果图片不多不大的话,也可以在UI线程中操作来图个方便。至于谷歌官方的说法,是只要是从硬盘或者从网络加载Bitmap,统统不应该在主线程中进行。

基础操作:使用AsyncTask

01 class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
02     private final WeakReference<ImageView> imageViewReference;
03     private int data = 0;
04  
05     public BitmapWorkerTask(ImageView imageView) {
06         // Use a WeakReference to ensure the ImageView can be garbage collected
07         imageViewReference = new WeakReference<ImageView>(imageView);
08     }
09  
10     // Decode image in background.
11     @Override
12     protected Bitmap doInBackground(Integer... params) {
13         data = params[0];
14         return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
15     }
16  
17     // Once complete, see if ImageView is still around and set bitmap.
18     @Override
19     protected void onPostExecute(Bitmap bitmap) {
20         if (imageViewReference != null && bitmap != null) {
21             final ImageView imageView = imageViewReference.get();
22             if (imageView != null) {
23                 imageView.setImageBitmap(bitmap);
24             }
25         }
26     }
27 }

以上代码摘自Android官方文档,是一个后台加载Bitmap并在加载完成后自动将Bitmap设置到ImageView的AsyncTask的实现。有了这个AsyncTask之后,异步加载Bitmap只需要下面的简单代码:

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

然后,一句loadBitmap(R.id.my_image, mImageView) 就能实现本地图片的异步加载了。

并发操作:在ListView和GridView中进行后台加载

在实际中,影响性能的往往是ListView和GridView这种包含大量图片的控件。在滑动过程中,大量的新图片在短时间内一起被加载,对于没有进行任何优化的程序,卡顿现象必然会随之而来。通过使用后台加载Bitmap的方式,这种问题将被有效解决。具体怎么做,我们来看看谷歌推荐的方法。

首先创建一个实现了Drawable接口的类,用来存储AsyncTask的引用。在本例中,选择了继承BitmapDrawable,用来给ImageView设置一个预留的占位图,这个占位图用于在AsyncTask执行完毕之前的显示。

01 static class AsyncDrawable extends BitmapDrawable {
02     private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
03  
04     public AsyncDrawable(Resources res, Bitmap bitmap,
05             BitmapWorkerTask bitmapWorkerTask) {
06         super(res, bitmap);
07         bitmapWorkerTaskReference =
08             new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
09     }
10  
11     public BitmapWorkerTask getBitmapWorkerTask() {
12         return bitmapWorkerTaskReference.get();
13     }
14 }

接下来,和上面类似,依然是使用一个loadBitmap()方法来实现对图片的异步加载。不同的是,要在启动AsyncTask之前,把AsyncTask传给AsyncDrawable,并且使用AsyncDrawable为ImageView设置占位图:

1 public void loadBitmap(int resId, ImageView imageView) {
2     if (cancelPotentialWork(resId, imageView)) {
3         final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
4         final AsyncDrawable asyncDrawable =
5                 new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
6         imageView.setImageDrawable(asyncDrawable);
7         task.execute(resId);
8     }
9 }

然后在Adapter的getView()方法中调用loadBitmap()方法,就可以为每个Item中的ImageView进行图片的动态加载了。

loadBitmap()方法的代码中有两个地方需要注意:第一,cancelPotentialWork()这个方法,它的作用是进行两项检查:首先检查当前是否已经有一个AsyncTask正在为这个ImageView加载图片,如果没有就直接返回true。如果有,再检查这个Task正在加载的资源是否与自己正要进行加载的资源相同,如果相同,那就没有必要再进行多一次的加载了,直接返回false;而如果不同(为什么会不同?文章最后会有解释),就取消掉这个正在进行的任务,并返回true。第二个需要注意的是,本例中的 BitmapWorkerTask 实际上和上例是有所不同的。这两点我们分开说,首先我们看cancelPotentialWork()方法的代码:

01 public static boolean cancelPotentialWork(int data, ImageView imageView) {
02     final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
03  
04     if (bitmapWorkerTask != null) {
05         final int bitmapData = bitmapWorkerTask.data;
06         if (bitmapData != data) {
07             // 取消之前的任务
08             bitmapWorkerTask.cancel(true);
09         } else {
10             // 相同任务已经存在,直接返回false,不再进行重复的加载
11             return false;
12         }
13     }
14     // 没有Task和ImageView进行绑定,或者Task由于加载资源不同而被取消,返回true
15     return true;
16 }

在cancelPotentialWork()的代码中,首先使用getBitmapWorkerTask()方法获取到与ImageView相关联的Task,然后进行上面所说的判断。好,我们接着来看这个getBitmapWorkerTask()是怎么写的:

01 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
02    if (imageView != null) {
03        final Drawable drawable = imageView.getDrawable();
04        if (drawable instanceof AsyncDrawable) {
05            final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
06            return asyncDrawable.getBitmapWorkerTask();
07        }
08     }
09     return null;
10 }

从代码中可以看出,该方法通过imageView获取到与它内部的 Drawable对象,如果获取到了并且该对象为AsyncDrawable的实例,就调用这个AsyncDrawable的 getBitmapWorkerTask()方法来获取到它对应的Task,也就是通过一个 ImageView->Drawable->AsyncTask的链来获取到ImageView所对应的AsyncTask。

好的,cancelPotentialWork()方法分析完了,我们回到刚才提到的第二个点:BitmapWorkerTask类的不同。这个类的改动在于onPostExecute()方法,具体请看下面代码:

01 class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
02     ...
03  
04     @Override
05     protected void onPostExecute(Bitmap bitmap) {
06         if (isCancelled()) {
07             bitmap = null;
08         }
09  
10         if (imageViewReference != null && bitmap != null) {
11             final ImageView imageView = imageViewReference.get();
12             final BitmapWorkerTask bitmapWorkerTask =
13                     getBitmapWorkerTask(imageView);
14             if (this == bitmapWorkerTask && imageView != null) {
15                 imageView.setImageBitmap(bitmap);
16             }
17         }
18     }
19 }

从代码中可以看出,在后台加载完Bitmap之后,它 并不是直接把Bitmap设置给ImageView,而是先判断这个ImageView对应的Task是不是自己(为什么会不同?文章最后会有解释)。如果是自己,才会执行ImageView的setImageBitmap()方法。到此,一个并发的异步加载ListView(或GridView)中图片的实现全部完成。

延伸:文中两个“为什么会不同”的解答

首先,简单说一下ListView中Item和Item对应的View的关系 (GridView中同理)。假设一个ListView含有100项,那么它的100个Item应该分别对应一个View用于显示,这样一共是100个 View。但Android实际上并没有这样做。出于内存考虑,Android只会为屏幕上可见的每个Item分配一个View。用户滑动 ListView,当第一个Item移动到在可视范围外后,他所对应的View将会被系统分配给下一个即将出现的Item。

回到问题。

我们不妨假设屏幕上显示了一个ListView,并且它最多能显示10个 Item,而用户在最顶部的Item(不妨称他为第1个Item)使用Task加载Bitmap的时候进行了滑动,并且直到第1个Item消失而第11个 Item已经在屏幕底部出现的时候,这个Task还没有加载完成。那么此时,原先与第1个Item绑定的ImageView已经被重新绑定到了第11个 Item上,并且第11个Item触发了getItem()方法。在getItem()方法中,ImageView第二次使用Task为自己加载Bitmap,但这时它需要加载的图片资源已经变了(由第1个Item对应的资源变成了第11个Item对应的资源),因此在cancelPotentialWork()方法执行时会判断两个资源不一致。这就是为什么相同ImageView却对应了不同的资源。

同理,一个Task持有了一个ImageView,但由于这个Task有可能已经过时,因此这个ImageView所对应的Task未必就是这个Task本身,也有可能是另一个更年轻的Task。

转载于:https://www.cnblogs.com/xingfuzzhd/p/3470603.html

高效使用Bitmaps(二) 后台加载Bitmap相关推荐

  1. 插件化基础(二)——加载插件资源

    系列文章目录: 插件化基础(一)--加载插件的类 插件化基础(二)--加载插件资源 插件化基础(三)--启动插件组件 一.了解 Asset 和 Resources 我们加载的资源通常来自 res 和 ...

  2. 怎么禁止WordPress后台加载谷歌字体?

    怎么禁止WordPress后台加载谷歌字体?最近发现登录WordPress后台异常缓慢,经常卡在fonts.googleapis.com,这是由于Wordpress后台外链加载了谷歌字体(代码位置在w ...

  3. OpenCvSharp人脸检测(二) DNN加载Caffe模型做人脸检测

    更多视觉图像处理相关内容,可关注[OpenCV与AI深度学习]公众号获取! 本文作者Color Space,文章未经作者允许禁止转载! 本文将介绍OpenCvSharp人脸检测(二) DNN加载Caf ...

  4. 黄聪:wordpress后台加载ajax.googleapis.com导致打开速度很慢的解决方案

    黄聪:wordpress后台加载ajax.googleapis.com导致打开速度很慢的解决方案 参考文章: (1)黄聪:wordpress后台加载ajax.googleapis.com导致打开速度很 ...

  5. 高效使用Bitmaps(一) 大Bitmap的加载

    转自:http://my.oschina.net/rengwuxian/blog/182885 高效使用Bitmaps有什么好处? 我 们常常提到的"Android程序优化",通常 ...

  6. Android Glide加载Bitmap的问题

    方案一 先将Bitmap转换成字节数组,然后用Glide加载 ByteArrayOutputStream baos = new ByteArrayOutputStream();Bitmap bitma ...

  7. java类二次加载_深入理解java之类加载器

    一.类与类加载器 类加载器:实现加载阶段的第一步,通过一个类的全限定名来将这个类的二进制字节流加载进jvm. 类与类加载器:任意一个类唯一性都是由它本身和加载它的类加载器确定,两个类是否相等在它们是由 ...

  8. wordpress后台加载慢的解决方法

    进入后台/在后台操作时,会发现页面加载很慢,原因之一是wordpress使用了google字体 解决方法:安装disable google fonts插件(2021.1亲测有效) (查资料还有一个原因 ...

  9. Springboot集成BeanValidation扩展二:加载jar中的资源文件

    一.需求 今天在搭建Springboot框架的时候,又遇到一个需求:在多模块系统中,有些模块想自己管理BeanValidation的资源文件(默认是启动项目claspath下的 ValidationM ...

最新文章

  1. array用法 numpy_关于Numpy Array的使用技巧整理
  2. Redis的学习记录
  3. 力扣【接雨水问题】 leetcode-42:暴力-备忘录-双指针三种方法
  4. 软考-信息系统项目管理师-项目整体管理
  5. [Winform]WebKit.Net使用
  6. 开放搜索查询分析服务架构解读
  7. 虚拟机中centos安装gcc
  8. SAS宏技术中,%let和call symput有什么区别?
  9. CSS 图片上下部与边框有间隙
  10. 【VMCloud云平台】SCSM(六)SCSM创建服务
  11. linux时间同步ntp服务的安装与配置
  12. ACL 2019 | 清华与华为提出ERNIE:知识图谱结合BERT才是「有文化」的语言模型
  13. SPSS联合Excel进行logistic回归亚组交互效应(交互作用)的可视化分析
  14. bzoj 2827 千山鸟飞绝 平衡树
  15. php添加页脚,WordPress网站页脚footer.php修改图文教程
  16. 以太网通信连接不上自检步骤
  17. 为什么小型软件外包公司很难盈利(一)
  18. 谈谈javascript中的多线程
  19. WDF VIOLATION 你的电脑遇到问题,需要重新启动
  20. 从头开始学Linux

热门文章

  1. 基于用户投票的排名算法(五):威尔逊区间
  2. Android中shape的使用
  3. 使用MyEclipse开发Java EE应用:用XDoclet创建EJB 2 Session Bean项目(四)
  4. 思考:用开发移动app的观念来开发网站
  5. cocos2d-x 3.1.1 学习笔记[17] 关于这些活动功能
  6. Cassandra 常见错误索引
  7. ThinkPHP框架搭建网站
  8. ***程序一般的启动方式
  9. 基于xmpp openfire smack开发之smack类库介绍和使用[2]
  10. 跨系统远程登录用Linux系统远程登录windows7