Android如何高效的加载图片(2)---在ui线程中处理Bitmaps
来源:互联网 发布:新概念英语网络课程 编辑:程序博客网 时间:2024/05/21 07:38
前面我们总结了Androd中如何去加载尺寸比较大的图片。现在我们需要考虑的问题是当我们从磁盘或者网络中加载图片时,由于磁盘的读取速度或者网速的原因导致话费很长的时间去加载。如果吧这些耗时的代码放在ui线程,会导致ANR异常。
所以。这篇文章中,我们将讨论使用AsyncTask在后台线程中去加载图片,并且最后将会教你如果处理并发问题。
使用AsyncTask
AsyncTask类提供了一些的方法在后台线程执行一些耗时操作,并且把最终的执行结果发布到ui线程。
AsyncTask使用步骤,首先,创建一个类去继承AsyncTask。然后重写它的一些方法,下面这断代码使用了AsyncTask和decodeSampledBitmapFromResource()(注:此方法为以合适的缩放比加载bitmap图片,具体请看上一节Android中Bitmaps 处理详解(1)中此方法的创建过程)把一张尺寸较大的图片加载进ImageView。
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { private final WeakReference<ImageView> imageViewReference; private int data = 0; public BitmapWorkerTask(ImageView imageView) { // 使用软引用是为了确保ImageView可以被及时的回收 imageViewReference = new WeakReference<ImageView>(imageView); } // 在后台线程中获取图片并转为bitmap. @Override protected Bitmap doInBackground(Integer... params) { data = params[0]; return decodeSampledBitmapFromResource(getResources(), data, 100, 100)); } // 获取到bitmap后,判断如果ImageView没有被回收,则吧图片加载进ImageView @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()方法中判断ImageView是否为null, 比如,如果用户在AsyncTask执行结束前关闭了Activity界面。
通过以上封装,我们在后台线程中加载图片可以简答的用下面的代码就可完成:
public void loadBitmap(int resId, ImageView imageView) { BitmapWorkerTask task = new BitmapWorkerTask(imageView); task.execute(resId);}
处理并发问题
常见的视图组件,如ListView和GridView,在使用前面AsyncTask操作时面对同样的问题就是,为了消耗内存,这些组件在滚动的时候会去循环利用他们的子View,如果每个子View都执行了AsyncTask,所以我们不能确定,某个item执行了AsyncTask,当Async执行完毕后,该item是否还存在,同样,我们也不能保证AsyncTask的启动顺序就是AsyncTask的完成顺序。
有国外大牛给出了解决方法,用ImageView存储最近的AsyncTask引用(如果这个现在不好理解,可窘继续往下看)。
首先,创建一个Drawable的子类来存储对任务(即AsyncTask)的引用,在本例中,使用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(); }}
我们看到上面代码中,先不管它的父类BitmapDrawable和弱引用WeakReference;我们简化上面的代码就是:
static class AsyncDrawable { BitmapWorkerTask bitmapWorkerTaskReference; public AsyncDrawable( BitmapWorkerTask bitmapWorkerTask) { bitmapWorkerTaskReference = bitmapWorkerTask; } public BitmapWorkerTask getBitmapWorkerTask() { return bitmapWorkerTaskReference; } }
通过简化的代码,我们知道,上步我们只是创建了一个类,然后在类中创建一个成员变量BitmapWorkerTask,然后添加set/get方法来设置获取BitmapWorkerTask。
回到我们的问题,我们前面说过,要把任务与ImageView绑定到一起,即把
BitmapWorkerTask和ImageView绑定到一起,测试我们想到的ImageView有setTag()和getTag()方法,可以完成,当然,这也是一种思路。
现在国外大牛提供了一种更巧妙的解决方法就是把通过ImageView的setImageDrawable()方法和getDrawable()方法来进行绑定,这样绑定的好处因为他的参数是Drawable对象,我们可以通过在Drawable的子类当中来设置任务,然后当任务执行前,ImageView就去加载Drawable子类所对应的图片,任务执行结束后,ImageView已经加载了需要去加载的图片。这样的Drawable就相当于一个placeholder占位图 ,实现了ImageView对任务的绑定。
所以我们上面类 需要去继承BitmapDrawable类。而用软引用的作用前面已经说过了,这里再不做重复说明了。
这会儿我们看看这个并发问题中处理加载图片的最终代码:
public void loadBitmap(int resId, ImageView imageView) { if (cancelPotentialWork(resId, imageView)) { final BitmapWorkerTask task = new BitmapWorkerTask(imageView); // 创建AsyncDrawable对象 final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); // 绑定ImageView与任务 imageView.setImageDrawable(asyncDrawable); task.execute(resId); }}
我们看到,在执行任务前,我们首先调用了cancelPotentialWork(resId, imageView)方法,那么这个方法是干什么用的呢?
下面为cancelPotentialWork(resId, imageView)方法的代码:
public static boolean cancelPotentialWork(int data, ImageView imageView) { // 获取当前ImageView绑定的任务 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) { // 获取到任务执行的资源Id final int bitmapData = bitmapWorkerTask.data; // 判断id是否被设置过,跟当前的id是否相同 if (bitmapData == 0 || bitmapData != data) { // 如果不相同,退出任务 bitmapWorkerTask.cancel(true); } else { // 如果相同,则说明当前的任务正在运行。 return false; } } // 当前的ImageView没有绑定任务,或者任务已经运行完成 return true;}
我们看到上面代码中有个方法,getBitmapWorkerTask(imageView),这个方法就是获取传入的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;}
下面来解释一下代码:
首先,我们会调用loadBitmap(int resId, ImageView imageView)传入要加载的图片的资源ID,和图片显示的控件去加载图片, 先去获取控件ImageView所绑定的加载任务,判断当前ImageView所绑定的任务(如果绑定了任务)中加载的图片资源Id是否与当前即将要被加载的图片资源ID相同,如果不同,则退出当前ImageView所绑定的任务,重新开始新的任务去加载当前传入的资源id。
如果当前的资源id与ImageView所绑定的任务正在加载的资源ID相同,则让它继续执行任务,不去干预。
最后我们重新去修改 BitmapWorkerTask中的onPostExecute()里面的代码如下:
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); } } }}
修改的代码主要作用是检查当前的任务是否被取消,和当前任务是否是ImageView控件所绑定的任务。
经过以上的封装,我们就可以在诸如ListView、GridView和其他任何复用子View的控件去加载图片。
请继续阅读下篇Android如何高效的加载图片(3)— 图片的缓存
- Android如何高效的加载图片(2)---在ui线程中处理Bitmaps
- 【Android Training - 09】高效地显示Bitmap图片 [ Lesson 2 - 在UI线程之外处理Bitmaps ]
- 高效地显示Bitmap图片 2 - 在UI线程之外处理Bitmaps
- Android 有效地展示图片(二)Processing Bitmaps Off the UI Thread 在ui线程外处理bitmap
- Android高性能加载大量图片系列课程2-在非UI线程中处理图片
- Android中高效的显示图片之二——在非UI线程中处理图片
- Android中高效的显示图片之二——在非UI线程中处理图片
- Android中高效的显示图片之二——在非UI线程中处理图片
- Processing Bitmaps Off the UI Thread不在UI线程中处理Bitmaps(Android官方翻译文档2)
- 在UI线程之外,多线程处理Bitmaps
- Android官方开发文档Training系列课程中文版:高效显示位图之在非UI线程中处理图片
- Android进阶练习 - 高效显示Bitmap(高效加载较大的 Bitmaps)
- 高效加载较大的 Bitmaps
- Displaying Bitmaps in Your UI在你的UI上展示Bitmaps(Android官方文档翻译——五)
- 高效使用Bitmaps(一) 大Bitmap的加载
- 高效使用Bitmaps(一) 大Bitmap的加载
- 高效使用Bitmaps(一) 大Bitmap的加载
- 高效使用Bitmaps(一) 大Bitmap的加载
- 20个高大上的linux命令
- Gray-Level Groupinig(GLG) 论文阅读与实现
- sqlserver 批量处理
- laravel CURD
- 构建 设置- 命令库——让命令调用更简单
- Android如何高效的加载图片(2)---在ui线程中处理Bitmaps
- android_系统广播大全
- html文件如何加载iOS本地图片
- 使用冒泡排序法
- 按键驱动-RK3128
- Matlab中size函数的用法
- ViewPager的预加载:setOffscreenPageLimit(int limit)
- Lua 打乱数组顺序
- Java 信号量 Semaphore 介绍