`
yhz61010
  • 浏览: 561474 次
  • 来自: -
博客专栏
63c13ecc-ef01-31cf-984e-de461c7dfde8
libgdx 游戏开发
浏览量:12247
社区版块
存档分类
最新评论

[原创] 连载 2 - 深入讨论 Android 关于高效显示图片的问题 - 如何在非 UI 线程处理位图

阅读更多
  更加详细的说明,可以参阅如下官网地址:http://developer.android.com/training/building-graphics.html

  快速导航
  1. 如何高效的加载大位图。(如何解码大位图,避免超过每个应用允许使用的最大内存)http://yhz61010.iteye.com/blog/1848337
  2. 如何在非 UI 线程处理位图。(如何使用 AsyncTask 在后台线程处理位图及处理并发问题)http://yhz61010.iteye.com/blog/1848811
  3. 如何对位图进行缓存。(如何通过创建内存缓存和磁盘缓存来流畅的显示多张位图)http://yhz61010.iteye.com/blog/1849645
  4. 如何管理位图内存。(如何针对不同的 Android 版本管理位图内存)http://yhz61010.iteye.com/blog/1850232
  5. 如何在 UI 中显示位图。(如何通过 ViewPager 和 GridView 显示多张图片)http://yhz61010.iteye.com/blog/1852927

  如何在非 UI 线程处理位图?
  前一篇文章http://yhz61010.iteye.com/blog/1848337提到的 BitmapFactory.decode* 方法,不应该在主 UI 线程被调用(除非位图来源是内存),因为加载位置的时间不可预知的,而且还依赖于很多其它因素(例如,磁盘或网络的读取时间,图片大小,CPU 功率等)。无论上述任何一个因素导致 UI 线程被阻塞,那么系统会将此程序标记成无响应状态,此时用户有权关闭该程序。

  本文将引导你如何使用 AsyncTask 在后台线程处理位图,并且将为你演示如何处理并发问题。

  使用 AsyncTask
  AsyncTask 类让我们可以使用简单的方法就可以在后台线程执行一些任务,并在处理完成后将结果反馈到 UI 线程。使用该类,需要创建一个它的子类,并覆写一些方法即可。下面为大家演示如何使用 AsyncTask 和 decodeSampledBitmapFromResource() 为 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);
            }
        }
    }
}

  其中,指向 ImageView 的 WeakReference 引用是为了确保 AsyncTask 允许对 ImageView 和任何指向它的引用进行垃圾回收。由于无法保证将后台任务执行完成时,ImageView 依然可用(例如,用户可能会在后台任务执行时离开了当前页面或者对页面进行了其它操作导致无法找到 ImageView),所以我们必须在 onPostExecute() 方法中,检查引用的可用性。

  只需向下面这样简单的创建一个任务,并执行该任务,就可以实现异步加载位图:
public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

  并发处理
  某些共通视图组件,例如 ListView 和 GridView,若像上面那样将它们和 AsyncTask 连用时,就会遇到新的问题。为了高效的利用内存,当用户在上述组件中进行滚动时,这些组件会重用其中的子视图。如果每个子视图触发一个 AsyncTask,那么我们无法保证在任务执行完成时,触发该任务的子视图没有被其它子视图使用。此处,我们也无法保证任务执行完成的顺序和调用的顺序的一致性。

  Multithreading for Performance 这篇文章(可能需要代理才能访问:http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html)深入的讨论了如何解决并发问题,并且提供了一种解决方法,就是在使用 ImageView 的地方,保存一个指向最近被使用的 AsyncTask 引用,当这个任务完成时,可以检查 AsyncTask。使用如之类似的方法,我们可以将前面使用过的 AsyncTask 进行扩展来达到我们的目前:
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 上。如果有其它正在运行的任务关联到当前的 ImageView 上,那么就可以调用 cancel() 方法来尝试取消先前的任务。在某些情况下,新任务的中的 data 和已经存在的任务的 data 相同,此时就不需要做任何处理。下面是 cancelPotentialWork 方法的具体实现:
public static boolean cancelPotentialWork(int data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

    if (bitmapWorkerTask != null) {
        final int bitmapData = bitmapWorkerTask.data;
        if (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);
            }
        }
    }
}

  综上所述,经过修改后的代码实现,可以适用于 ListView 和 GridView 组件,以及任何其它会重复使用它们子视图的组件。具体做法也很简单,只需要在原来为 ImageView 设值的地方调用 loadBitmap 方法即可。例如,在实现 GridView 时,在返回的 adapter 中的 getView() 方法中调用上述方法即可。
8
2
分享到:
评论

相关推荐

    Android-dialog库可以在任意类内调用子线程或ui线程内均可显示

    4. 处理线程问题:库会自动处理线程切换,确保在UI线程中显示Dialog,避免出现异常。 5. 监听用户操作:为Dialog的按钮或其他交互元素设置监听器,处理用户的点击事件。 在压缩包中的`AndroidDialogDemo-master`...

    qt多线程实例-数据处理和UI界面显示

    在Qt框架中,多线程技术是实现高效并发处理的关键,尤其在数据处理和用户界面(UI)更新方面。这个实例“qt多线程实例-数据处理和UI界面显示”很可能是为了展示如何在不阻塞UI的情况下进行繁重的数据处理任务。 在...

    Android----线程实现图片移动

    线程在Android中扮演着处理后台任务的重要角色,它可以避免因为长时间运行操作而阻塞主线程,确保UI的流畅性。 首先,我们需要理解Android的线程模型。主线程,也被称为UI线程,负责处理所有的用户交互,如触摸事件...

    C# 线程更新UI界面

    本篇文章将深入探讨如何通过委托在子线程中更新UI界面。 1. **多线程基础知识** - 线程:线程是程序中的执行流,每个进程至少有一个线程。 - 主线程:应用程序启动时创建的第一个线程,负责处理UI交互和事件。 -...

    android UI界面开发图片

    开发者应遵循“密度独立像素”(DP,Density-independent Pixel)的原则,确保图片在各种屏幕尺寸下显示一致。通常,需要为每个密度提供相应尺寸的图片,避免因拉伸导致的模糊。 在UI设计中,图标的设计是一大重点...

    Android中UI线程与后台线程交互的探讨.pdf

    在Android应用开发中,UI线程(也称为主线程)负责处理用户界面的交互,而后台线程则用于执行耗时操作,如网络请求、数据库操作等,以避免阻塞UI,保证用户界面的流畅性。当后台线程完成耗时操作后,通常需要将结果...

    多线程更新UI--不错的例子

    不过,由于线程安全问题,直接在非UI线程上修改UI组件可能会导致异常,因此我们需要使用`Control.Invoke`或`Control.BeginInvoke`方法来安全地在UI线程上执行更新UI的操作。 接下来,我们讨论UI更新。在Windows ...

    WPF后台线程更新UI

    在Windows Presentation Foundation(WPF)开发中,UI(用户界面)通常由主线程管理,而后台线程用于执行耗时的任务。然而,由于GUI组件不是线程安全的,直接在后台线程修改UI元素可能会导致应用程序崩溃或出现不可...

    Android从网络加载图片并显示在ImageView控件上

    在Android开发中,将网络上的图片加载到ImageView控件上是一项常见的需求,特别是在构建社交应用、电商应用或者新闻阅读类应用时。这个过程涉及到多个关键知识点,包括网络请求、图片缓存策略、线程管理以及UI更新等...

    Android 大位图压缩方法二

    在Android开发中,大位图(Bitmap)的处理是一个重要的技术点,特别是在处理高分辨率图像或者需要显示大量图片的应用中。由于Android系统对内存管理的限制,直接加载大位图可能导致应用崩溃,这就是著名的"Out of ...

    Android的线程使用来更新UI

    在Android开发中,由于UI操作必须在主线程(也称为UI线程)中进行,因此在后台线程处理任务并更新界面时需要遵循特定的机制。以下是对标题和描述中涉及知识点的详细解释: 1. **线程模型**: Android系统采用单...

    Android多线程处理 详解

    在深入探讨Android中的多线程处理之前,我们需要先理解Android的基本进程和线程模型。 **Android进程:** 当一个Android应用首次启动时,系统会为该应用创建一个Linux进程。此进程中包含了应用的主要线程——主线程...

    C#-WinForm跨线程修改UI界面的实例

    通过学习这个实例,开发者可以掌握如何在多线程环境中正确处理UI更新,避免出现线程安全问题,提高应用程序的响应性和用户体验。 总结来说,C# WinForm应用中的跨线程修改UI是通过异步编程和UI线程同步机制来实现的...

    线程处理UI的理解Demo

    在Android开发中,线程处理UI的理解是至关重要的,因为它涉及到应用程序的性能和用户体验。"线程处理UI的理解Demo"这个主题旨在通过实例演示如何在Android环境中正确地管理线程,特别是与用户界面(UI)交互时。在这...

    Android Thread线程动态操作UI的Demo

    Android系统默认的主线程(Main Thread)负责处理UI交互,如果在这个线程中执行耗时操作,会阻塞UI更新,导致应用无响应(ANR)。因此,了解如何在Android中正确地在后台线程处理任务并动态更新UI是开发者必备的技能...

    [毕设季 android开发]第六讲:Android线程及UI更新

    在Android应用开发中,线程管理和UI更新是至关重要的部分,因为Android系统为每个应用程序分配一个主线程,也称为UI线程。主线程主要负责处理用户界面的交互,包括绘制和更新视图。然而,执行耗时的操作(如网络请求...

    qt编程_在子线程中更新UI界面

    在Qt编程中,UI(用户界面)的更新通常在主线程中进行,因为GUI(图形用户界面)组件的渲染和事件处理是与主线程紧密关联的。然而,当执行耗时的操作,如网络请求、大数据处理或长时间计算时,如果在主线程中执行,...

    安卓开发-演化理解 Android 异步加载图片.zip

    这个主题“安卓开发-演化理解 Android 异步加载图片”涵盖了如何在Android平台上高效地处理图片加载,避免UI线程阻塞,提高应用性能。下面我们将详细探讨这一领域的多个知识点。 1. UI线程与工作线程: Android...

    Android高级应用源码-演化理解 Android 异步加载图片.zip

    在Android开发中,异步...通过分析这个压缩包中的源码,开发者可以深入了解Android图片异步加载的各个层面,提高自己在处理图片加载问题上的能力。无论是使用开源库还是自定义解决方案,都能从中获得宝贵的实践经验。

    C#WinForm跨线程更新UI的四种方法

    在C#编程中,开发Windows桌面应用程序时,我们经常遇到多线程操作,尤其是在WinForms应用中。当后台线程需要更新用户界面(UI)时,由于.NET Framework的安全机制,直接操作UI控件会引发“Cross-thread operation ...

Global site tag (gtag.js) - Google Analytics