Question Chargement paresseux d'images dans ListView


J'utilise un ListView pour afficher quelques images et légendes associées à ces images. Je reçois les images d'Internet. Existe-t-il un moyen de charger paresseux les images alors que le texte s'affiche, l'interface utilisateur n'est pas verrouillée et les images sont affichées lors du téléchargement?

Le nombre total d'images n'est pas fixé.


1731
2018-02-12 15:59


origine


Réponses:


Voici ce que j'ai créé pour conserver les images que mon application affiche actuellement. Veuillez noter que l'objet "Log" utilisé ici est mon wrapper personnalisé autour de la dernière classe Log dans Android.

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

1015
2018-02-18 03:56



J'ai fait une démo simple d'une liste paresseuse (situé à GitHub) avec des images. Cela peut être utile à quelqu'un. Il télécharge les images dans le fil d'arrière-plan. Les images sont mises en cache sur une carte SD et en mémoire. L'implémentation du cache est très simple et juste suffisante pour la démo. Je décode les images avec inSampleSize pour réduire la consommation de mémoire. J'essaie aussi de gérer correctement les vues recyclées.

Alt text


982
2018-06-18 08:04



Je recommande l'instrument open source Chargeur d'image universel. Il est basé sur le projet de Fedor Vlasov Liste paresseux et a été grandement amélioré depuis lors.

  • Multithread chargement de l'image
  • Possibilité de configuration étendue de la configuration d'ImageLoader (exécuteurs de thread, downlaoder, décodeur, mémoire et cache de disque, options d'affichage d'image, et autres)
  • Possibilité de mettre en cache des images en mémoire et / ou sur le système de fichiers de l'appareil (ou carte SD)
  • Possibilité d'écouter le processus de chargement
  • Possibilité de personnaliser chaque appel d'image avec des options séparées
  • Support de widget
  • Prise en charge d'Android 2.0+


530
2017-12-19 13:53



Multithreading pour la performance, un tutoriel de Gilles Debunne.

Cela vient du blog des développeurs Android. Le code suggéré utilise:

  • AsyncTasks.
  • Une taille dure et limitée, FIFO cache.
  • Un doux, facilement garbage collectcache
  • UNE espace réservé  Drawable pendant que vous téléchargez.

enter image description here


147
2017-08-12 11:07



Mise à jour: Notez que cette réponse est assez inefficace maintenant. Le Garbage Collector agit de manière agressive sur SoftReference et WeakReference, ce code n'est donc PAS adapté aux nouvelles applications.  (Au lieu de cela, essayez des bibliothèques comme Chargeur d'image universel suggéré dans d'autres réponses.)

Merci à James pour le code, et Bao-Long pour la suggestion d'utiliser SoftReference. J'ai implémenté les changements SoftReference sur le code de James. Malheureusement, les SoftReferences ont provoqué une collecte trop rapide de mes images. Dans mon cas c'était bien sans les trucs SoftReference, parce que ma taille de liste est limitée et mes images sont petites.

Il y a une discussion il y a un an concernant les SoftReferences sur google groups: lien vers le fil. En guise de solution à la récupération de place trop précoce, ils suggèrent la possibilité de définir manuellement la taille du tas VM en utilisant dalvik.system.VMRuntime.setMinimumHeapSize (), ce qui n'est pas très attrayant pour moi.

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



Picasso 

Utilisez la bibliothèque Picasso de Jake Wharton. (Une bibliothèque ImageLoading parfaite forme le développeur de ActionBarSherlock)

Une puissante bibliothèque de téléchargement et de mise en cache d'images pour Android.

Les images ajoutent un contexte et un flair visuel indispensables aux applications Android. Picasso permet un chargement d'image sans tracas dans votre application, souvent dans une ligne de code!

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

De nombreux pièges courants du chargement d'images sur Android sont gérés automatiquement par Picasso:

Gérer le recyclage ImageView et télécharger l'annulation dans un adaptateur. Transformations d'image complexes avec un minimum d'utilisation de la mémoire. Mémoire automatique et mise en cache de disque.

Bibliothèque de Picasso Jake Wharton

Glisser

Glide est un framework de gestion des médias Open Source rapide et efficace pour Android qui intègre le décodage multimédia, la mémoire et la mise en cache disque, ainsi que la mise en pool des ressources dans une interface simple et facile à utiliser.

Glide prend en charge la récupération, le décodage et l'affichage des images vidéo, des images et des images GIF animées. Glide inclut une API flexible qui permet aux développeurs de se connecter à pratiquement n'importe quelle pile réseau. Par défaut, Glide utilise une pile personnalisée HttpUrlConnection, mais inclut également des bibliothèques d'utilitaires connectées au projet Volley de Google ou à la bibliothèque OkHttp de Square.

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

L'objectif principal de Glide est de faire défiler toute sorte de liste d'images aussi facilement et rapidement que possible, mais Glide est également efficace pour presque tous les cas où vous devez chercher, redimensionner et afficher une image distante.

Glide Image Chargement de la bibliothèque

Fresque par Facebook 

Fresco est un système puissant pour afficher des images dans les applications Android.

Fresco prend en charge le chargement et l'affichage des images, vous n'avez donc pas à le faire. Il chargera les images du réseau, du stockage local ou des ressources locales et affichera un espace réservé jusqu'à ce que l'image soit arrivée. Il a deux niveaux de cache; un en mémoire et un autre en stockage interne.

Github de Fresco

Dans Android 4.x et inférieur, Fresco met des images dans une région spéciale de la mémoire Android. Cela permet à votre application de s'exécuter plus rapidement et de souffrir beaucoup moins souvent de l'OutOfMemoryError.

Documentation Fresco


84
2018-04-04 12:35



Chargeur haute performance - après avoir examiné les méthodes suggérées ici, j'ai utilisé La solution de Ben avec quelques changements -

  1. J'ai réalisé que travailler avec des drawables est plus rapide qu'avec des bitmaps, donc j'utilise plutôt des drawables

  2. Utiliser SoftReference est super, mais cela rend l'image en cache trop souvent supprimée, j'ai donc ajouté une liste Linked qui contient des références d'images, empêchant que l'image soit supprimée, jusqu'à ce qu'elle atteigne une taille prédéfinie

  3. Pour ouvrir l'InputStream j'ai utilisé java.net.URLConnection qui me permet d'utiliser le cache web (vous devez d'abord définir un cache de réponse, mais c'est une autre histoire)

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

77
2017-12-27 23:27



J'ai suivi cette formation Android et je pense qu'il fait un excellent travail pour télécharger des images sans bloquer l'interface principale. Il gère également la mise en cache et le traitement du défilement de nombreuses images: Chargement de grandes bitmaps efficacement


75
2018-05-22 06:00