题 ListView中的图像延迟加载


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

图像总数不固定。


1733
2018-02-12 15:59


起源


您可以使用 GreenDroid的AsyncImageView。打电话吧 setUrl。 - Pascal Dimassimo
我用过它。这是一个很棒的实现。 AsyncImageView是大型GreenDroid项目的一部分,这使得您的应用程序更大,甚至在您需要的所有内容中都是AsyncImageView。此外,似乎,GreenDroid项目自2011年以来未更新。 - borisstr
你甚至可以尝试这个库: Android的HTTP图像经理 在我看来,这是异步加载图像的最佳选择。 - Ritesh Kumar Dubey
只要使用毕加索,它就能完成所有工作。 'Picasso.with(yourContext).load(img src / path / drawable here).into(imageView即你的目标);'而已! - Anuj Sharma
尝试使用:github.com/nostra13/Android-Universal-Image-Loader ,这个库对于延迟加载和图像缓存非常快速有效 - krunal patel


答案:


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

1017
2018-02-18 03:56



我认为你应该使用SoftReferences,这样你的程序永远不会导致OutOfMemoryException。由于GC可以在堆大小增加时清除软引用...您可以管理自己的生成,就像几秒钟后您可以将图像放到该列表中,在加载之前,您应检查是否存在图像然后再不下载而是收集它从该列表中,并将其返回到您的softref列表,并在一段时间后,您可以清除您的硬件列表:) - AZ_
Google Shelves项目是一个很好的例子,看看他们是如何做到的 code.google.com/p/shelves - AZ_
当drawableMap包含图像时,不要错过返回...没有启动提取线程吗? - Karussell
此代码有几个问题。首先你应该缓存Drawables,这将导致内存泄漏: stackoverflow.com/questions/7648740/... 。其次,缓存本身永远不会被清除,因此它将永远增长,这是另一个内存泄漏。 - satur9nine
没有人听说过 LRU Cache  developer.android.com/training/displaying-bitmaps/... - Muhammad Babar


我做了 一个懒惰列表的简单演示 (位于GitHub)有图像。这可能对某人有帮助。它在后台线程中下载图像。图像正在缓存在SD卡和内存中。缓存实现非常简单,仅适用于演示。我用inSampleSize解码图像以减少内存消耗。我也尝试正确处理回收的视图。

Alt text


983
2018-06-18 08:04



谢谢,我在我的项目中几乎无处不在使用稍微修改过的代码版本: code.google.com/p/vimeoid/source/browse/apk/src/com/fedorvlasov/... - shaman.sir
有没有人看过Gilles Debunne的代码作为替代品。我看到的两个巨大差异是1)在内存缓存与SD卡和2)使用AsyncTasks而不是线程池。 android-developers.blogspot.com/2010/07/... - Richard
有一个bug,有时会被解雇:10-13 09:58:46.738:ERROR / AndroidRuntime(24250):未捕获的处理程序:由于未捕获的异常导致主要退出10-13 09:58:46.768:ERROR / AndroidRuntime(24250) :java.lang.ArrayIndexOutOfBoundsException:数组索引超出范围:0 10-13 09:58:46.768:ERROR / AndroidRuntime(24250):at java.util.Vector.elementAt(Vector.java:331)10-13 09: 58:46.768:ERROR / AndroidRuntime(24250):at java.util.Vector.get(Vector.java:445)10-13 09:58:46.768:ERROR / AndroidRuntime(24250):at com.my.app.image .ImageLoader $ PhotosQueue.Clean(ImageLoader.java:91) - Mikooos
我想为像我这样的新手添加如果你想将这个代码添加到你自己的项目中,你必须在清单中添加外部缓存权限。谢谢 - Adam
@BruceHill最新源代码中没有photosQueue。你似乎在使用一些非常旧的版本。 - Fedor


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

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


530
2017-12-19 13:53



