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()方法中使用。

目录
相关文章
|
6月前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
97 20
Android经典面试题之图片Bitmap怎么做优化
|
7月前
|
数据处理 开发工具 数据安全/隐私保护
Android平台RTMP推送|轻量级RTSP服务|GB28181接入之文字、png图片水印的精进之路
本文探讨了Android平台上推流模块中添加文字与PNG水印的技术演进。自2015年起,为了满足应急指挥及安防领域的需求,逐步发展出三代水印技术:第一代为静态文字与图像水印;第二代实现了动态更新水印内容的能力,例如实时位置与时间信息;至第三代,则优化了数据传输效率,直接使用Bitmap对象传递水印数据至JNI层,减少了内存拷贝次数。这些迭代不仅提升了用户体验和技术效率,也体现了开发者追求极致与不断创新的精神。
|
7月前
|
自然语言处理 定位技术 API
Android经典实战之如何获取图片的经纬度以及如何根据经纬度获取对应的地点名称
本文介绍如何在Android中从图片提取地理位置信息并转换为地址。首先利用`ExifInterface`获取图片内的经纬度,然后通过`Geocoder`将经纬度转为地址。注意操作需在子线程进行且考虑多语言支持。
360 4
|
8月前
|
XML Android开发 UED
💥Android UI设计新风尚!掌握Material Design精髓,让你的界面颜值爆表!🎨
【7月更文挑战第28天】随着移动应用市场的发展,用户对界面设计的要求不断提高。Material Design是由Google推出的设计语言,强调真实感、统一性和创新性,通过模拟纸张和墨水的物理属性创造沉浸式体验。它注重色彩、排版、图标和布局的一致性,确保跨设备的统一视觉风格。Android Studio提供了丰富的Material Design组件库,如按钮、卡片等,易于使用且美观。
225 1
|
9月前
|
XML IDE 开发工具
【Android UI】自定义带按钮的标题栏
【Android UI】自定义带按钮的标题栏
80 7
【Android UI】自定义带按钮的标题栏
|
7月前
|
XML 前端开发 Android开发
Android经典实战之Kotlin中实现圆角图片和圆形图片
本文介绍两种实现圆角图像视图的方法。第一种是通过自定义Kotlin `AppCompatImageView`,重写`onDraw`方法使用`Canvas`和`Path`进行圆角剪裁。第二种利用Android Material库中的`ShapeableImageView`,简单配置即可实现圆角效果。两种方法均易于实现且提供动态调整圆角半径的功能。
130 0
|
9月前
|
Android开发 开发者
Android UI设计中,Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等,定义在`styles.xml`。
【6月更文挑战第26天】Android UI设计中,Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等,定义在`styles.xml`。要更改主题,首先在该文件中创建新主题,如`MyAppTheme`,覆盖所需属性。然后,在`AndroidManifest.xml`中应用主题至应用或特定Activity。运行时切换主题可通过重新设置并重启Activity实现,或使用`setTheme`和`recreate()`方法。这允许开发者定制界面并与品牌指南匹配,或提供多主题选项。
133 6
|
9月前
|
开发工具 Android开发 开发者
Android `.9.png` 图像是用于UI的可拉伸格式,保持元素清晰度和比例
【6月更文挑战第26天】Android `.9.png` 图像是用于UI的可拉伸格式,保持元素清晰度和比例。通过边上的黑线定义拉伸区域,右下角黑点标识内容区域,适应文本或组件大小变化。常用于按钮、背景等,确保跨屏幕尺寸显示质量。Android SDK 提供`draw9patch.bat`工具来创建和编辑。**
275 6
|
9月前
|
API Android开发 开发者
`RecyclerView`是Android API 21引入的UI组件,用于替代ListView和GridView
【6月更文挑战第26天】`RecyclerView`是Android API 21引入的UI组件,用于替代ListView和GridView。它提供高效的数据视图复用,优化的布局管理,支持多种布局(如线性、网格),并解耦数据、适配器和视图。RecyclerView的灵活性、性能(如局部刷新和动画支持)和扩展性使其成为现代Android开发的首选,特别是在处理大规模数据集时。
105 2
|
9月前
|
Android开发 开发者
Android UI中的Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等。要更改主题
【6月更文挑战第25天】Android UI中的Theme定义了Activity的视觉风格,包括颜色、字体、窗口样式等。要更改主题,首先在`styles.xml`中定义新主题,如`MyAppTheme`,然后在`AndroidManifest.xml`中设置`android:theme`。可应用于全局或特定Activity。运行时切换主题需重置Activity,如通过`setTheme()`和`recreate()`方法。这允许开发者定制界面以匹配品牌或用户偏好。
86 2