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

[原创] 连载 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
分享到:
评论

相关推荐

    Xamarin.Android 非UI线程更新UI

    在Xamarin.Android开发中,非UI线程更新UI是一个常见的需求,但同时也涉及到线程安全问题。本篇文章将深入探讨如何在Xamarin.Android环境中正确地从非UI线程(也称为后台线程)更新用户界面。 首先,我们需要理解...

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

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

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

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

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

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

    工作线程 UI线程实例

    在编程领域,尤其是在Android或Java应用开发中,工作线程(Worker Thread)和用户界面线程(UI Thread)是两个非常关键的概念。正确理解和运用它们对于优化程序性能、避免阻塞用户界面至关重要。本文将通过实例来...

    android 通过服务线程改变ui

    然而,直接在非UI线程中修改UI组件(如ImageView)是不允许的,因为Android的UI工具包不是线程安全的。为了解决这个问题,Android提供了几种同步机制: 1. `Activity.runOnUiThread(Runnable)`:允许在UI线程中执行...

    C# 线程更新UI界面

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

    Android---UI篇

    •Android---UI篇---Tab Layout(选项卡布局) ...•Android---UI篇---ListView之ArrayAdapter(列表)---2 • •Android---UI篇---ListView之SimpleCursorAdapter(列表)---3 • •Android---UI篇---Menu(菜单)

    android UI界面开发图片

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

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

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

    对Android App UI线程的一点理解

    在Android应用开发中,UI线程,也被称为主线程,是负责处理用户界面更新和事件响应的核心线程。本文将深入探讨Android App UI线程的工作原理、重要性以及如何优化其性能。 首先,理解Android UI线程的工作机制至关...

    android多线程异步下载图片

    在Android中,主线程(UI线程)负责处理用户交互和界面更新,如果在这个线程执行耗时操作,比如网络请求和图片解码,会导致应用无响应(ANR)。因此,我们需要将这些任务放到后台线程(也称工作线程)中执行,这就是...

    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系统采用单...

    OkHttp简单封装,支持进度UI线程回调

    通过这个项目,开发者可以学习如何有效地整合OkHttp和Gson,实现网络请求、文件上传和下载,以及如何在Android中处理UI线程的回调。这对于提升Android应用的网络功能和用户体验具有重要的实践价值。

    Android用线程实现ImageView图片变换+可以停止和继续

    2. **AsyncTask**:Android提供了一个内置的异步任务类AsyncTask,它允许我们在后台线程中执行任务并在UI线程中更新结果。对于简单的图片加载和变换,AsyncTask是一个不错的选择,因为它简化了线程间的通信。 3. **...

    Android多线程处理 详解

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

    线程处理UI的理解Demo

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

Global site tag (gtag.js) - Google Analytics