如何在 Android 的 ListView 中延迟加载图像
Posted
技术标签:
【中文标题】如何在 Android 的 ListView 中延迟加载图像【英文标题】:How to lazy load images in ListView in Android 【发布时间】:2010-10-07 05:13:54 【问题描述】:我正在使用ListView
来显示一些图像以及与这些图像相关联的标题。我正在从互联网上获取图像。有没有办法延迟加载图像,以便在文本显示时,UI 不会被阻止并且图像在下载时显示?
图片总数不固定。
【问题讨论】:
您可以使用GreenDroid's AsyncImageView。只需致电setUrl
。
我用过。这是一个很棒的实现。坏消息是 AsyncImageView 是大型 GreenDroid 项目的一部分,即使在您只需要 AsyncImageView 的情况下,它也会使您的应用程序变得更大。此外,GreenDroid 项目似乎自 2011 年以来没有更新。
你甚至可以试试这个库:android-http-image-manager,在我看来它最适合异步加载图像。
只要用毕加索,它会自己做。 'Picasso.with(yourContext).load(img src/path/drawable here).into(imageView 即你的目标);'就是这样!
尝试使用 :github.com/nostra13/Android-Universal-Image-Loader ,这个库对于延迟加载和图像缓存非常快速高效
【参考方案1】:
我这样做的方法是启动一个线程以在后台下载图像并将每个列表项的回调传递给它。当图像下载完成时,它会调用更新列表项视图的回调。
但是,当您回收视图时,此方法效果不佳。
【讨论】:
为每个图像使用一个线程也是我使用的方法。如果您将模型与视图分开,您可以将模型保留在 Activity 之外(如在“应用程序”类中)以保持它们的缓存。如果您有很多图像,请注意资源不足。 能否详细说明。我是android开发的新手。不过感谢您的提示 为每个图像启动一个新线程并不是一个有效的解决方案。您最终可能会在内存中出现大量线程并冻结 UI。 Fedor,同意,我通常使用队列和线程池,这是 imo 的最佳方式。【参考方案2】:这是我为保存我的应用当前显示的图像而创建的。请注意,这里使用的“Log”对象是我在 Android 中围绕最终 Log 类的自定义包装器。
package com.wilson.android.library;
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
import java.io.IOException;
public class DrawableManager
private final Map<String, Drawable> drawableMap;
public DrawableManager()
drawableMap = new HashMap<String, Drawable>();
public Drawable fetchDrawable(String urlString)
if (drawableMap.containsKey(urlString))
return drawableMap.get(urlString);
Log.d(this.getClass().getSimpleName(), "image url:" + urlString);
try
InputStream is = fetch(urlString);
Drawable drawable = Drawable.createFromStream(is, "src");
if (drawable != null)
drawableMap.put(urlString, drawable);
Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", "
+ drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", "
+ drawable.getMinimumHeight() + "," + drawable.getMinimumWidth());
else
Log.w(this.getClass().getSimpleName(), "could not get thumbnail");
return drawable;
catch (MalformedURLException e)
Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
return null;
catch (IOException e)
Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
return null;
public void fetchDrawableOnThread(final String urlString, final ImageView imageView)
if (drawableMap.containsKey(urlString))
imageView.setImageDrawable(drawableMap.get(urlString));
final Handler handler = new Handler()
@Override
public void handleMessage(Message message)
imageView.setImageDrawable((Drawable) message.obj);
;
Thread thread = new Thread()
@Override
public void run()
//TODO : set imageView to a "pending" image
Drawable drawable = fetchDrawable(urlString);
Message message = handler.obtainMessage(1, drawable);
handler.sendMessage(message);
;
thread.start();
private InputStream fetch(String urlString) throws MalformedURLException, IOException
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet request = new HttpGet(urlString);
HttpResponse response = httpClient.execute(request);
return response.getEntity().getContent();
【讨论】:
我认为您应该使用 SoftReferences,以便您的程序永远不会导致 OutOfMemoryException。由于 GC 可以在堆大小增加时清除软引用...您可以管理自己的生成,例如几秒钟后您可以将图像放入该列表,并且在加载之前您应该检查图像是否存在然后不要再次下载它而不是收集它从该列表中并将其放回您的软引用列表,一段时间后您可以清除您的硬列表:) Google Shelves 项目是一个很好的例子,看看他们是如何做到的code.google.com/p/shelves 当drawableMap包含图像时你不会错过返回...而不启动获取线程吗? 这段代码有几个问题。首先你应该缓存 Drawables,这会导致内存泄漏:***.com/questions/7648740/…。其次,缓存本身永远不会被清除,所以它会永远增长,这是另一个内存泄漏。 没有人听说过LRU Cache
developer.android.com/training/displaying-bitmaps/…【参考方案3】:
更新:请注意,此答案现在非常无效。垃圾收集器对 SoftReference 和 WeakReference 采取积极行动,因此此代码不适合新应用程序。(相反,请尝试其他答案中建议的 Universal Image Loader 之类的库。)
感谢 James 提供代码,感谢 Bao-Long 提出使用 SoftReference 的建议。我在 James 的代码上实现了 SoftReference 更改。不幸的是,SoftReferences 导致我的图像被垃圾收集得太快了。在我的情况下,没有 SoftReference 的东西很好,因为我的列表大小是有限的而且我的图像很小。
一年前有一个关于 google 组上的 SoftReferences 的讨论:link to thread。作为过早垃圾回收的解决方案,他们建议使用 dalvik.system.VMRuntime.setMinimumHeapSize() 手动设置 VM 堆大小的可能性,这对我来说不是很有吸引力。
public DrawableManager()
drawableMap = new HashMap<String, SoftReference<Drawable>>();
public Drawable fetchDrawable(String urlString)
SoftReference<Drawable> drawableRef = drawableMap.get(urlString);
if (drawableRef != null)
Drawable drawable = drawableRef.get();
if (drawable != null)
return drawable;
// Reference has expired so remove the key from drawableMap
drawableMap.remove(urlString);
if (Constants.LOGGING) Log.d(this.getClass().getSimpleName(), "image url:" + urlString);
try
InputStream is = fetch(urlString);
Drawable drawable = Drawable.createFromStream(is, "src");
drawableRef = new SoftReference<Drawable>(drawable);
drawableMap.put(urlString, drawableRef);
if (Constants.LOGGING) Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", "
+ drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", "
+ drawable.getMinimumHeight() + "," + drawable.getMinimumWidth());
return drawableRef.get();
catch (MalformedURLException e)
if (Constants.LOGGING) Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
return null;
catch (IOException e)
if (Constants.LOGGING) Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
return null;
public void fetchDrawableOnThread(final String urlString, final ImageView imageView)
SoftReference<Drawable> drawableRef = drawableMap.get(urlString);
if (drawableRef != null)
Drawable drawable = drawableRef.get();
if (drawable != null)
imageView.setImageDrawable(drawableRef.get());
return;
// Reference has expired so remove the key from drawableMap
drawableMap.remove(urlString);
final Handler handler = new Handler()
@Override
public void handleMessage(Message message)
imageView.setImageDrawable((Drawable) message.obj);
;
Thread thread = new Thread()
@Override
public void run()
//TODO : set imageView to a "pending" image
Drawable drawable = fetchDrawable(urlString);
Message message = handler.obtainMessage(1, drawable);
handler.sendMessage(message);
;
thread.start();
【讨论】:
您可以创建硬代和软代等代。你可以修复一个时间清除缓存将清除所有未在 3 秒内访问的图像。你可以看看谷歌货架项目 developer.android.com/reference/java/lang/ref/… SoftReference 文档有一个关于缓存的注释,请参阅“避免缓存的软引用”部分。大多数应用程序应该使用 android.util.LruCache 而不是软引用。 我很欣赏您的代码,但现在在新的 Android O/S 中有“激进的”垃圾收集。持有弱引用对我来说没有任何意义。 @j2emanue 你是对的,正如我试图在我的答案顶部指出的那样,SoftReferences 垃圾收集得太快了。我将尝试编辑此答案以使其更加清晰。【参考方案4】:我用图片制作了a simple demo of a lazy list(位于 GitHub)。
基本用法
ImageLoader imageLoader=new ImageLoader(context); ... imageLoader.DisplayImage(url, imageView);
不要忘记添加 对您的 AndroidManifest.xml 的以下权限:
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> Please
只创建一个 ImageLoader 实例并在你的周围重复使用它 应用。这样图像缓存会更有效率。
这可能对某人有帮助。它在后台线程中下载图像。图像缓存在 SD 卡和内存中。缓存实现非常简单,对于演示来说已经足够了。我使用 inSampleSize 解码图像以减少内存消耗。我也尝试正确处理回收的视图。
【讨论】:
【参考方案5】:Multithreading For Performance,Gilles Debunne 的教程。
这是来自 Android 开发者博客。建议的代码使用:
AsyncTasks
。
一个硬的、有限的大小,FIFO cache
。
一个软的、轻松的garbage collect
-ed 缓存。
一个占位符 Drawable
下载时。
【讨论】:
它在 2.1 中也可以正常工作。只是不要使用 AndroidHttpClient。 @thomas-ahle 谢谢,我看到 AndroidHttpClient 在 2.1 中出现错误,因为它是从 2.2 实现的,但并没有真正尝试找到其他东西来代替它。 @Adina 你说得对,我忘了。然而,配方中没有什么不能与普通的 HttpClient 一样好。 我在几个地方听说过,Google 不推荐软引用,因为与早期版本的系统相比,android 内核非常渴望收集这些引用。 你能帮忙吗? ***.com/questions/62624070/…【参考方案6】:我编写了一个教程,解释了如何在列表视图中延迟加载图像。我详细介绍了回收和并发问题。我还使用固定线程池来防止产生大量线程。
Lazy loading of images in Listview Tutorial
【讨论】:
【参考方案7】:高性能加载程序 - 在检查了此处建议的方法之后, 我使用 Ben's solution 进行了一些更改 -
我意识到使用drawable比使用位图更快,所以我使用drawable代替
使用 SoftReference 很好,但它会使缓存的图像被过于频繁地删除,所以我添加了一个包含图像引用的链接列表,防止图像被删除,直到它达到预定义的大小
为了打开 InputStream,我使用了 java.net.URLConnection,它允许我使用网络缓存(您需要先设置响应缓存,但这是另一回事)
我的代码:
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Collections;
import java.util.WeakHashMap;
import java.lang.ref.SoftReference;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import android.os.Handler;
import android.os.Message;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
public class DrawableBackgroundDownloader
private final Map<String, SoftReference<Drawable>> mCache = new HashMap<String, SoftReference<Drawable>>();
private final LinkedList <Drawable> mChacheController = new LinkedList <Drawable> ();
private ExecutorService mThreadPool;
private final Map<ImageView, String> mImageViews = Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
public static int MAX_CACHE_SIZE = 80;
public int THREAD_POOL_SIZE = 3;
/**
* Constructor
*/
public DrawableBackgroundDownloader()
mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
/**
* Clears all instance data and stops running threads
*/
public void Reset()
ExecutorService oldThreadPool = mThreadPool;
mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
oldThreadPool.shutdownNow();
mChacheController.clear();
mCache.clear();
mImageViews.clear();
public void loadDrawable(final String url, final ImageView imageView,Drawable placeholder)
mImageViews.put(imageView, url);
Drawable drawable = getDrawableFromCache(url);
// check in UI thread, so no concurrency issues
if (drawable != null)
//Log.d(null, "Item loaded from mCache: " + url);
imageView.setImageDrawable(drawable);
else
imageView.setImageDrawable(placeholder);
queueJob(url, imageView, placeholder);
private Drawable getDrawableFromCache(String url)
if (mCache.containsKey(url))
return mCache.get(url).get();
return null;
private synchronized void putDrawableInCache(String url,Drawable drawable)
int chacheControllerSize = mChacheController.size();
if (chacheControllerSize > MAX_CACHE_SIZE)
mChacheController.subList(0, MAX_CACHE_SIZE/2).clear();
mChacheController.addLast(drawable);
mCache.put(url, new SoftReference<Drawable>(drawable));
private void queueJob(final String url, final ImageView imageView,final Drawable placeholder)
/* Create handler in UI thread. */
final Handler handler = new Handler()
@Override
public void handleMessage(Message msg)
String tag = mImageViews.get(imageView);
if (tag != null && tag.equals(url))
if (imageView.isShown())
if (msg.obj != null)
imageView.setImageDrawable((Drawable) msg.obj);
else
imageView.setImageDrawable(placeholder);
//Log.d(null, "fail " + url);
;
mThreadPool.submit(new Runnable()
@Override
public void run()
final Drawable bmp = downloadDrawable(url);
// if the view is not visible anymore, the image will be ready for next time in cache
if (imageView.isShown())
Message message = Message.obtain();
message.obj = bmp;
//Log.d(null, "Item downloaded: " + url);
handler.sendMessage(message);
);
private Drawable downloadDrawable(String url)
try
InputStream is = getInputStream(url);
Drawable drawable = Drawable.createFromStream(is, url);
putDrawableInCache(url,drawable);
return drawable;
catch (MalformedURLException e)
e.printStackTrace();
catch (IOException e)
e.printStackTrace();
return null;
private InputStream getInputStream(String urlString) throws MalformedURLException, IOException
URL url = new URL(urlString);
URLConnection connection;
connection = url.openConnection();
connection.setUseCaches(true);
connection.connect();
InputStream response = connection.getInputStream();
return response;
【讨论】:
效果很好!顺便说一句,类名中有一个错字。 如果它可以节省别人的时间:import java.util.Map; import java.util.HashMap; import java.util.LinkedList; import java.util.Collections; import java.util.WeakHashMap; import java.lang.ref.SoftReference; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import android.graphics.drawable.Drawable; import android.widget.ImageView; import android.os.Handler; import android.os.Message; import java.io.InputStream; import java.net.MalformedURLException; import java.io.IOException; import java.net.URL; import java.net.URLConnection;
非常感谢,这是一个很好的实现。我还放置了一个不同的占位符来表示何时加载可绘制对象,以便用户可以获得一些反馈。
另外,我认为最好在 executorService (mThreadPool) 中使用 LIFO 队列而不是默认的 FIFO,以便首先加载最后请求的图像(可能是可见的图像)。见***.com/questions/4620061/how-to-create-lifo-executor
@MichaelReed,如果您是 Eclipse 用户,我建议您使用 Ctrl-Shift-O(这是字母 O,而不是数字 0)。它可以自动执行添加导入的过程并为您组织它们。如果您使用的是 Mac,请改用 Command-Shift-O。【参考方案8】:
我只想再添加一个很好的例子,XML Adapters。因为它被谷歌使用,我也使用相同的逻辑来避免 OutOfMemory 错误。
基本上this ImageDownloader 是您的答案(因为它涵盖了您的大部分要求)。有些你也可以在其中实现。
【讨论】:
ImageDownloader 类未得到编译:请参阅下面的解决方案code.google.com/p/parleys-android-nextgen/issues/detail?id=1【参考方案9】:我推荐开源仪器Universal Image Loader。它最初基于 Fedor Vlasov 的项目 LazyList,并从那时起得到了极大的改进。
多线程图像加载 可以广泛调整 ImageLoader 的配置(线程执行器、下载器、解码器、内存和磁盘缓存、显示图像选项等) 可以在内存和/或设备的文件系统(或 SD 卡)中缓存图像 “监听”加载过程的可能性 可以使用单独的选项自定义每个显示图像调用 小部件支持 Android 2.0+ 支持【讨论】:
【参考方案10】:看看Shutterbug,Applidium 的轻量级 SDWebImage(ios 上的一个不错的库)移植到 Android。 它支持异步缓存,存储失败的 URL,很好地处理并发,并且包含有用的子类。
也欢迎拉取请求(和错误报告)!
【讨论】:
【参考方案11】:我认为这个问题在 Android 开发人员中非常流行,并且有很多这样的库声称可以解决这个问题,但其中似乎只有少数几个。 AQuery 就是这样一个库,但它在各个方面都比大多数库都好,值得一试。
【讨论】:
【参考方案12】:我已关注此 Android 培训,我认为它在不阻塞主 UI 的情况下下载图像方面做得很好。它还处理缓存和处理许多图像的滚动:Loading Large Bitmaps Efficiently
【讨论】:
对不起,我只指出了 Google IO 应用程序的一个类(我来不及编辑)。你真的应该研究他们所有的图像加载和缓存实用程序类,你可以在same package as the cache class中找到。 是否有人建议从 iosched 应用程序的 util 文件夹中获取 DiskLruCache、Image*.java 文件以帮助处理列表视图的图像加载/缓存?我的意思是绝对值得关注有关该主题的在线开发人员指南,但这些课程(来自 iosched)在模式上走得更远。【参考方案13】:Novoda 还有一个很棒的lazy image loading library,许多应用程序,如 Songkick、Podio、SecretDJ 和 ImageSearch 都使用他们的库。
他们的图书馆在 Github 上托管 here,他们也有一个非常活跃的 issues tracker。他们的项目似乎也很活跃,在撰写此回复时已提交超过 300 次。
【讨论】:
其实 Novoda 是一个很棒的库,但是……有时您不需要一个庞大的库,只需要一个简单的解决方案。这就是为什么 Github 中的 LazyList 如此出色的原因,如果您的应用程序仅在 listView 中显示图像并且不是您的应用程序的主要功能,只是另一个我更喜欢使用更轻量级的活动。否则,如果您知道必须经常使用并且是核心的一部分,请尝试 Novoda。【参考方案14】:嗯,从 Internet 加载图像的时间有很多解决方案。您也可以使用库Android-Query。它将为您提供所有必需的活动。确定你想要做什么并阅读图书馆维基页面。并解决图片加载限制。
这是我的代码:
@Override
public View getView(int position, View convertView, ViewGroup parent)
View v = convertView;
if (v == null)
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row, null);
ImageView imageview = (ImageView) v.findViewById(R.id.icon);
AQuery aq = new AQuery(convertView);
String imageUrl = "http://www.vikispot.com/z/images/vikispot/android-w.png";
aq.id(imageview).progress(this).image(imageUrl, true, true, 0, 0, new BitmapAjaxCallback()
@Override
public void callback(String url, ImageView iv, Bitmap bm, AjaxStatus status)
iv.setImageBitmap(bm);
));
return v;
应该可以解决你的延迟加载问题。
【讨论】:
对我来说很好,但需要一个 Jar 文件才能包含在您的项目中。您可以从此处下载该 JAR 文件 AQuery androidAQuery = new AQuery(this);链接为:code.google.com/archive/p/android-query/downloads【参考方案15】:检查我的LazyList 的分叉。基本上,我通过延迟 ImageView 的调用来改进 LazyList 并创建两个方法:
-
当您需要输入“正在加载图片...”之类的内容时
当您需要显示下载的图像时。
我还通过在此对象中实现singleton 来改进 ImageLoader。
【讨论】:
【参考方案16】:DroidParts 具有 ImageFetcher,需要零配置才能开始使用。
使用磁盘和内存中的Least Recently Used (LRU) 缓存。 高效解码图像。 支持在后台线程中修改位图。 具有简单的交叉淡入淡出。 有图片加载进度回调。例如克隆DroidPartsGram:
【讨论】:
您好,我查看了代码示例,但在使用带有 ArrayAdapter 的 ImageFetcher 时遇到问题,您介意看看我的问题吗? ***.com/questions/21089147/… 谢谢=]【参考方案17】:我一直在使用来自新的 Android Volley 库 com.android.volley.toolbox.NetworkImageView
的 NetworkImageView,它似乎运行良好。显然,这与 Google Play 和其他新的 Google 应用程序中使用的视图相同。绝对值得一试。
Google I/O 2013 volley image cache tutorial
Developers Google events
【讨论】:
我认为这是最好的解决方案 - 其他答案都非常老了 - volley 非常快,并且与 jake warthons disklrucache 结合使用它是一个完美的解决方案 - 我尝试了很多其他的解决方案,但没有一个是稳定的快如凌空【参考方案18】:public class ImageDownloader
Map<String, Bitmap> imageCache;
public ImageDownloader()
imageCache = new HashMap<String, Bitmap>();
// download function
public void download(String url, ImageView imageView)
if (cancelPotentialDownload(url, imageView))
// Caching code right here
String filename = String.valueOf(url.hashCode());
File f = new File(getCacheDirectory(imageView.getContext()),
filename);
// Is the bitmap in our memory cache?
Bitmap bitmap = null;
bitmap = (Bitmap) imageCache.get(f.getPath());
if (bitmap == null)
bitmap = BitmapFactory.decodeFile(f.getPath());
if (bitmap != null)
imageCache.put(f.getPath(), bitmap);
// No? download it
if (bitmap == null)
try
BitmapDownloaderTask task = new BitmapDownloaderTask(
imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(
task);
imageView.setImageDrawable(downloadedDrawable);
task.execute(url);
catch (Exception e)
Log.e("Error==>", e.toString());
else
// Yes? set the image
imageView.setImageBitmap(bitmap);
// cancel a download (internal only)
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;
// gets an existing download if one exists for the imageview
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;
// our caching functions
// Find the dir to save cached images
private static File getCacheDirectory(Context context)
String sdState = android.os.Environment.getExternalStorageState();
File cacheDir;
if (sdState.equals(android.os.Environment.MEDIA_MOUNTED))
File sdDir = android.os.Environment.getExternalStorageDirectory();
// TODO : Change your diretcory here
cacheDir = new File(sdDir, "data/ToDo/images");
else
cacheDir = context.getCacheDir();
if (!cacheDir.exists())
cacheDir.mkdirs();
return cacheDir;
private void writeFile(Bitmap bmp, File f)
FileOutputStream out = null;
try
out = new FileOutputStream(f);
bmp.compress(Bitmap.CompressFormat.PNG, 80, out);
catch (Exception e)
e.printStackTrace();
finally
try
if (out != null)
out.close();
catch (Exception ex)
// download asynctask
public 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.
url = (String) params[0];
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();
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
// Change bitmap only if this process is still associated with
// it
if (this == bitmapDownloaderTask)
imageView.setImageBitmap(bitmap);
// cache the image
String filename = String.valueOf(url.hashCode());
File f = new File(
getCacheDirectory(imageView.getContext()), filename);
imageCache.put(f.getPath(), bitmap);
writeFile(bitmap, f);
static class DownloadedDrawable extends ColorDrawable
private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask)
super(Color.WHITE);
bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>(
bitmapDownloaderTask);
public BitmapDownloaderTask getBitmapDownloaderTask()
return bitmapDownloaderTaskReference.get();
// the actual download code
static Bitmap downloadBitmap(String url)
HttpParams params = new BasicHttpParams();
params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
HttpVersion.HTTP_1_1);
HttpClient client = new DefaultHttpClient(params);
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;
【讨论】:
【参考方案19】:我遇到了这个问题并实现了 lruCache。我相信您需要 API 12 及更高版本或使用兼容性 v4 库。 lurCache 是快速内存,但它也有预算,所以如果您担心可以使用磁盘缓存... Caching Bitmaps 中都有描述。
我现在将提供我的实现,它是一个 singleton,我可以从这样的任何地方调用:
//Where the first is a string and the other is a imageview to load.
DownloadImageTask.getInstance().loadBitmap(avatarURL, iv_avatar);
这是理想的缓存代码,然后在检索网络图像时在适配器的 getView 中调用上述代码:
public class DownloadImageTask
private LruCache<String, Bitmap> mMemoryCache;
/* Create a singleton class to call this from multiple classes */
private static DownloadImageTask instance = null;
public static DownloadImageTask getInstance()
if (instance == null)
instance = new DownloadImageTask();
return instance;
//Lock the constructor from public instances
private DownloadImageTask()
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize)
@Override
protected int sizeOf(String key, Bitmap bitmap)
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
;
public void loadBitmap(String avatarURL, ImageView imageView)
final String imageKey = String.valueOf(avatarURL);
final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null)
imageView.setImageBitmap(bitmap);
else
imageView.setImageResource(R.drawable.ic_launcher);
new DownloadImageTaskViaWeb(imageView).execute(avatarURL);
private void addBitmapToMemoryCache(String key, Bitmap bitmap)
if (getBitmapFromMemCache(key) == null)
mMemoryCache.put(key, bitmap);
private Bitmap getBitmapFromMemCache(String key)
return mMemoryCache.get(key);
/* A background process that opens a http stream and decodes a web image. */
class DownloadImageTaskViaWeb extends AsyncTask<String, Void, Bitmap>
ImageView bmImage;
public DownloadImageTaskViaWeb(ImageView bmImage)
this.bmImage = bmImage;
protected Bitmap doInBackground(String... urls)
String urldisplay = urls[0];
Bitmap mIcon = null;
try
InputStream in = new java.net.URL(urldisplay).openStream();
mIcon = BitmapFactory.decodeStream(in);
catch (Exception e)
Log.e("Error", e.getMessage());
e.printStackTrace();
addBitmapToMemoryCache(String.valueOf(urldisplay), mIcon);
return mIcon;
/* After decoding we update the view on the main UI. */
protected void onPostExecute(Bitmap result)
bmImage.setImageBitmap(result);
【讨论】:
【参考方案20】:我可以推荐一种像魅力一样工作的不同方式:Android Query。
您可以从here 下载该JAR 文件
AQuery androidAQuery = new AQuery(this);
举个例子:
androidAQuery.id(YOUR IMAGEVIEW).image(YOUR IMAGE TO LOAD, true, true, getDeviceWidth(), ANY DEFAULT IMAGE YOU WANT TO SHOW);
它非常快速和准确,使用它您可以找到更多功能,例如加载时的动画、获取位图(如果需要)等。
【讨论】:
【参考方案21】:我使用droidQuery。从 URL 加载图像有两种机制。第一个(简写)很简单:
$.with(myView).image(url);
这可以很容易地添加到ArrayAdapter
的getView(...)
方法中。
普通方法将提供更多控制权,并且具有此处甚至未讨论的选项(例如缓存和回调),但可以在此处找到将输出大小指定为 200px x 200px 的基本实现:
$.ajax(new AjaxOptions().url(url)
.type("GET")
.dataType("image")
.imageWidth(200).imageHeight(200)
.success(new Function()
@Override
public void invoke($ droidQuery, Object... params)
myImageView.setImageBitmap((Bitmap) params[0]);
)
.error(new Function()
@Override
public void invoke($ droidQuery, Object... params)
AjaxError e = (AjaxError) params[0];
Log.e("$", "Error " + e.status + ": " + e.error);
)
);
【讨论】:
【参考方案22】:您可以尝试 Aquery Android 库来延迟加载图像和列表视图...下面的代码可能对您有所帮助.....download library from here。
AQuery aq = new AQuery(mContext);
aq.id(R.id.image1).image("http://data.whicdn.com/images/63995806/original.jpg");
【讨论】:
【参考方案23】:这是 Android 上的一个常见问题,许多人已经通过多种方式解决了这个问题。在我看来,我见过的最好的解决方案是名为Picasso 的相对较新的库。以下是重点:
开源,但由Jake Wharton
的 ActionBarSherlock 成名领导。
用一行代码从网络或应用资源中异步加载图片
ListView
自动检测
自动磁盘和内存缓存
可以进行自定义转换
大量可配置选项
超级简单的 API
经常更新
【讨论】:
【参考方案24】:试试Aquery。它具有非常简单的异步加载和缓存图像的方法。
【讨论】:
【参考方案25】:URLImageViewHelper 是一个很棒的库,可以帮助您做到这一点。
【讨论】:
【参考方案26】:毕加索
使用杰克沃顿的毕加索图书馆。 (ActionBarSherlock 开发者提供的完美图片加载库)
一个强大的 Android 图像下载和缓存库。
图像为 Android 应用程序添加了急需的上下文和视觉效果。 Picasso 允许在您的应用程序中轻松加载图像 - 通常只需一行代码!
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
Picasso 会自动处理 Android 上许多常见的图像加载陷阱:
在适配器中处理 ImageView 回收和下载取消。 使用最少的内存进行复杂的图像转换。 自动内存和磁盘缓存。
Picasso Jake Wharton's Library
滑行
Glide 是一个快速高效的 Android 开源媒体管理框架,它将媒体解码、内存和磁盘缓存以及资源池封装到一个简单易用的界面中。
Glide 支持获取、解码和显示视频静止图像、图像和动画 GIF。 Glide 包含一个灵活的 API,允许开发人员插入几乎任何网络堆栈。默认情况下,Glide 使用自定义的基于 HttpUrlConnection 的堆栈,但也包含实用程序库插件到 Google 的 Volley 项目或 Square 的 OkHttp 库。
Glide.with(this).load("your-url-here").into(imageView);
Glide 的主要重点是尽可能平滑和快速地滚动任何类型的图像列表,但 Glide 也适用于几乎所有需要获取、调整大小和显示远程图像的情况。
Glide Image Loading Library
Facebook 的壁画
Fresco 是一个强大的系统,用于在 Android 应用程序中显示图像。
Fresco 负责图像加载和显示,因此您不必这样做。它将从网络、本地存储或本地资源加载图像,并显示一个占位符,直到图像到达。它有两级缓存;一个在内存中,另一个在内部存储中。
Fresco Github
在 Android 4.x 及更低版本中,Fresco 将图像放在 Android 内存的特殊区域中。这可以让您的应用程序运行得更快 - 并且更少地遭受可怕的 OutOfMemoryError。
Fresco Documentation
【讨论】:
Picasso 是 Square 开发的一个库【参考方案27】:1. Picasso 允许在您的应用程序中轻松加载图像 - 通常只需一行代码!
使用 Gradle:
implementation 'com.squareup.picasso:picasso:(insert latest version)'
只需一行代码!
Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);
2. Glide 一个专注于平滑滚动的 Android 图像加载和缓存库
使用 Gradle:
repositories
mavenCentral()
google()
dependencies
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
// 对于一个简单的视图:
Glide.with(this).load("http://i.imgur.com/DvpvklR.png").into(imageView);
3. fresco是一个强大的Android图像显示系统 applications.Fresco 负责图像加载和显示,因此您无需 到。
Getting Started with Fresco
【讨论】:
本教程可以帮助你更多的 PICASOO :- androidtutorialshub.com/… 和 GLIDE :- androidtutorialshub.com/…【参考方案28】:一些答案已经提到使用各种图像库,如 Universal Image Loader 和 androidimageloader 等。这是一个老问题,但对于仍在寻找类似东西的人来说,有 several such libraries 用于图像加载/缓存。
【讨论】:
【参考方案29】:另一种方法是通过 getView() 方法中的线程中的适配器:
Thread pics_thread = new Thread(new Runnable()
@Override
public void run()
Bitmap bitmap = getPicture(url);
if(bitmap != null)
runOnUiThread(new Runnable()
@Override
public void run()
holder.imageview.setImageBitmap(bitmap);
adapter.notifyDataSetChanged();
);
);
pics_thread.start();
当然,您应该始终缓存图像以避免额外的操作,您可以将图像放入 HashMap 数组中,检查图像是否存在于数组中,如果不存在,则继续线程或从您那里加载图像哈希映射数组。还要始终检查您是否没有泄漏内存,位图和可绘制对象通常会占用大量内存。您可以自行优化代码。
【讨论】:
我当然喜欢在不同的线程中获取位图。但是我在 getView() 中使用此代码的唯一问题是会有许多线程为多个图像运行。并且 getView 可能会尝试一次加载多个或多个图像。【参考方案30】:给犹豫不决的人提供一个快速提示:使用什么库来延迟加载图像:
有四种基本方式。
DIY => 不是最好的解决方案,但对于一些图像,如果您不想使用其他库的麻烦
Volley 的延迟加载库 => 来自 android 的人。它很好,一切都很好,但文档记录很差,因此使用起来有问题。
Picasso:一个简单有效的解决方案,您甚至可以指定要引入的确切图像大小。它使用起来非常简单,但对于必须处理的应用程序来说可能不是很“高性能”海量图片。
UIL:延迟加载图像的最佳方式。您可以缓存图像(当然需要许可),初始化一次加载器,然后完成您的工作。迄今为止我见过的最成熟的异步图片加载库。
【讨论】:
以上是关于如何在 Android 的 ListView 中延迟加载图像的主要内容,如果未能解决你的问题,请参考以下文章