Android中高效的显示图片之二——在非UI线程中处理图片

时间:2021-08-18 22:51:32

在“加载大图”文章中提到的BitmapFactory.decode*方法,如果源数据是在磁盘、网络或其它任何不是在内存中的位置,那么它都不应该在UI线程中执行。因为它的加载时间不可预测且依赖于一系列因素(磁盘读写速度、图片大小、CPU频率等)。如果在主线程中执行这个操作,一旦它阻塞了主线程,就会导致系统ANR。本节介绍使用AsyncTask在后台处理图片和演示怎么处理并发问题。

一、使用一个AsyncTask

AsyncTask类提供一个简易的方法在后台线程中执行一些任务并把结果发布到UI线程。使用它只需要创建一个它的子类并重写它的几个方法即可。下面是一个使用AsyncTask和decodeSampleBitmapFromResource加载大图到ImageView的例子:

  1. class BitmapWorkerTask extends AsyncTask {
  2. private final WeakReference imageViewReference;
  3. private int data = 0;
  4. public BitmapWorkerTask(ImageView imageView) {
  5. //使用弱引用保证ImageView可以被正常回收
  6. imageViewReference = new WeakReference(imageView);
  7. }
  8. // 在后台加载图片
  9. @Override
  10. protected Bitmap doInBackground(Integer... params) {
  11. data = params[0];
  12. return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
  13. }
  14. // 一旦完成,如果ImageView还存在,将图片设置给它
  15. @Override
  16. protected void onPostExecute(Bitmap bitmap) {
  17. if (imageViewReference != null && bitmap != null) {
  18. final ImageView imageView = imageViewReference.get();
  19. if (imageView != null) {
  20. imageView.setImageBitmap(bitmap);
  21. }
  22. }
  23. }
  24. }

对ImageView使用弱引用可以确保AsyncTask不会阻止ImageView和它的任何引用被垃圾回收器回收。因为不能保证当任务完成后ImageView还存在,所以在onPostExecute()方法中需要对引用进行检查。

想要异步加载图片,只需要创建一个任务并执行它:

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

二、处理并发

上面讲述的使用AsyncTask的方法加载图片,对于有些控件例如ListView和GridView来说会产生新的问题。为了优化内存使用,当用户拖动时这些控件重复使用它的子视图。如果每个子视图触发一个AsyncTask,无法确保当它在后台执行完毕后,与它相关联的子视图没有被其它子视图重复使用。另外,也无法保证这些任务开始的顺序与它们完成的顺序一致。下面的方法可以解决这个问题:

创建一个专用的Drawable的子类用来保存任务的引用,这里使用了一个BitmapDrawable,因此当任务完成时占位图片可以在ImageView中显示。

  1. static class AsyncDrawable extends BitmapDrawable {
  2. private final WeakReference bitmapWorkerTaskReference;
  3. public AsyncDrawable(Resources res, Bitmap bitmap,
  4. BitmapWorkerTask bitmapWorkerTask) {
  5. super(res, bitmap);
  6. bitmapWorkerTaskReference =
  7. new WeakReference(bitmapWorkerTask);
  8. }
  9. public BitmapWorkerTask getBitmapWorkerTask() {
  10. return bitmapWorkerTaskReference.get();
  11. }
  12. }

在执行BitmapWorkerTask之前,创建一个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. }

上面代码中的cancelPotentialWork方法用来检查是否已经有另外一个与ImageView关联的任务正在运行。如果有,通过调用cancel()方法取消之前的任务。有一定概率,新任务的数据与已存在的一致,此时没有必要再做其他工作,下面是方法的实现:

  1. public static boolean cancelPotentialWork(int data, ImageView imageView) {
  2. final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
  3. if (bitmapWorkerTask != null) {
  4. final int bitmapData = bitmapWorkerTask.data;
  5. if (bitmapData != data) {
  6. // Cancel previous task
  7. bitmapWorkerTask.cancel(true);
  8. } else {
  9. // The same work is already in progress
  10. return false;
  11. }
  12. }
  13. // No task associated with the ImageView, or an existing task was cancelled
  14. return true;
  15. }

上面代码使用的一个helper方法getBitmapWorkerTask(),用来获取于特定ImageView相关联的任务:

  1. private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
  2. if (imageView != null) {
  3. final Drawable drawable = imageView.getDrawable();
  4. if (drawable instanceof AsyncDrawable) {
  5. final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
  6. return asyncDrawable.getBitmapWorkerTask();
  7. }
  8. }
  9. return null;
  10. }

最后一步是更新BitmapWorkerTask中的onPostExecute()方法,这样它可以检查任务是否被取消或者当前任务是否与ImageView关联:

  1. class BitmapWorkerTask extends AsyncTask {
  2. ...
  3. @Override
  4. protected void onPostExecute(Bitmap bitmap) {
  5. if (isCancelled()) {
  6. bitmap = null;
  7. }
  8. if (imageViewReference != null && bitmap != null) {
  9. final ImageView imageView = imageViewReference.get();
  10. final BitmapWorkerTask bitmapWorkerTask =
  11. getBitmapWorkerTask(imageView);
  12. if (this == bitmapWorkerTask && imageView != null) {
  13. imageView.setImageBitmap(bitmap);
  14. }
  15. }
  16. }
  17. }

