Android官方开发文档Training系列课程中文版:高效显示位图之在非UI线程中处理图片

简介: 原文地址:http://android.xsoftlab.net/training/displaying-bitmaps/process-bitmap.html我们在上节课Load Large Bitmaps Efficiently中讨论了BitmapFactory.decode*方法,说到了不应该在UI线程中执行读取数据的过程,尤其是从磁盘或者网络上读取数据(或者其它读取速度次于内存的地方)。

原文地址:http://android.xsoftlab.net/training/displaying-bitmaps/process-bitmap.html

我们在上节课Load Large Bitmaps Efficiently中讨论了BitmapFactory.decode*方法,说到了不应该在UI线程中执行读取数据的过程,尤其是从磁盘或者网络上读取数据(或者其它读取速度次于内存的地方)。读取数据的时间是不可预料的,这取决于各种各样的因素(从磁盘或者网络读取的速度、图片的大小、CPU的功率,etc.)。如果这其中的一个因素阻塞了UI线程,那么系统会标志程序为无响应标志,并会给用户提供一个关闭的选项(请查看Designing for Responsiveness获取更多信息)。

这节课讨论了通过使用AsyncTask在非UI线程中处理位图以及展示如何处理并发问题。

使用AsyncTask

AsyncTask提供了一种简要的方式来处理后台进程的工作,并会将处理后的结果推送到UI线程中。如果要使用这个类,需要创建该类的子类,然后重写所提供的方法。这里有个例子,展示了如何使用AsyncTaskdecodeSampledBitmapFromResource()来加载一张大图到ImageView上:

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

ImageViewWeakReference可以确保AsyncTask不会阻止ImageView及它所引用的事务被垃圾回收器回收。这不能保证在任务执行完毕的时候ImageView还依然存在,所以你还必须在onPostExecute()方法中检查一下它的引用。ImageView可能已经不存在了,比如说吧,当用户离开了activity或者在任务结束的时候一些配置发生了变化。

为了启动异步任务来加载图片,需要简单的创建一个新任务并执行它:

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

处理并发

一些普通的View控件比如ListViewGridView会涉及到另一个问题,就是当与AsyncTask结合使用的时候会出现并发问题。为了能有效的使用内存,这些控件会随着用户的滑动来回收子View。如果每一个子View都会触发一个AsyncTask,那么就不能保障在任务完成的时候,与之相关联的View没有被回收利用。此外,对于顺序启动的任务也不能保障可以按顺序完成。

博客Multithreading for Performance进一步的讨论了如何处理并发,它提供了一个解决方案:在ImageView中存储了最近的AsyncTask的引用,这个引用可以在任务完成的时候对最近的AsyncTask进行检查。通过类似的办法,那么上面章节的AsyncTask可以被扩展成类似的模式。

创建一个专用的Drawable子类来存储工作任务的引用。在这种情况下,BitmapDrawable就会被用到,所以在任务完成之前可以有一个占位图显示在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) {
    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关联上了。如果是这样的话,它会通过cancel()尝试取消原来的任务。在少数情况下,新建的任务数据可能会与已经存在的任务相匹配,所以就不要有进一步的动作。下面是cancelPotentialWork()方法的实现:

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

有个辅助方法:getBitmapWorkerTask(),它被用来接收与指定ImageView相关联的任务:

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

最后一步就是在BitmapWorkerTask中更新onPostExecute(),所以它会检查任务是否已经被取消和检查当前的任务是否与与之相关联的ImageView相匹配:

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

现在这个实现就适合用到类似ListViewGridView这种会回收它们子View的组件上了,简单的调用loadBitmap()就可以正常给ImageView设置图片了。比如,在一个GridView的实现中,这个方法就可以在相应适配器的getView()方法中使用。

目录
相关文章
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
【7月更文挑战第28天】在Android开发中,确保UI流畅性至关重要。多线程与异步编程技术可将耗时操作移至后台,避免阻塞主线程。我们通常采用`Thread`类、`Handler`与`Looper`、`AsyncTask`及`ExecutorService`等进行多线程编程。
167 2
|
Java Android开发
Android面试题经典之Glide取消加载以及线程池优化
Glide通过生命周期管理在`onStop`时暂停请求,`onDestroy`时取消请求,减少资源浪费。在`EngineJob`和`DecodeJob`中使用`cancel`方法标记任务并中断数据获取。当网络请求被取消时,`HttpUrlFetcher`的`cancel`方法设置标志,之后的数据获取会返回`null`,中断加载流程。Glide还使用定制的线程池,如AnimationExecutor、diskCacheExecutor、sourceExecutor和newUnlimitedSourceExecutor,其中某些禁止网络访问,并根据CPU核心数动态调整线程数。
405 2
|
11月前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
12月前
|
API Android开发 iOS开发
深入探索Android与iOS的多线程编程差异
在移动应用开发领域,多线程编程是提高应用性能和响应性的关键。本文将对比分析Android和iOS两大平台在多线程处理上的不同实现机制,探讨它们各自的优势与局限性,并通过实例展示如何在这两个平台上进行有效的多线程编程。通过深入了解这些差异,开发者可以更好地选择适合自己项目需求的技术和策略,从而优化应用的性能和用户体验。
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
462 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
203 4
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
349 11
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
在Android开发中,为应对复杂应用场景和繁重计算任务,多线程与异步编程成为保证UI流畅性的关键。本文将介绍Android中的多线程基础,包括Thread、Handler、Looper、AsyncTask及ExecutorService等,并通过示例代码展示其实用性。AsyncTask适用于简单后台操作,而ExecutorService则能更好地管理复杂并发任务。合理运用这些技术,可显著提升应用性能和用户体验,避免内存泄漏和线程安全问题,确保UI更新顺畅。
413 5
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。

热门文章

最新文章