Android 图片异步下载及缓存--Multithreading For Performance
2012-04-27 16:53
489 查看
概述
这篇文章的目的是为了解决
ListView加载来自网络的图片的性能优化。
同时学习
Android多线程与图片缓存方面的知识。
资料来源:
GillesDebunne ‘s android blog
如果无法访问,请点击这里-> 对!就这里
源码:
android-imagedownloader
如果无法访问,请点击这里-> 对!就这里
正文
想要让交互式应用程序表现的更好,UI主线程就要做技能可能少的工作。任何一个可能使你程序挂起(ANR)的长耗时的任务都应该在另外一个线程中进行处理。典型的长耗时任务就是网络操作了,它具有不可知的延迟。当处理一个长耗时的任务时,如果向用户提示任务的进度,那么他们会忍受一定时长的等待;相反地,如果程序假死在那里,会让用户变得烦躁。
这篇文章,我们创建一个简单的图片下载的程序来阐述这个模式,我们使用从网上下载的图片来填充ListView中的图标。创建一个异步的任务在后台下载图片会让我们的程序更快。
一个图片下载程序(Image Downloader)
网络上下载图片想对很简单,使用Android FrameWork提供的HTTP相关的类就可以实现。这里有个实现:
创建了一个client和HTTP request,如果返回成功,然会的实体的数据流中所包含的bitmap被解码生成Bitmap。Application 的manifest文件中需要加入INTERNET权限保证程序能够进行网络访问。
Note: a bug in the previous versions of
prevent this code from working over a slow connection. Decode a new
to fix the problem. Here is the implementation of this helper class:
着保证skip()方法能正确地跳过指定的byte数,直到达到文件的结尾。
如果在ListAdapter中的getView()方法中直接使用这个方法,滑动列表的时候会非常的卡。每一个新View的加载展现都需要等待图片下载成功,这会阻止列表的平滑滚动。
实际上,这是一个很糟糕的想法以至于AndroidHttpClient甚至不允许它在主线程中被调用。上面的代码将会展现 "This thread forbids HTTP requests"的错误消息。如果你真想自找麻烦的话,可以用DefaultHttpClient进行替换。
介绍异步任务(AsyncTask)
AsyncTask类为从UI线程中开启一个新的task提供了一种简单的实现方案。我们创建一个ImageDownloader类来负责创建这些tasks。他会提供一个download方法,他会将从制定url下载下来的图片指定给一个ImageView。
BitmapDownloaderTask是一个下载图片的AsyncTask。通过调用execute()启动,该方法在UI线程中被调用,很快地执行并返回结果,这样就达到了快速执行的目的。下面是这个类的实现:
doInBackground()方法实际上是运行在task自己独立进程中的,它只是简单地调用downloadBitmap方法,该方法我们在文章的开始出已经实现了。
onPostExecute,当task执行完毕后在调用者的UI线程中执行。他以doInBackground返回的Bitmap作为参数,并且该bitmap被关联到了ImageView控件。
注意:ImageView使用了软引用(WeakReference)这样就保证了一个进程中的下载不会阻止一个被杀死的Activity的ImageView被系统垃圾回收。这就解释了为什么在onPostExecute中使用软引用和ImageView之前要检查两者都不为null。
这个简单的例子阐述了AsyncTask的使用方法,如果你尝试,会发现这些简短的代码改善了列表的性能,现在滑动的非常流畅。 Read Painless
threading for more details on AsyncTasks.
但是,我们现在的实现暴露了ListView特有的行为缺陷,为了内存的效率因素,ListView会重复使用那些在用户滚动(score)列表过程中展现过的view。如果用户快速滑动(fling)列表一个给定的ImageView对象会被重复多次使用。每当ImageView的一次正确显示都会触发图片下载的任务,这最后会改变他的imsge。那么问题在哪?同大多数的并行程序(parallel
applications)类似,关键的问题是在有序化(ordering)。在我们这个情形下,不可能保证下载的task是按照他们开始的顺序结束的。那么很可能有这种结果,ImageView最终展示的图片是先前的某一item的图片,这个item花费了较长的时间图片下载下来。如果下载的图片只被绑定一次,并且都被指定唯一的imageview,这是没有问题的。但是我们还是在ListView中使用这种比较常见的情形中解决以下这个问题吧。
并发处理(Handling concurrency)
为了解决这个问题,我们需要记录下载的次序,保证最后一次启动请求的图片被有效地展现出来。事实上可以实现让每一个ImageView记录它最后一次的下载。我们将会为ImageView添加这个特别的信息,通过使用自定义的ImageView的子类。他将会在下载过程中临时绑定给ImageView。下面是DownloadedDrawable类:
这个实现基于ColorDrawable,他将会导致ImageView在下载过程中展示黑色的背景。当然可以使用“下载中”等提示性图片替换。再一次,注意使用了WeakReference来限制对象间的相互依赖。
下面修改原有的代码把这个类考虑进去。首先download方法会创建这个类的实例并将实例关联给ImageView。
canclePotentialDownload()方法当一个新的下载的时候,停止这个图片对应的所有可能的下载进程。注意,这个不能充分保证最新的下载就能显示,可能任务已经结束,正在等待onPostExecute()方法,这个可能在最新的下载完成后被执行。
canclePotentialDownload方法使用AsyncTask类的cancle方法来停止在进程中的下载任务。通常情况下会返回true,这样下载就可以在download方法中启动。唯一不希望这种情况发生的情境是具有相同URL的下载已经在进程中,这种情况下我们希望他继续执行。注意这种实现,当一个ImageView已经被系统回收时,与其相关联的下载并没有被停止,一个RecyclerListener可能因此需要被使用。
这个方法使用了辅助方法getBitmapDownloadTask,该方法简单易懂。
最后,onPostExecute需要做一下调整保证只有当ImageView与Download process还有关联时,将图片与ImageView进行绑定。
经过这些修改之后,ImageDownloader就能提供我们所期望的基本服务了,
这篇文章的目的是为了解决
ListView加载来自网络的图片的性能优化。
同时学习
Android多线程与图片缓存方面的知识。
资料来源:
GillesDebunne ‘s android blog
如果无法访问,请点击这里-> 对!就这里
源码:
android-imagedownloader
如果无法访问,请点击这里-> 对!就这里
正文
想要让交互式应用程序表现的更好,UI主线程就要做技能可能少的工作。任何一个可能使你程序挂起(ANR)的长耗时的任务都应该在另外一个线程中进行处理。典型的长耗时任务就是网络操作了,它具有不可知的延迟。当处理一个长耗时的任务时,如果向用户提示任务的进度,那么他们会忍受一定时长的等待;相反地,如果程序假死在那里,会让用户变得烦躁。
这篇文章,我们创建一个简单的图片下载的程序来阐述这个模式,我们使用从网上下载的图片来填充ListView中的图标。创建一个异步的任务在后台下载图片会让我们的程序更快。
一个图片下载程序(Image Downloader)
网络上下载图片想对很简单,使用Android FrameWork提供的HTTP相关的类就可以实现。这里有个实现:
static Bitmap downloadBitmap(String url) { final AndroidHttpClient client = AndroidHttpClient.newInstance("Android"); final HttpGet getRequest = new HttpGet(url); try { HttpResponse response = client.execute(getRequest); final int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url); return null; } final HttpEntity entity = response.getEntity(); if (entity != null) { InputStream inputStream = null; try { inputStream = entity.getContent(); final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); return bitmap; } finally { if (inputStream != null) { inputStream.close(); } entity.consumeContent(); } } } catch (Exception e) { // Could provide a more explicit error message for IOException or IllegalStateException getRequest.abort(); Log.w("ImageDownloader", "Error while retrieving bitmap from " + url, e.toString()); } finally { if (client != null) { client.close(); } } return null; }
创建了一个client和HTTP request,如果返回成功,然会的实体的数据流中所包含的bitmap被解码生成Bitmap。Application 的manifest文件中需要加入INTERNET权限保证程序能够进行网络访问。
Note: a bug in the previous versions of
BitmapFactory.decodeStreammay
prevent this code from working over a slow connection. Decode a new
FlushedInputStream(inputStream)instead
to fix the problem. Here is the implementation of this helper class:
static class FlushedInputStream extends FilterInputStream { public FlushedInputStream(InputStream inputStream) { super(inputStream); } @Override public long skip(long n) throws IOException { long totalBytesSkipped = 0L; while (totalBytesSkipped < n) { long bytesSkipped = in.skip(n - totalBytesSkipped); if (bytesSkipped == 0L) { int byte = read(); if (byte < 0) { break; // we reached EOF } else { bytesSkipped = 1; // we read one byte } } totalBytesSkipped += bytesSkipped; } return totalBytesSkipped; } }
着保证skip()方法能正确地跳过指定的byte数,直到达到文件的结尾。
如果在ListAdapter中的getView()方法中直接使用这个方法,滑动列表的时候会非常的卡。每一个新View的加载展现都需要等待图片下载成功,这会阻止列表的平滑滚动。
实际上,这是一个很糟糕的想法以至于AndroidHttpClient甚至不允许它在主线程中被调用。上面的代码将会展现 "This thread forbids HTTP requests"的错误消息。如果你真想自找麻烦的话,可以用DefaultHttpClient进行替换。
介绍异步任务(AsyncTask)
AsyncTask类为从UI线程中开启一个新的task提供了一种简单的实现方案。我们创建一个ImageDownloader类来负责创建这些tasks。他会提供一个download方法,他会将从制定url下载下来的图片指定给一个ImageView。
public class ImageDownloader { public void download(String url, ImageView imageView) { BitmapDownloaderTask task = new BitmapDownloaderTask(imageView); task.execute(url); } } /* class BitmapDownloaderTask, see below */ }
BitmapDownloaderTask是一个下载图片的AsyncTask。通过调用execute()启动,该方法在UI线程中被调用,很快地执行并返回结果,这样就达到了快速执行的目的。下面是这个类的实现:
class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> { private String url; private final WeakReference<ImageView> imageViewReference; public BitmapDownloaderTask(ImageView imageView) { imageViewReference = new WeakReference<ImageView>(imageView); } @Override // Actual download method, run in the task thread protected Bitmap doInBackground(String... params) { // params comes from the execute() call: params[0] is the url. return downloadBitmap(params[0]); } @Override // Once the image is downloaded, associates it to the imageView protected void onPostExecute(Bitmap bitmap) { if (isCancelled()) { bitmap = null; } if (imageViewReference != null) { ImageView imageView = imageViewReference.get(); if (imageView != null) { imageView.setImageBitmap(bitmap); } } } }
doInBackground()方法实际上是运行在task自己独立进程中的,它只是简单地调用downloadBitmap方法,该方法我们在文章的开始出已经实现了。
onPostExecute,当task执行完毕后在调用者的UI线程中执行。他以doInBackground返回的Bitmap作为参数,并且该bitmap被关联到了ImageView控件。
注意:ImageView使用了软引用(WeakReference)这样就保证了一个进程中的下载不会阻止一个被杀死的Activity的ImageView被系统垃圾回收。这就解释了为什么在onPostExecute中使用软引用和ImageView之前要检查两者都不为null。
这个简单的例子阐述了AsyncTask的使用方法,如果你尝试,会发现这些简短的代码改善了列表的性能,现在滑动的非常流畅。 Read Painless
threading for more details on AsyncTasks.
但是,我们现在的实现暴露了ListView特有的行为缺陷,为了内存的效率因素,ListView会重复使用那些在用户滚动(score)列表过程中展现过的view。如果用户快速滑动(fling)列表一个给定的ImageView对象会被重复多次使用。每当ImageView的一次正确显示都会触发图片下载的任务,这最后会改变他的imsge。那么问题在哪?同大多数的并行程序(parallel
applications)类似,关键的问题是在有序化(ordering)。在我们这个情形下,不可能保证下载的task是按照他们开始的顺序结束的。那么很可能有这种结果,ImageView最终展示的图片是先前的某一item的图片,这个item花费了较长的时间图片下载下来。如果下载的图片只被绑定一次,并且都被指定唯一的imageview,这是没有问题的。但是我们还是在ListView中使用这种比较常见的情形中解决以下这个问题吧。
并发处理(Handling concurrency)
为了解决这个问题,我们需要记录下载的次序,保证最后一次启动请求的图片被有效地展现出来。事实上可以实现让每一个ImageView记录它最后一次的下载。我们将会为ImageView添加这个特别的信息,通过使用自定义的ImageView的子类。他将会在下载过程中临时绑定给ImageView。下面是DownloadedDrawable类:
static class DownloadedDrawable extends ColorDrawable { private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference; public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) { super(Color.BLACK); bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask); } public BitmapDownloaderTask getBitmapDownloaderTask() { return bitmapDownloaderTaskReference.get(); } }
这个实现基于ColorDrawable,他将会导致ImageView在下载过程中展示黑色的背景。当然可以使用“下载中”等提示性图片替换。再一次,注意使用了WeakReference来限制对象间的相互依赖。
下面修改原有的代码把这个类考虑进去。首先download方法会创建这个类的实例并将实例关联给ImageView。
public void download(String url, ImageView imageView) { if (cancelPotentialDownload(url, imageView)) { BitmapDownloaderTask task = new BitmapDownloaderTask(imageView); DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task); imageView.setImageDrawable(downloadedDrawable); task.execute(url, cookie); } }
canclePotentialDownload()方法当一个新的下载的时候,停止这个图片对应的所有可能的下载进程。注意,这个不能充分保证最新的下载就能显示,可能任务已经结束,正在等待onPostExecute()方法,这个可能在最新的下载完成后被执行。
private static boolean cancelPotentialDownload(String url, ImageView imageView) { BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); if (bitmapDownloaderTask != null) { String bitmapUrl = bitmapDownloaderTask.url; if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) { bitmapDownloaderTask.cancel(true); } else { // The same URL is already being downloaded. return false; } } return true; }
canclePotentialDownload方法使用AsyncTask类的cancle方法来停止在进程中的下载任务。通常情况下会返回true,这样下载就可以在download方法中启动。唯一不希望这种情况发生的情境是具有相同URL的下载已经在进程中,这种情况下我们希望他继续执行。注意这种实现,当一个ImageView已经被系统回收时,与其相关联的下载并没有被停止,一个RecyclerListener可能因此需要被使用。
这个方法使用了辅助方法getBitmapDownloadTask,该方法简单易懂。
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) { if (imageView != null) { Drawable drawable = imageView.getDrawable(); if (drawable instanceof DownloadedDrawable) { DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable; return downloadedDrawable.getBitmapDownloaderTask(); } } return null; }
最后,onPostExecute需要做一下调整保证只有当ImageView与Download process还有关联时,将图片与ImageView进行绑定。
if (imageViewReference != null) { ImageView imageView = imageViewReference.get(); BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); // Change bitmap only if this process is still associated with it if (this == bitmapDownloaderTask) { imageView.setImageBitmap(bitmap); } }
经过这些修改之后,ImageDownloader就能提供我们所期望的基本服务了,
相关文章推荐
- android异步从网络下载图片,并缓存到本地
- Android异步从网络下载图片并且缓存图片到本地的demo
- Android异步从网络下载图片并且缓存图片到本地的demo
- Android异步从网络下载图片并且缓存图片到本地的demo
- Multithreading For Performance 使用异步线程优化界面效果
- Android异步下载图片并且缓存图片到本地
- Android异步从网络下载图片并且缓存图片到本地的demo
- Android 异步下载图片并缓存到本地以节约网络流量
- Android异步从网络下载图片并且缓存图片到本地的demo
- Android异步下载图片并且缓存图片到本地
- Android使用异步下载缓存图片
- Android异步下载图片并且缓存图片到本地DEMO详解
- Android:ListView异步加载图片(实现网络下载、存储本地、缓存内存、压缩显示)
- android异步下载图片缓存到sdcard
- Android: Multithreading For Performance
- android开发----异步下载图片,并且进行缓存和显示图片
- Android异步下载图片并且缓存图片到本地
- Android下载图片 图片的异步加载 和缓存存取
- Android异步下载图片并且缓存图片到本地
- Android异步下载图片并且缓存图片到本地