如果你正在寻找伟大的“图像加载”代码,它的创造者将其视为并保持为他的宝贝(不仅是一次性解决方案),你只是找到它; Nostra - AndroidGecko
真的很棒的工作,但它有点落后于我的列表视图。如果你可以告诉最好的性能'ImageLoader'构建......或者可能是因为'DisplayImageOptions'。 - dinigo
我非常喜欢这个gridview,但我不能将它用于我的教程。我是android的初学者。我做了关于gridview的应用程序,但我的应用程序是targetSdkVersion 15所以我需要在后台线程中使用Asynctask进程。我以前使用这个gridview但它不起作用。我如何在targetSdkVersion 15中使用它? - kongkea
您可以使用标记创建单独的问题 [通用图像装载机] - NOSTRA
在阅读官方的android文档并尝试自己做这些东西之后,我很确定这个库是最终所有的图像加载。不能upvote足够。 - Core


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

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

  • AsyncTasks
  • 坚硬,有限的尺寸, FIFO cache
  • 柔软,轻松 garbage collect-ed缓存。
  • 一个 占位符  Drawable 当你下载。

enter image description here


147
2017-08-12 11:07



但它建立在Android 2.2(Froyo)上...... - Adinia
它在2.1中也可以正常工作。只是不要使用AndroidHttpClient。 - Thomas Ahle
@ thomas-ahle谢谢你,我看到AndroidHttpClient在2.1中给出了错误,因为它是从2.2实现的,但是并没有真正试图找到其他东西来替换它。 - Adinia
@Adina你是对的,我忘记了。但是,配方中没有任何东西可以用普通的HttpClient完成。 - Thomas Ahle
还有OOM仍然...... - Andro Selva


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

感谢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();
}

102
2018-05-05 13:16



你可以创造像硬代和软代的世代。你可以修复一个时间清除缓存将清除所有未在3秒内访问的图像..你可以看看谷歌货架项目 - AZ_
developer.android.com/reference/java/lang/ref/... SoftReference doc有关于缓存的说明,请参阅“避免缓存的软引用”部分。大多数应用程序应使用android.util.LruCache而不是软引用。 - vokilam
我很欣赏你的代码,但现在在新的Android O / S中有“积极”的垃圾收集。持弱的参考对我没有任何意义。 - j2emanue
@ j2emanue你是对的,因为我试图在我的答案的顶部指出,SoftReferences被垃圾收集得太快了。我会尝试编辑这个答案,使其更清晰。 - TalkLittle


毕加索 

使用杰克沃顿的毕加索图书馆。 (一个完美的ImageLoading Library构成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("http://goo.gl/h8qOq7").into(imageView);

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

滑翔图像加载库

Fresco by Facebook 

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

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

Fresco Github

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

壁画文件


84
2018-04-04 12:35



毕加索是由Square开发的图书馆 - LordRaydenMK


高性能加载器 - 在检查了这里建议的方法之后, 我用了 本的解决方案 有一些变化 -

  1. 我意识到使用drawables比使用位图更快,所以我使用drawables代替

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

  3. 要打开InputStream,我使用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 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;
}
}

77
2017-12-27 23:27



效果很好!顺便说一下,班级名称中有拼写错误。 - Mullins
如果它节省了其他人的时间: 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; - Michael Reed
非常感谢,这是一个很好的实现。我还为加载drawable时添加了一个不同的占位符,以便用户可以获得一些反馈。 - Juan Hernandez
另外我认为最好在executorService(mThreadPool)中使用LIFO队列而不是默认FIFO,因此首先加载请求的最后图像(可能是可见的)。看到 stackoverflow.com/questions/4620061/how-to-create-lifo-executor - Juan Hernandez
@MichaelReed,如果您是Eclipse用户,我建议使用Ctrl-Shift-O(字母O,而不是数字0)。它可以自动添加导入过程并为您组织导入。如果您使用的是Mac,请使用Command-Shift-O代替。 - SilithCrowe