这样下来就可以适用于ListView和GridView及其他任何重复利用子视图的控件。只需要在你通常给ImageView设置图片的地方调用loadBitmap方法即可。

Android中高效的显示图片之二——在非UI线程中处理图片的更多相关文章

  1. Android中高效的显示图片之三——缓存图片

    加载一张图片到UI相对比较简单,如果一次要加载一组图片,就会变得麻烦很多.像ListView,GridView,ViewPager等控件,需要显示的图片和将要显示的图片数量可能会很大. 为了减少内存使 ...

  2. Android 高级UI设计笔记17:Android在非UI线程中显示Toast

    1. 子线程的Toast怎么显示不出来? 因为Toast在创建的时候会依赖于一个Handler,并且一个Handler是需要有一个Looper才能够创建,而普通的线程是不会自动去创建一个Looper对 ...

  3. Android开之在非UI线程中更新UI

    当在非UI线程中更新UI(程序界面)时会出现例如以下图所看到的的异常: 那怎样才干在非UI线程中更细UI呢? 方法有非常多种.在这里主要介绍三种: 第一种:调用主线程mHandler的post(Run ...

  4. Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面

    Android应用的开发过程中需要把繁重的任务(IO,网络连接等)放到其他线程中异步执行,达到不阻塞UI的效果. 下面将由浅入深介绍Android进行异步处理的实现方法和系统底层的实现原理. 本文介绍 ...

  5. 一个解决在非UI线程中访问UI 异常的小方法

    写 WPF 的童鞋可能都会碰到 在非UI线程中访问 UI 异常的问题.这是为了防止数据不一致做的安全限制. 子线程中更新UI还要交给主线程更新,引用满天飞,实在是麻烦. 接下来,我们推出一个可以称之为 ...

  6. WPF非UI线程中调用App.Current.MainWindow.Dispatcher提示其他线程拥有此对象,无权使用。

    大家都知道在WPF中对非UI线程中要处理对UI有关的对象进行操作,一般需要使用委托的方式,代码基本就是下面的写法 App.Current.MainWindow.Dispatcher.Invoke(ne ...

  7. Android训练课程(Android Training) - 高效的显示图片

    高效的显示图片(Displaying BitmapsEfficiently) 了解如何使用通用的技术来处理和读取位图对象,让您的用户界面(UI)组件是可响应的,并避免超过你的应用程序内存限制的方式.如 ...

  8. Android在非UI线程中更新UI的方法

    1.使用Thread+Handler实现非UI线程更新UI界面 在UI Thread中创建Handler.用sendMessage(message)或者obtainMessage(result, ob ...

  9. 为什么在非UI线程中操作UI的改变失不安全的

    因为你如果允许在非UI线程更新操作UI的东西,那我再另一个非UI线程也可以更新这个Ui的东西 这样就会有冲突,比如你的线程刚好跑到修改UI这里,我的另一个UI也有可能跑到这里,所以这样导致线程不安全. ...

随机推荐

  1. JVM的垃圾回收机制详解和调优

    JVM的垃圾回收机制详解和调优 gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都 ...

  2. [Phalcon] Phalcon系统默认事件列表

    版本: 2.0.6 Phalcon\Mvc\Application application:boot 可终止 是 参数 Phalcon\Events\Event $event 事件本身 Phalcon ...

  3. Careercup - Facebook面试题 - 6026101998485504

    2014-05-02 10:47 题目链接 原题: Given an unordered array of positive integers, create an algorithm that ma ...

  4. fmt:formatDate标签的输出格式

    http://blog.csdn.net/lidawei201/article/details/7197834

  5. Vijos.1096 津津储蓄计划

    见问题: https://vijos.org/p/1096 主题概述 津津的零花钱一直都是自己的管理.每月初的母亲津津300美元,津津将于本月支出预算.而且总是做同样的实际支出与预算.  为了让津津学 ...

  6. xxxxxxxxxxxxxx

    一.Linux命令的分类 1.内部命令:属于Shell解释器的一部分 2.外部命令:独立于Shell解释器之外的程序 3.type命令,查看命令是外部命令还是内部命令: [root@www ~]# t ...

  7. shell 脚本——判断条件

    在之前的shell语言学习笔记中已经写过shell的几种判断语句及循环语句,也简单的介绍了shell语言判断语句和判断条件.在此再做进一步学习. test命令的测试功能 test命令用于检测系统文件及 ...

  8. windows环境下ElasticSearch5以上版本安装head插件

    我的ElasticSearch版本是5以上的,网上搜了好多安装方式,都不对. 还好找到一个成功的,转载过来做记录. 原文地址:ElasticSearch-5.0安装head插件 步骤 下载node.j ...

  9. 牛客网Java刷题知识点之为什么HashMap和HashSet区别

    不多说,直接上干货! HashMap  和  HashSet的区别是Java面试中最常被问到的问题.如果没有涉及到Collection框架以及多线程的面试,可以说是不完整.而Collection框架的 ...

  10. SAP abab 设定动态标题 SET TITLEBAR XXXX WITH XXXX

    供ALV输出EXCEL时调用. 动态地改变TITLEBAR上的文字描写叙述,详细使用方法例如以下: 1.双击TITLEBAR.进入TITLEBAR的编辑框,在这里写入&1,相当于宏定义的宏的參 ...