如何在Android的ListView中延迟加载图像

我正在使用ListView来显示与这些图像相关的一些图像和标题。我从Internet获取图像。有没有一种方法可以延迟加载图像,以便在文本显示时,UI不会被阻止,并且在下载时显示图像?

图像的总数不固定。

573530 次浏览

我这样做的方法是启动一个线程在后台下载图像,并为每个列表项给它一个回调。当图像下载完成时,它会调用回调来更新列表项的视图。

但是,当您回收视图时,此方法不能很好地工作。

这是我创建的用于保存我的应用程序当前显示的图像的内容。请注意,此处使用的“Log”对象是我在Android内部围绕最终Log类的自定义包装器。

package com.wilson.android.library;
/*Licensed to the Apache Software Foundation (ASF) under one or morecontributor license agreements.  See the NOTICE filedistributed with this work for additional informationregarding copyright ownership.  The ASF licenses this fileto you under the Apache License, Version 2.0 (the"License"); you may not use this file except in compliancewith 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 ANYKIND, either express or implied.  See the License for thespecific language governing permissions and limitationsunder 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(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message message) {imageView.setImageDrawable((Drawable) message.obj);}};
Thread thread = new Thread() {@Overridepublic void run() {//TODO : set imageView to a "pending" imageDrawable 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();}}

更新:请注意,这个答案现在非常无效。垃圾收集器对SoftReference和WeakReference采取积极行动,因此此代码不适合新应用程序。(相反,尝试像其他答案中建议的通用图像加载器这样的库。)

感谢James提供的代码,感谢宝龙提供的使用SoftReference的建议。我在James的代码上实现了SoftReference的更改。不幸的是,SoftReference导致我的图像被垃圾收集得太快。在我的情况下,没有SoftReference的东西也没关系,因为我的列表大小有限,而且我的图像很小。

一年前在google组上有一个关于SoftRe的讨论:链接到线程.作为太早的垃圾回收机制的解决方案,他们建议使用dalvik.system.VMRuntime.set最小堆大小()手动设置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 drawableMapdrawableMap.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 drawableMapdrawableMap.remove(urlString);}
final Handler handler = new Handler() {@Overridepublic void handleMessage(Message message) {imageView.setImageDrawable((Drawable) message.obj);}};
Thread thread = new Thread() {@Overridepublic void run() {//TODO : set imageView to a "pending" imageDrawable drawable = fetchDrawable(urlString);Message message = handler.obtainMessage(1, drawable);handler.sendMessage(message);}};thread.start();}

我用图像制作了一个懒惰列表的简单演示(位于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解码图像以减少内存消耗。我还尝试正确处理回收的视图。

替换文本

多线程性能,Gilles Debunne的教程。

这来自Android开发者博客。建议的代码使用:

  • AsyncTasks
  • 一个硬的,有限的尺寸,FIFO cache
  • 一个软的,易于garbage collect-ed缓存。
  • 下载时占位符Drawable

输入图片描述

我写了一个教程,解释了如何在列表视图中延迟加载图像。我详细介绍了回收和并发问题。我还使用固定线程池来防止产生大量线程。

Listview教程中图像的延迟加载

高性能加载器-在检查了这里建议的方法之后,我用Ben的解决方案做了一些修改-

  1. 我意识到使用可绘制比使用位图更快,所以我使用可绘制

  2. 使用SoftReference很棒,但它会使缓存的图像经常被删除,所以我添加了一个链接列表来保存图像引用,防止图像被删除,直到它达到预定义的大小

  3. 要打开输入流,我使用java.net.URLConnection,它允许我使用Web缓存(您需要先设置响应缓存,但那是另一回事)

我的代码:

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 issuesif (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() {@Overridepublic 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() {@Overridepublic void run() {final Drawable bmp = downloadDrawable(url);// if the view is not visible anymore, the image will be ready for next time in cacheif (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;}}

我只想再添加一个很好的例子,XML适配器。因为它被Google使用,我也使用相同的逻辑来避免OutOfMemory错误。

基本上这个ImageDownloader是您的答案(因为它涵盖了您的大部分需求)。您也可以在其中实现一些。

我推荐开源工具通用图像加载器。它最初基于Fedor Vlasov的项目懒惰列表,从那时起已经有了很大的改进。

  • 多线程图像加载
  • 广泛调整ImageLoader配置的可能性(线程执行器、下载器、解码器、内存和磁盘缓存、显示图像选项等)
  • 在内存和/或设备的文件系统(或SD卡)上缓存图像的可能性
  • 可以“听”加载过程
  • 可以使用单独的选项自定义每个显示图像调用
  • 小部件支持
  • Android 2.0+支持

看看梭特虫,Applideum的轻量级SDWebImage(iOS上的一个不错的库)移植到Android。它支持异步缓存,存储失败的URL,很好地处理并发,并包含有用的子类。

也欢迎拉取请求(和bug报告)!

我认为这个问题在Android开发人员中非常受欢迎,并且有很多这样的库声称可以解决这个问题,但其中只有少数似乎是正确的。获取是一个这样的库,但它在各个方面都比大多数库要好,值得尝试。

我遵循了这个Android培训,我认为它在下载图像而不阻塞主UI方面做得很好。它还处理缓存和滚动许多图像:高效加载大型位图

Novoda也有一个很棒的延迟图像加载库,许多应用程序,如Songick、Podio、秘书DJ和ImageSearch都使用他们的库。

他们的库托管在Github上这里,他们也有一个非常活跃的问题跟踪器。他们的项目似乎也非常活跃,在写这篇回复时有超过300次提交。

好吧,从互联网加载图像有很多解决方案。您也可以使用库Android查询功能。它会为您提供所有所需的活动。确保您想做什么并阅读库wiki页面。并解决图像加载限制。

这是我的代码:

@Overridepublic 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() {@Overridepublic void callback(String url, ImageView iv, Bitmap bm, AjaxStatus status) {iv.setImageBitmap(bm);}));
return v;}

它应该解决你的懒惰加载问题。

检查我的懒惰列表分支。基本上,我通过延迟ImageView的调用来改进LazyList并创建两个方法:

  1. 当您需要放置“加载图像…”之类的内容时
  2. 当您需要显示下载的图像时。

我还通过在此对象中实现单例来改进ImageLoader。

DroidParts图片获取,需要零配置才能开始。

  • 使用磁盘和内存最近最少使用(LRU)缓存。
  • 高效解码图像。
  • 支持在后台线程修改位图。
  • 有简单的交叉淡入淡出。
  • 具有图像加载进度回调。

CloneDroidPartGram图片类型举个例子:

在此处输入图像描述

我一直在使用新的Android Volley Librarycom.android.volley.toolbox.NetworkImageView中的NetworkImageView,它似乎运行得很好。显然,这与googleplay和其他新的Google应用程序中使用的视图相同。绝对值得一试。

public class ImageDownloader {
Map<String, Bitmap> imageCache;
public ImageDownloader() {imageCache = new HashMap<String, Bitmap>();
}
// download functionpublic void download(String url, ImageView imageView) {if (cancelPotentialDownload(url, imageView)) {
// Caching code right hereString 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 itif (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 imageimageView.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 imageviewprivate 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 imagesprivate 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 herecacheDir = new File(sdDir, "data/ToDo/images");} elsecacheDir = 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 asynctaskpublic 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 threadprotected 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 imageViewprotected 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// itif (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 codestatic 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// IllegalStateExceptiongetRequest.abort();Log.w("ImageDownloader", "Error while retrieving bitmap from "+ url + e.toString());} finally {if (client != null) {// client.close();}}return null;}}

我遇到了这个问题并实现了lruCache。我相信你需要API 12及以上版本,或者使用兼容性v4库。lurCache是快速内存,但它也有预算,所以如果你担心你可以使用磁盘缓存……这一切都在缓存位图中描述。

我现在将提供我的实现,它是我从任何地方调用的单例,如下所示:

//Where the first is a string and the other is a imageview to load.
DownloadImageTask.getInstance().loadBitmap(avatarURL, iv_avatar);

以下是缓存的理想代码,然后在检索Web图像时在适配器的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 instancesprivate 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) {@Overrideprotected 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);}}}

我可以推荐一种不同的工作方式:Android Query。

您可以从这里下载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);

它非常快速和准确,使用它,您可以在加载时找到更多功能,如动画,获取位图(如果需要)等。

我使用droidQuery。从URL加载图像有两种机制。第一个(简写)很简单:

$.with(myView).image(url);

这可以很容易地添加到ArrayAdaptergetView(...)方法中。


远程方法将提供更多的控制,并且具有此处甚至没有讨论的选项(例如缓存和回调),但可以在此处找到将输出大小指定为200px x 200px的基本实现:

$.ajax(new AjaxOptions().url(url).type("GET").dataType("image").imageWidth(200).imageHeight(200).success(new Function() {@Overridepublic void invoke($ droidQuery, Object... params) {myImageView.setImageBitmap((Bitmap) params[0]);}}).error(new Function() {@Overridepublic void invoke($ droidQuery, Object... params) {AjaxError e = (AjaxError) params[0];Log.e("$", "Error " + e.status + ": " + e.error);}}));

您可以尝试AQuery安卓库来延迟加载图像和列表视图…下面的代码可能会帮助您……从这里下载库

AQuery aq = new AQuery(mContext);aq.id(R.id.image1).image("http://data.whicdn.com/images/63995806/original.jpg");

这是Android上的一个常见问题,已经被很多人以多种方式解决。在我看来,我见过的最好的解决方案是名为毕加索的相对较新的库。以下是亮点:

  • 开源,但以行动酒吧夏洛克名气中的Jake Wharton为首。
  • 使用一行代码从网络或应用程序资源异步加载图像
  • 自动ListView检测
  • 自动磁盘和内存缓存
  • 可以进行自定义转换
  • 大量可配置选项
  • 超级简单的API
  • 经常更新

试试AQuery。它有非常简单的方法来异步加载和缓存图像。

URLImageViewHelper查看辅助工具列表是一个很棒的库,可以帮助你做到这一点。

毕加索

使用杰克·沃顿的毕加索图书馆。(来自ActionBarSherlock开发人员的完美图像加载库)

适用于Android的强大图像下载和缓存库。

图像为Android应用程序添加了急需的上下文和视觉风格。Picasso允许在您的应用程序中轻松加载图像-通常只需一行代码!

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

在Android上加载图像的许多常见缺陷由Picasso自动处理:

在适配器中处理ImageView回收和下载取消。使用最少内存的复杂图像转换。自动内存和磁盘缓存。

毕加索杰克·沃顿的图书馆

滑翔

Glide是一个快速高效的Android开源媒体管理框架,它将媒体解码、内存和磁盘缓存以及资源池包装成一个简单易用的界面。

Glide支持获取、解码和显示视频剧照、图像和动画GIF。Glide包括一个灵活的API,允许开发人员插入几乎任何网络堆栈。默认情况下,Glide使用基于HttpUrlConnection的自定义堆栈,但也包括Google Volley项目或Square OkHttp库的实用程序库插件。

Glide.with(this).load("your-url-here").into(imageView);

Glide的主要重点是使滚动任何类型的图像列表尽可能平滑和快速,但Glide也适用于几乎任何需要获取,调整大小和显示远程图像的情况。

滑翔图像加载库

Facebook的壁画

Fresco是一个功能强大的系统,用于在Android应用程序中显示图像。

Fresco负责图像加载和显示,所以你不必这样做。它将从网络、本地存储或本地资源加载图像,并显示占位符,直到图像到达。它有两个级别的缓存;一个在内存中,另一个在内部存储中。

壁画Github

在Android 4. x及更低版本中,Fresco将图像放在Android内存的特殊区域中。这让您的应用程序运行得更快-并且更少地遭受可怕的OutOfMemoryError。

壁画文档

1.毕加索允许在您的应用程序中轻松加载图像-通常只需一行代码!

使用Gradle:

implementation 'com.squareup.picasso:picasso:(insert latest version)'

只有一行代码!

Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);

2.滑翔一个专注于平滑滚动的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.<强>壁画是一个在Android中显示图像的强大系统应用程序。弗雷斯科负责图像加载和显示,所以你不必到。

开始使用壁画

一些答案已经提到使用各种图像库,如Universal Image Loader和androidemageloader等。这是一个古老的问题,但对于任何仍然在寻找这样的东西的人来说,有几个这样的图书馆用于图像加载/缓存。

另一种方法是通过getView()方法中线程中的适配器:

Thread pics_thread = new Thread(new Runnable() {@Overridepublic void run() {Bitmap bitmap = getPicture(url);if(bitmap != null) {runOnUiThread(new Runnable() {@Overridepublic void run() {holder.imageview.setImageBitmap(bitmap);adapter.notifyDataSetChanged();}});}}});
pics_thread.start();

当然,你应该总是缓存你的图像以避免额外的操作,你可以把你的图像放在一个HashMap数组中,检查图像是否存在于数组中,如果不存在,继续线程或者从你的HashMap数组中加载图像。还要经常检查你没有泄漏内存,位图和绘图通常会占用大量内存。优化你的代码取决于你。

对于那些对延迟加载图像使用哪个库犹豫不决的人来说,这只是一个快速提示:

有四种基本方法。

  1. DIY=>不是最好的解决方案,但对于一些图像,如果你想没有使用其他库的麻烦

  2. Volley的懒惰加载库=>来自android的家伙。它很好,一切都很好,但记录很差,因此使用起来是个问题。

  3. Picasso:一个简单的解决方案,只是工作,你甚至可以指定你想要引入的确切图像大小。它使用起来非常简单,但对于必须处理大量图像的应用程序来说,可能不是很“高性能”。

  4. UIL:延迟加载图像的最佳方法。您可以缓存图像(当然您需要权限),初始化加载程序一次,然后完成您的工作。迄今为止我见过的最成熟的异步图像加载库。

您必须尝试这个通用加载器是最好的。我在做了很多关于延迟加载的RnD之后使用了这个。

通用图像加载器

功能

  • 多线程图像加载(异步或同步)
  • ImageLoader配置的广泛定制(线程执行器、下载器、解码器、内存和磁盘缓存、显示图像选项等)
  • 每个显示图像调用的许多自定义选项(存根图像、缓存开关、解码选项、位图处理和显示等)
  • 内存和/或磁盘(设备的文件系统或SD卡)中的图像缓存
  • 监听加载过程(包括下载进度)

Android 2.0+支持

输入图片描述

上面所有的代码都有自己的价值,但以我个人的经验,试试毕加索。

毕加索是一个专门用于此目的的库,事实上它将自动管理缓存和所有其他网络操作。

请访问:http://code.tutsplus.com/tutorials/android-sdk-working-with-picasso--cms-22149

我发现滑翔Picasso更好。我使用毕加索来加载大约32个大小在200-500KB左右的图像,我总是得到OOM。但是Glide解决了我所有的OOM问题。

它在下载后缓存每个图像。还加载图像和延迟加载。

package com.fudiyoxpress.images;
import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.HttpURLConnection;import java.net.URL;import java.util.Collections;import java.util.Map;import java.util.WeakHashMap;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Handler;import android.widget.ImageView;
import com.fudiyoxpress.R;import com.fudiyoxpress.config.Config;import com.fudiyoxpress.twitter.ScaleBitmap;
public class ImageLoader {
// Initialize MemoryCacheMemoryCache memoryCache = new MemoryCache();
FileCache fileCache;
Context C;
// Create Map (collection) to store image and image url in key value pairprivate Map<ImageView, String> imageViews = Collections.synchronizedMap(new WeakHashMap<ImageView, String>());ExecutorService executorService;
// handler to display images in UI threadHandler handler = new Handler();
public ImageLoader(Context context) {
C = context;fileCache = new FileCache(context);
// Creates a thread pool that reuses a fixed number of// threads operating off a shared unbounded queue.executorService = Executors.newFixedThreadPool(5);
}
// default image show in list (Before online image download)final int stub_id = R.drawable.restlogoplaceholder;
public void DisplayImage(String url, ImageView imageView, Context context,boolean header_flag) {
Bitmap largeIcon = BitmapFactory.decodeResource(context.getResources(),R.drawable.restlogoplaceholder);header_flag = false;// Store image and url in MapimageViews.put(imageView, url);
// Check image is stored in MemoryCache Map or not (see// MemoryCache.java)Bitmap bitmap = memoryCache.get(url);
if (bitmap != null) {// if image is stored in MemoryCache Map then// Show image in listview rowBitmap b = ScaleBitmap.getScaledBitmap(context, bitmap, header_flag);imageView.setImageBitmap(b);
} else {// queue Photo to download from urlqueuePhoto(url, imageView, header_flag);
// Before downloading image show default imageimageView.setImageBitmap(ScaleBitmap.getScaledBitmap(context,largeIcon, header_flag));
}}


private void queuePhoto(String url, ImageView imageView, boolean header_flag) {// Store image and url in PhotoToLoad objectPhotoToLoad p = new PhotoToLoad(url, imageView, header_flag);
// pass PhotoToLoad object to PhotosLoader runnable class// and submit PhotosLoader runnable to executers to run runnable// Submits a PhotosLoader runnable task for execution
executorService.submit(new PhotosLoader(p));}
// Task for the queueprivate class PhotoToLoad {public String url;public ImageView imageView;public boolean b;
public PhotoToLoad(String u, ImageView i, boolean header_flag) {url = u;imageView = i;b = header_flag;}}
class PhotosLoader implements Runnable {PhotoToLoad photoToLoad;
PhotosLoader(PhotoToLoad photoToLoad) {this.photoToLoad = photoToLoad;}
@Overridepublic void run() {try {// Check if image already downloadedif (imageViewReused(photoToLoad))return;// download image from web urlBitmap bmp = getBitmap(photoToLoad.url);
// set image data in Memory CachememoryCache.put(photoToLoad.url, bmp);
if (imageViewReused(photoToLoad))return;
// Get bitmap to displayBitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
// Causes the Runnable bd (BitmapDisplayer) to be added to the// message queue.// The runnable will be run on the thread to which this handler// is attached.// BitmapDisplayer run method will callhandler.post(bd);
} catch (Throwable th) {// th.printStackTrace();}}}
private Bitmap getBitmap(String url) {File f = fileCache.getFile(url);
// from SD cache// CHECK : if trying to decode file which not exist in cache return nullBitmap b = decodeFile(f);if (b != null)return b;
// Download image file from webtry {
// // download the imageBitmap bitmap = null;
URL imageURL = null;try {
imageURL = new URL(Config.WEB_URL + "/ServeBlob?id=" + url);
HttpURLConnection connection = (HttpURLConnection) imageURL.openConnection();connection.setDoInput(true);connection.connect();// if(!(new File(imageURL.toString())).exists())// {// imageURL=new URL("");// }InputStream inputStream = connection.getInputStream();
// Constructs a new FileOutputStream that writes to// file// if file not exist then it will create fileOutputStream os = new FileOutputStream(f);
// See Utils class CopyStream method// It will each pixel from input stream and// write pixels to output stream (file)Utils.CopyStream(inputStream, os);
os.close();
BitmapFactory.Options options = new BitmapFactory.Options();options.inSampleSize = 8;
bitmap = BitmapFactory.decodeStream(inputStream, null, options);
} catch (IOException e) {
// e.printStackTrace();}
// Now file created and going to resize file with defined height// Decodes image and scales it to reduce memory consumptionbitmap = decodeFile(f);
return bitmap;
} catch (Throwable ex) {ex.printStackTrace();if (ex instanceof OutOfMemoryError)memoryCache.clear();return null;}}
// Decodes image and scales it to reduce memory consumptionprivate Bitmap decodeFile(File f) {
try {
// Decode image sizeBitmapFactory.Options o = new BitmapFactory.Options();o.inJustDecodeBounds = true;FileInputStream stream1 = new FileInputStream(f);BitmapFactory.decodeStream(stream1, null, o);stream1.close();
// Find the correct scale value. It should be the power of 2.
// Set width/height of recreated imagefinal int REQUIRED_SIZE = 85;
int width_tmp = o.outWidth, height_tmp = o.outHeight;int scale = 1;while (true) {if (width_tmp / 2 < REQUIRED_SIZE|| height_tmp / 2 < REQUIRED_SIZE)break;width_tmp /= 2;height_tmp /= 2;scale *= 2;}
// decode with current scale valuesBitmapFactory.Options o2 = new BitmapFactory.Options();o2.inSampleSize = scale;FileInputStream stream2 = new FileInputStream(f);Bitmap bitmap = BitmapFactory.decodeStream(stream2, null, o2);stream2.close();return bitmap;
} catch (FileNotFoundException e) {} catch (IOException e) {e.printStackTrace();}return null;}
boolean imageViewReused(PhotoToLoad photoToLoad) {
String tag = imageViews.get(photoToLoad.imageView);// Check url is already exist in imageViews MAPif (tag == null || !tag.equals(photoToLoad.url))return true;return false;}
// Used to display bitmap in the UI threadclass BitmapDisplayer implements Runnable {Bitmap bitmap;PhotoToLoad photoToLoad;
public BitmapDisplayer(Bitmap b, PhotoToLoad p) {bitmap = b;photoToLoad = p;}
public void run() {if (imageViewReused(photoToLoad))return;
// Show bitmap on UIif (bitmap != null) {photoToLoad.imageView.setImageBitmap(ScaleBitmap.getScaledBitmap(C, bitmap, photoToLoad.b));} else {
}// photoToLoad.imageView.setImageResource(stub_id);
}}
public void clearCache() {// Clear cache directory downloaded images and stored data in mapsmemoryCache.clear();fileCache.clear();}
}



package com.fudiyoxpress.images;
import java.util.Collections;import java.util.Iterator;import java.util.LinkedHashMap;import java.util.Map;import java.util.Map.Entry;import android.graphics.Bitmap;import android.util.Log;
public class MemoryCache {
private static final String TAG = "MemoryCache";
//Last argument true for LRU orderingprivate Map<String, Bitmap> cache = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(10,1.5f,true));
//current allocated sizeprivate long size=0;
//max memory cache folder used to download images in bytesprivate long limit = 1000000;
public MemoryCache(){
//use 25% of available heap sizesetLimit(Runtime.getRuntime().maxMemory()/4);}
public void setLimit(long new_limit){
limit=new_limit;Log.i(TAG, "MemoryCache will use up to "+limit/1024./1024.+"MB");}
public Bitmap get(String id){try{if(!cache.containsKey(id))return null;//NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78return cache.get(id);}catch(NullPointerException ex){ex.printStackTrace();return null;}}
public void put(String id, Bitmap bitmap){try{if(cache.containsKey(id))size-=getSizeInBytes(cache.get(id));cache.put(id, bitmap);size+=getSizeInBytes(bitmap);checkSize();}catch(Throwable th){th.printStackTrace();}}
private void checkSize() {Log.i(TAG, "cache size="+size+" length="+cache.size());if(size>limit){Iterator<Entry<String, Bitmap>> iter=cache.entrySet().iterator();//least recently accessed item will be the first one iteratedwhile(iter.hasNext()){Entry<String, Bitmap> entry=iter.next();size-=getSizeInBytes(entry.getValue());iter.remove();if(size<=limit)break;}Log.i(TAG, "Clean cache. New size "+cache.size());}}
public void clear() {try{//NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78cache.clear();size=0;}catch(NullPointerException ex){ex.printStackTrace();}}
long getSizeInBytes(Bitmap bitmap) {if(bitmap==null)return 0;return bitmap.getRowBytes() * bitmap.getHeight();}}



package com.fudiyoxpress.images;
import java.io.InputStream;import java.io.OutputStream;
public class Utils {public static void CopyStream(InputStream is, OutputStream os){final int buffer_size=1024;try{
byte[] bytes=new byte[buffer_size];for(;;){//Read byte from input stream
int count=is.read(bytes, 0, buffer_size);if(count==-1)break;
//Write byte from output streamos.write(bytes, 0, count);}}catch(Exception ex){}}}

您可以使用一些第三方库,例如PiccasoVolley来进行有效的延迟加载。您也可以通过实现以下内容创建自己的

  1. 实现从url下载图像的代码

  2. 实现存储和检索图像的缓存机制(使用android的LruCache进行缓存)

除了异步加载数据高速缓存,您可能需要UI缓存,如设置缓存大小

除了加载可见项目数据外,您可能需要加载近似可见项目数据

AndroidX分页库是另一个选项,例如,您可以从SQLite数据库加载、缓存和显示10,000,000个项目到RecyclerView。参考分页列表

示例:假设列表视图可见项目是[6,7,8,9,10],您可能需要加载[6,7,8,9,10]并预加载项目[1,2,3,4,5]&[11,12,13,14,15],因为用户可能会滚动到前页或后页

使用滑翔库。它对我有用,也适用于你的代码。它适用于图像和GIF。

ImageView imageView = (ImageView) findViewById(R.id.test_image);GlideDrawableImageViewTarget imagePreview = new GlideDrawableImageViewTarget(imageView);Glide.with(this).load(url).listener(new RequestListener<String, GlideDrawable>() {@Overridepublic boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {return false;}
@Overridepublic boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {return false;}}).into(imagePreview);}

如果你想像Facebook一样显示Shimmer布局,有一个官方的Facebook库。Facebook微光Android

它照顾一切,你只需要把你想要的设计代码以嵌套的方式在微光框架。这是一个示例代码。

<com.facebook.shimmer.ShimmerFrameLayoutandroid:id=“@+id/shimmer_view_container”android:layout_width=“wrap_content”android:layout_height="wrap_content"shimmer:duration="1000">
<here will be your content to display />
</com.facebook.shimmer.ShimmerFrameLayout>

这是它的java代码。

ShimmerFrameLayout shimmerContainer = (ShimmerFrameLayout) findViewById(R.id.shimmer_view_container);shimmerContainer.startShimmerAnimation();

在gradle文件中添加此依赖项。

implementation 'com.facebook.shimmer:shimmer:0.1.0@aar'

这就是它的样子微光Android截图

更新:如果您正在寻找2020年由静态编程语言Coroutines支持的解决方案,请尝试Coil。

线圈是协程图像加载器的首字母缩写。

功能

  1. 快速: Coil执行了许多优化,包括内存和磁盘缓存、内存中的图像下采样、重用位图,自动暂停/取消请求等。
  2. 轻量级: Coil为您的APK添加了约2000个方法(对于已经使用OkHttp和Coroutines的应用程序),这与毕加索相当,明显低于滑翔壁画
  3. 易于使用: Coil的API利用静态编程语言的语言特性来简化和减少样板。
  4. 现代: Coil是静态编程语言优先,使用现代库,包括Coroutines、OkHttp、Okio和AndroidX生命周期。

Gradle设置:

线圈在mavenCentral()上可用。

implementation("io.coil-kt:coil:1.0.0")

快速入门

要将图像加载到ImageView中,请使用load扩展函数:

// URLimageView.load("https://www.example.com/image.jpg")
// ResourceimageView.load(R.drawable.image)
// FileimageView.load(File("/path/to/image.jpg"))

或在后台线程

// Coil (suspends the current coroutine; non-blocking and thread safe)val request = ImageRequest.Builder(context).data(url).size(width, height).build()val drawable = context.imageLoader.execute(request).drawable

你也可以从毕加索/滑翔迁移

留档这里

滑翔

Glide是一个快速高效的Android开源媒体管理框架,它将媒体解码、内存和磁盘缓存以及资源池包装成一个简单易用的界面。

Glide支持获取、解码和显示视频剧照、图像和动画GIF。Glide包括一个灵活的API,允许开发人员插入几乎任何网络堆栈。默认情况下,Glide使用基于HttpUrlConnection的自定义堆栈,但也包括Google Volley项目或Square OkHttp库的实用程序库插件。

Glide.with(this).load("your-url-here").into(imageView);

Glide的主要重点是使滚动任何类型的图像列表尽可能平滑和快速,但Glide也适用于几乎任何需要获取,调整大小和显示远程图像的情况。

滑翔库

毕加索

使用杰克·沃顿的毕加索图书馆。(来自ActionBarSherlock开发人员的完美图像加载库)

适用于Android的强大图像下载和缓存库。

图像为Android应用程序添加了急需的上下文和视觉风格。Picasso允许在您的应用程序中轻松加载图像-通常只需一行代码!

Picasso.with(context).load("your-url-here").into(imageView);

在Android上加载图像的许多常见缺陷由Picasso自动处理:

在适配器中处理ImageView回收和下载取消。使用最少内存的复杂图像转换。自动内存和磁盘缓存。

毕加索图书馆

这就是你用jetpack合成做的。

implementation("io.coil-kt:coil-compose:1.3.1") // Add the Coil-Compose library
Image(painter = rememberImagePainter("https://www.example.com/image.jpg"),contentDescription = "My image description",modifier = Modifier.size(128.dp))

感谢nglauber和Gabriele Mariotti的这个这个答案。