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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/541966/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 02:16:48  来源:igfitidea点击:

How to lazy load images in ListView in Android

androidimagelistviewurluniversal-image-loader

提问by lostInTransit

I am using a ListViewto display some images and captions associated with those images. I am getting the images from the Internet. Is there a way to lazy load images so while the text displays, the UI is not blocked and images are displayed as they are downloaded?

我正在使用 aListView来显示与这些图像相关的一些图像和标题。我从互联网上获取图像。有没有办法延迟加载图像,以便在显示文本时不会阻止 UI 并在下载图像时显示图像?

The total number of images is not fixed.

图像总数不固定。

采纳答案by James A Wilson

Here's what I created to hold the images that my app is currently displaying. Please note that the "Log" object in use here is my custom wrapper around the final Log class inside Android.

这是我创建的用于保存我的应用程序当前显示的图像的内容。请注意,此处使用的“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();
    }
}

回答by Fedor

I made a simple demo of a lazy list(located at GitHub) with images.

我做了一个带有图像的惰性列表(位于 GitHub)的简单演示

Basic Usage

ImageLoader imageLoader=new ImageLoader(context); ...
imageLoader.DisplayImage(url, imageView); 

Don't forget to add the following permissions to your AndroidManifest.xml:

 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> Please

create only one instance of ImageLoader and reuse it all around your application. This way image caching will be much more efficient.

基本用法

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 的一个实例并在您的应用程序中重用它。这样图像缓存会更有效率。

It may be helpful to somebody. It downloads images in the background thread. Images are being cached on an SD card and in memory. The cache implementation is very simple and is just enough for the demo. I decode images with inSampleSize to reduce memory consumption. I also try to handle recycled views correctly.

这可能对某人有帮助。它在后台线程中下载图像。图像缓存在 SD 卡和内存中。缓存实现非常简单,对于演示来说就足够了。我使用 inSampleSize 解码图像以减少内存消耗。我也尝试正确处理回收的视图。

Alt text

替代文字

回答by nostra13

I recommend open source instrument Universal Image Loader. It is originally based on Fedor Vlasov's project LazyListand has been vastly improved since then.

我推荐开源工具Universal Image Loader。它最初基于 Fedor Vlasov 的项目LazyList,并从那时起得到了极大的改进。

  • Multithread image loading
  • Possibility of wide tuning ImageLoader's configuration (thread executors, downlaoder, decoder, memory and disc cache, display image options, and others)
  • Possibility of image caching in memory and/or on device's file sysytem (or SD card)
  • Possibility to "listen" loading process
  • Possibility to customize every display image call with separated options
  • Widget support
  • Android 2.0+ support
  • 多线程图片加载
  • 可以广泛调整 ImageLoader 的配置(线程执行器、下载器、解码器、内存和磁盘缓存、显示图像选项等)
  • 可以在内存和/或设备的文件系统(或 SD 卡)中缓存图像
  • 可以“听”加载过程
  • 可以使用单独的选项自定义每个显示图像调用
  • 小工具支持
  • 安卓 2.0+ 支持

回答by Thomas Ahle

Multithreading For Performance, a tutorial by Gilles Debunne.

Multithreading For Performance,Gilles Debunne 的教程。

This is from the Android Developers Blog. The suggested code uses:

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

  • AsyncTasks.
  • A hard, limited size, FIFO cache.
  • A soft, easily garbage collect-ed cache.
  • A placeholderDrawablewhile you download.
  • AsyncTasks.
  • 一个坚硬的、有限的尺寸,FIFO cache
  • 一个软的、易于编辑的garbage collect缓存。
  • 下载时的占位符Drawable

enter image description here

在此处输入图片说明

回答by TalkLittle

Update: Note that this answer is pretty ineffective now. The Garbage Collector acts aggressively on SoftReference and WeakReference, so this code is NOT suitable for new apps.(Instead, try libraries like Universal Image Loadersuggested in other answers.)

更新:请注意,此答案现在非常无效。Garbage Collector 对 SoftReference 和 WeakReference 采取积极的行动,因此此代码不适合新应用程序。(相反,请尝试其他答案中建议的像Universal Image Loader这样的库。)

Thanks to James for the code, and Bao-Long for the suggestion of using SoftReference. I implemented the SoftReference changes on James' code. Unfortunately SoftReferences caused my images to be garbage collected too quickly. In my case it was fine without the SoftReference stuff, because my list size is limited and my images are small.

感谢 James 提供代码,感谢 Bao-Long 提供使用 SoftReference 的建议。我对 James 的代码实施了 SoftReference 更改。不幸的是,SoftReferences 导致我的图像被垃圾收集得太快。在我的情况下,没有 SoftReference 的东西很好,因为我的列表大小有限而且我的图像很小。

