[英]How to lazy load images in ListView in Android
我正在使用ListView
来显示与这些图像相关的一些图像和标题。 我从互联网上获取图像。 有没有办法延迟加载图像,以便在显示文本时不会阻止 UI 并在下载图像时显示图像?
图像总数不固定。
这是我创建的用于保存我的应用程序当前显示的图像的内容。 请注意,此处使用的“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();
}
}
我做了一个带有图像的惰性列表(位于 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 解码图像以减少内存消耗。 我也尝试正确处理回收的视图。
我推荐开源工具Universal Image Loader 。 它最初基于 Fedor Vlasov 的项目LazyList ,并从那时起得到了极大的改进。
Multithreading For Performance ,Gilles Debunne 的教程。
这是来自 Android 开发者博客。 建议的代码使用:
AsyncTasks
。FIFO cache
。garbage collect
缓存。Drawable
。更新:请注意,这个答案现在非常无效。 Garbage Collector 对 SoftReference 和 WeakReference 采取积极的行动,因此此代码不适合新应用程序。 (相反,请尝试其他答案中建议的像Universal Image Loader这样的库。)
感谢 James 提供代码,感谢 Bao-Long 提供使用 SoftReference 的建议。 我对 James 的代码实施了 SoftReference 更改。 不幸的是,SoftReferences 导致我的图像被垃圾收集得太快。 就我而言,没有 SoftReference 的东西也很好,因为我的列表大小有限,而且我的图像很小。
一年前有一个关于 google 组上的 SoftReferences 的讨论: 链接到线程。 作为对过早垃圾收集的解决方案,他们建议使用 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();
}
毕加索
使用杰克沃顿的毕加索图书馆。 (来自 ActionBarSherlock 开发者的完美图片加载库)
一个强大的安卓图像下载和缓存库。
图像为 Android 应用程序添加了急需的上下文和视觉风格。 Picasso 允许在您的应用程序中轻松加载图像——通常只需一行代码!
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
Picasso 自动处理了许多在 Android 上加载图像的常见陷阱:
在适配器中处理 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 负责图像加载和显示,因此您不必这样做。 它将从网络、本地存储或本地资源加载图像,并在图像到达之前显示占位符。 它有两级缓存; 一个在内存中,另一个在内部存储中。
在 Android 4.x 及更低版本中,Fresco 将图像放在 Android 内存的一个特殊区域中。 这让您的应用程序运行得更快 - 并且更少遭受可怕的 OutOfMemoryError 。
高性能加载器 - 在检查了此处建议的方法后,我使用了Ben 的解决方案并进行了一些更改 -
我意识到使用 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;
}
}
我已经关注了这个 Android 培训,我认为它在不阻塞主 UI 的情况下下载图像方面做得非常出色。 它还处理缓存和处理滚动浏览许多图像: 有效地加载大位图
1. Picasso允许在您的应用程序中轻松加载图像——通常只需一行代码!
使用摇篮:
implementation 'com.squareup.picasso:picasso:(insert latest version)'
只需一行代码!
Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);
2. Glide一个专注于平滑滚动的Android图片加载和缓存库
使用摇篮:
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 应用程序中显示图像。Fresco 负责图像加载和显示,所以你不必。
我写了一个教程,解释了如何在列表视图中延迟加载图像。 我详细介绍了回收和并发问题。 我还使用固定线程池来防止产生大量线程。
我这样做的方法是启动一个线程在后台下载图像,并为每个列表项传递一个回调。 图像下载完成后,它会调用更新列表项视图的回调。
但是,当您回收视图时,此方法效果不佳。
我只想再添加一个很好的例子, XML Adapters 。 因为它被谷歌使用,我也使用相同的逻辑来避免 OutOfMemory 错误。
基本上这个 ImageDownloader就是你的答案(因为它涵盖了你的大部分需求)。 有些你也可以在其中实施。
我一直在使用来自新的 Android Volley 库com.android.volley.toolbox.NetworkImageView
NetworkImageView ,它似乎工作得很好。 显然,这与Google Play和其他新的 Google 应用程序中使用的视图相同。 绝对值得一试。
这是Android上的一个常见问题,已经被很多人通过多种方式解决了。 在我看来,我见过的最好的解决方案是名为Picasso的相对较新的库。 以下是重点:
Jake Wharton
的ActionBarSherlock的Jake Wharton
领导。ListView
检测嗯,从网上图片加载时间有很多解决办法。 您也可以使用库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;
}
它应该可以解决您的延迟加载问题。
我认为这个问题在 Android 开发人员中非常流行,并且有很多这样的库声称可以解决这个问题,但似乎只有少数几个。 AQuery就是这样的一个库,但它在各个方面都比大多数库要好,值得一试。
你一定要试试这个 Universal Loader 是最好的。 我在延迟加载上做了很多 RnD 后使用这个。
特征
安卓 2.0+ 支持
看看Shutterbug ,Applidium 的轻量级 SDWebImage(iOS 上的一个不错的库)移植到 Android。 它支持异步缓存,存储失败的 URL,很好地处理并发,并包含有用的子类。
也欢迎拉取请求(和错误报告)!
对于犹豫要使用什么库来延迟加载图像的人来说,这只是一个快速提示:
有四种基本方式。
DIY => 不是最好的解决方案,但对于一些图像,如果你想不使用其他库的麻烦
Volley 的延迟加载库 => 来自 android 人员。 它很好,一切都很好,但是文档很差,因此使用起来有问题。
Picasso:一个简单有效的解决方案,您甚至可以指定要引入的确切图像大小。它使用起来非常简单,但对于必须处理大量图像的应用程序来说可能不是很“高效”。
UIL:延迟加载图像的最佳方式。 你可以缓存图像(当然你需要许可),初始化加载器一次,然后完成你的工作。 迄今为止我见过的最成熟的异步图片加载库。
检查我的LazyList 分支。 基本上,我通过延迟 ImageView 的调用来改进 LazyList 并创建两个方法:
我还通过在此对象中实现单例来改进 ImageLoader。
如果你想像 Facebook 一样显示 Shimmer 布局,有一个官方的 facebook 库。 Facebook微光Android
它会处理一切,您只需要将所需的设计代码以嵌套方式放入微光框架中。 这是一个示例代码。
<com.facebook.shimmer.ShimmerFrameLayout
android: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'
以上所有代码都有其自身的价值,但根据我的个人经验,请尝试使用 Picasso。
Picasso是专门用于此目的的库,实际上它将自动管理缓存和所有其他网络操作。您必须在项目中添加库,只需编写一行代码即可从远程 URL 加载图像。
请访问这里: http : //code.tutsplus.com/tutorials/android-sdk-working-with-picasso--cms-22149
使用滑行库。 它对我有用,也适用于您的代码。它也适用于图像和 gif。
ImageView imageView = (ImageView) findViewById(R.id.test_image);
GlideDrawableImageViewTarget imagePreview = new GlideDrawableImageViewTarget(imageView);
Glide
.with(this)
.load(url)
.listener(new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
return false;
}
})
.into(imagePreview);
}
试试Aquery 。 它具有非常简单的异步加载和缓存图像的方法。
URLImageViewHelper是一个了不起的库,可以帮助您做到这一点。
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;
}
}
我遇到了这个问题并实现了 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 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);
}
}
}
您可以尝试延迟加载图像和列表视图的Aquery Android库...下面的代码可能会帮助您..... 从这里下载库。
AQuery aq = new AQuery(mContext);
aq.id(R.id.image1).image("http://data.whicdn.com/images/63995806/original.jpg");
我使用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);
}
})
);
另一种方法是通过 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 数组中,检查图像是否存在于数组中,如果不存在,继续线程或者从你的 HashMap 数组加载图像。 还要始终检查您是否没有泄漏内存,位图和可绘制对象通常占用大量内存。 由您来优化您的代码。
我发现Glide是比Picasso
更好的选择。 我正在使用 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 MemoryCache
MemoryCache memoryCache = new MemoryCache();
FileCache fileCache;
Context C;
// Create Map (collection) to store image and image url in key value pair
private Map<ImageView, String> imageViews = Collections
.synchronizedMap(new WeakHashMap<ImageView, String>());
ExecutorService executorService;
// handler to display images in UI thread
Handler 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 Map
imageViews.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 row
Bitmap b = ScaleBitmap
.getScaledBitmap(context, bitmap, header_flag);
imageView.setImageBitmap(b);
} else {
// queue Photo to download from url
queuePhoto(url, imageView, header_flag);
// Before downloading image show default image
imageView.setImageBitmap(ScaleBitmap.getScaledBitmap(context,
largeIcon, header_flag));
}
}
private void queuePhoto(String url, ImageView imageView, boolean header_flag) {
// Store image and url in PhotoToLoad object
PhotoToLoad 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 queue
private 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;
}
@Override
public void run() {
try {
// Check if image already downloaded
if (imageViewReused(photoToLoad))
return;
// download image from web url
Bitmap bmp = getBitmap(photoToLoad.url);
// set image data in Memory Cache
memoryCache.put(photoToLoad.url, bmp);
if (imageViewReused(photoToLoad))
return;
// Get bitmap to display
BitmapDisplayer 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 call
handler.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 null
Bitmap b = decodeFile(f);
if (b != null)
return b;
// Download image file from web
try {
// // download the image
Bitmap 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 file
OutputStream 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 consumption
bitmap = 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 consumption
private Bitmap decodeFile(File f) {
try {
// Decode image size
BitmapFactory.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 image
final 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 values
BitmapFactory.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 MAP
if (tag == null || !tag.equals(photoToLoad.url))
return true;
return false;
}
// Used to display bitmap in the UI thread
class 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 UI
if (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 maps
memoryCache.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 ordering
private Map<String, Bitmap> cache = Collections.synchronizedMap(
new LinkedHashMap<String, Bitmap>(10,1.5f,true));
//current allocated size
private long size=0;
//max memory cache folder used to download images in bytes
private long limit = 1000000;
public MemoryCache(){
//use 25% of available heap size
setLimit(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=78
return 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 iterated
while(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=78
cache.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 stream
os.write(bytes, 0, count);
}
}
catch(Exception ex){}
}
}
一些答案已经提到使用各种图像库,如 Universal Image Loader 和 androidimageloader 等。这是一个古老的问题,但对于任何仍在寻找这样的东西的人来说,有几个这样的库用于图像加载/缓存。
您可以使用一些第三方库(例如Piccaso
或Volley
来实现有效的延迟加载。 您还可以通过实现以下内容来创建自己的
实现从url下载图片的代码
实现存储和检索图像的缓存机制(使用android的LruCache
进行缓存)
更新:如果您正在寻找由 Kotlin 协程支持的 2020 年解决方案,请尝试使用 Coil。
Coil 是Coroutine Image Loader的首字母缩写。
特征
摇篮设置:
线圈在mavenCentral()
上可用。
implementation("io.coil-kt:coil:1.0.0")
快速开始
要将图像加载到 ImageView 中,请使用加载扩展功能:
// URL
imageView.load("https://www.example.com/image.jpg")
// Resource
imageView.load(R.drawable.image)
// File
imageView.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
你也可以从 Picasso/Glide迁移
完整文档在这里
除了异步加载数据缓存,你可能需要 UI 缓存,比如setViewCacheSize
除了加载可见项数据外,您可能需要加载近似可见项数据
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],因为用户可能滚动到前页或后页
滑行
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 应用程序添加了急需的上下文和视觉风格。 Picasso 允许在您的应用程序中轻松加载图像——通常只需一行代码!
Picasso.with(context).load("your-url-here").into(imageView);
Picasso 自动处理了许多在 Android 上加载图像的常见陷阱:
在适配器中处理 ImageView 回收和下载取消。 以最少的内存使用复杂的图像转换。 自动内存和磁盘缓存。
这就是您使用Jetpack Compose 的方式。
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)
)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.