There's a discussion from a year ago regarding the SoftReferences on google groups: link to thread. As a solution to the too-early garbage collection, they suggest the possibility of manually setting the VM heap size using dalvik.system.VMRuntime.setMinimumHeapSize(), which is not very attractive to me.

一年前有一个关于 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();
}

回答by Ashwin S Ashok

Picasso

毕加索

Use Jake Wharton's Picasso Library. (A Perfect ImageLoading Library form the developer of ActionBarSherlock)

使用Hyman沃顿的毕加索图书馆。(来自 ActionBarSherlock 开发者的完美 ImageLoading 库)

A powerful image downloading and caching library for Android.

一个强大的安卓图像下载和缓存库。

Images add much-needed context and visual flair to Android applications. Picasso allows for hassle-free image loading in your application—often in one line of code!

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

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

Many common pitfalls of image loading on Android are handled automatically by Picasso:

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

Handling ImageView recycling and download cancellation in an adapter. Complex image transformations with minimal memory use. Automatic memory and disk caching.

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

Picasso Jake Wharton's Library

毕加索Hyman沃顿的图书馆

Glide

滑行

Glide is a fast and efficient open source media management framework for Android that wraps media decoding, memory and disk caching, and resource pooling into a simple and easy to use interface.

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

Glide supports fetching, decoding, and displaying video stills, images, and animated GIFs. Glide includes a flexible api that allows developers to plug in to almost any network stack. By default Glide uses a custom HttpUrlConnection based stack, but also includes utility libraries plug in to Google's Volley project or Square's OkHttp library instead.

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

Glide.with(this).load("http://goo.gl/h8qOq7").into(imageView);

Glide's primary focus is on making scrolling any kind of a list of images as smooth and fast as possible, but Glide is also effective for almost any case where you need to fetch, resize, and display a remote image.

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

Glide Image Loading Library

Glide 图片加载库

Fresco by Facebook

Facebook 的壁画

Fresco is a powerful system for displaying images in Android applications.

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

Fresco takes care of image loading and display, so you don't have to. It will load images from the network, local storage, or local resources, and display a placeholder until the image has arrived. It has two levels of cache; one in memory and another in internal storage.

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

Fresco Github

壁画 Github

In Android 4.x and lower, Fresco puts images in a special region of Android memory. This lets your application run faster - and suffer the dreaded OutOfMemoryError much less often.

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

Fresco Documentation

壁画文档

回答by Asaf Pinhassi

High performance loader - after examining the methods suggested here, I used Ben's solutionwith some changes -

高性能加载器 - 在检查了此处建议的方法后,我使用了Ben 的解决方案并进行了一些更改 -

  1. I realized that working with drawables is faster that with bitmaps so I uses drawables instead

  2. Using SoftReference is great, but it makes the cached image to be deleted too often, so I added a Linked list that holds images references, preventing from the image to be deleted, until it reached a predefined size

  3. To open the InputStream I used java.net.URLConnection which allows me to use web cache (you need to set a response cache first, but that's another story)

  1. 我意识到使用 drawables 比使用 bitmaps 更快,所以我改用 drawables

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

  3. 要打开 InputStream 我使用了 java.net.URLConnection ,它允许我使用网络缓存(您需要先设置响应缓存,但这是另一回事)

My code:

我的代码:

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;
}
}

回答by toobsco42

I have followed this Android Training and I think it does an excellent job at downloading images without blocking the main UI. It also handles caching and dealing with scrolling through many images: Loading Large Bitmaps Efficiently

我已经关注了这个 Android 培训,我认为它在不阻塞主 UI 的情况下下载图像方面做得非常出色。它还处理缓存和处理滚动浏览许多图像:有效地加载大位图

回答by chiragkyada

1.Picassoallows for hassle-free image loading in your application—often in one line of code!

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

Use Gradle:

使用摇篮:

implementation 'com.squareup.picasso:picasso:2.71828'

Just one line of code!

只需一行代码!

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

2.GlideAn image loading and caching library for Android focused on smooth scrolling

2. Glide一个专注于平滑滚动的Android图片加载和缓存库

Use Gradle:

使用摇篮:

repositories {
  mavenCentral() 
  google()
}

dependencies {
   implementation 'com.github.bumptech.glide:glide:4.7.1'
   annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
}

// For a simple view:

// 对于一个简单的视图:

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

3.frescois a powerful system for displaying images in Android applications.Fresco takes care of image loading and display, so you don't have to.

3. fresco是一个强大的系统,用于在 Android 应用程序中显示图像。Fresco 负责图像加载和显示,因此您不必这样做。

Getting Started with Fresco

Fresco 入门

回答by Ben Ruijl

I've written a tutorial that explains how to do lazy-loading of images in a listview. I go into some detail about the issues of recycling and concurrency. I also use a fixed thread pool to prevent spawning a lot of threads.

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

Lazy loading of images in Listview Tutorial

Listview教程中图片的延迟加载