Comment charger des images paresseusement dans ListView dans Android

1932

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

Le nombre total d'images n'est pas fixe.

lostInTransit
la source
10
Vous pouvez utiliser AsyncImageView de GreenDroid . Appelez setUrl.
Pascal Dimassimo
7
Je l'ai utilisé. C'est une merveilleuse mise en œuvre. Mauvaise nouvelle: AsyncImageView fait partie d'un grand projet GreenDroid, ce qui rend votre application plus grande, même si vous n'avez besoin que d'AsyncImageView. En outre, il semble que le projet GreenDroid ne soit pas mis à jour depuis 2011.
borisstr
5
Vous pouvez même essayer cette bibliothèque: Android-http-image-manager qui est à mon avis le meilleur pour le chargement asynchrone d'images.
Ritesh Kumar Dubey
34
Utilisez simplement Picasso, il fera tout lui-même. 'Picasso.with (yourContext) .load (img src / path / drawable here) .into (imageView ie votre cible);' C'est ça!
Anuj Sharma
6
essayez d'utiliser: github.com/nostra13/Android-Universal-Image-Loader , cette bibliothèque est très rapide et efficace pour le chargement paresseux et la mise en cache d'images
krunal patel

Réponses:

1087

Voici ce que j'ai créé pour contenir les images que mon application affiche actuellement. Veuillez noter que l'objet "Log" utilisé ici est mon wrapper personnalisé autour de la classe Log finale 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();
    }
}
James A Wilson
la source
104
Je pense que vous devriez utiliser SoftReferences pour que votre programme ne cause jamais OutOfMemoryException. Comme GC peut effacer les références quand la taille du tas augmente ... vous pouvez gérer votre propre génération comme après quelques secondes, vous pouvez mettre vos images dans cette liste et avant de charger, vérifiez que si l'image existe, ne la téléchargez pas à nouveau plutôt collectez à partir de cette liste et aussi le remettre dans votre liste softref et après un certain temps, vous pouvez purger votre liste hard :)
AZ_
36
Le projet Google Shelves est un excellent exemple de la façon dont ils ont fait code.google.com/p/shelves
AZ_
12
Ne manquez-vous pas un retour lorsque drawableMap contient l'image ... sans démarrer le fil de récupération?
Karussell
5
Ce code a plusieurs problèmes. Tout d'abord, vous devez mettre en cache Drawables, ce qui entraînera une fuite de mémoire: stackoverflow.com/questions/7648740/… . Deuxièmement, le cache lui-même n'est jamais effacé, il augmentera donc pour toujours, c'est une autre fuite de mémoire.
satur9nine
10
personne n'a entendu parler de LRU Cache developer.android.com/training/displaying-bitmaps/…
Muhammad Babar
1025

J'ai fait une démo simple d'une liste paresseuse (située sur GitHub) avec des images.

Utilisation de base

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

N'oubliez pas d'ajouter les autorisations suivantes à votre AndroidManifest.xml:

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

créez une seule instance d'ImageLoader et réutilisez-la tout autour de votre application. De cette façon, la mise en cache d'image sera beaucoup plus efficace.

Cela peut être utile à quelqu'un. Il télécharge des 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 suffit juste pour la démo. Je décode les images avec inSampleSize pour réduire la consommation de mémoire. J'essaie également de gérer correctement les vues recyclées.

Texte alternatif

Fedor
la source
7
Merci, j'utilise une version légèrement modifiée de votre code presque partout dans mon projet: code.google.com/p/vimeoid/source/browse/apk/src/com/fedorvlasov/…
shaman.sir
3
Quelqu'un a-t-il considéré le code de Gilles Debunne comme une alternative? Les deux grandes différences que je vois sont 1) la mise en cache de la mémoire par rapport à la carte SD et 2) l'utilisation d'AsyncTasks au lieu d'un pool de threads. android-developers.blogspot.com/2010/07/…
Richard
4
Il y a un bogue, parfois il est déclenché: 10-13 09: 58: 46.738: ERREUR / AndroidRuntime (24250): Gestionnaire non capturé: le thread principal se ferme en raison d'une exception non capturée 10-13 09: 58: 46.768: ERREUR / AndroidRuntime (24250) : java.lang.ArrayIndexOutOfBoundsException: index de tableau hors plage: 0 10-13 09: 58: 46.768: ERREUR / AndroidRuntime (24250): sur java.util.Vector.elementAt (Vector.java:331) 10-13 09-13: 58: 46.768: ERREUR / AndroidRuntime (24250): sur java.util.Vector.get (Vector.java:445) 10-13 09: 58: 46.768: ERREUR / AndroidRuntime (24250): sur com.my.app.image .ImageLoader $ PhotosQueue.Clean (ImageLoader.java:91)
Mikooos
64
Je voudrais ajouter pour les débutants comme moi que si vous souhaitez ajouter ce code à votre propre projet, vous devez ajouter des autorisations de cache externe dans le manifeste. Merci
Adam
2
@BruceHill Il n'y a pas de photosQueue dans le dernier code source. Vous semblez utiliser une version très ancienne.
Fedor
552

Je recommande un instrument open source Universal Image Loader . Il est à l'origine basé sur le projet LazyList de Fedor Vlasov et a été considérablement amélioré depuis lors.

  • Chargement d'image multithread
  • Possibilité de réglage large de la configuration d'ImageLoader (exécuteurs de threads, downlaoder, décodeur, mémoire et cache de disque, options d'image d'affichage, etc.)
  • Possibilité de mise en cache d'image 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 d'affichage avec des options séparées
  • Prise en charge des widgets
  • Prise en charge d'Android 2.0+

nostra13
la source
16
si vous cherchez un excellent code de "chargement d'image" que son créateur traite et maintient comme son précieux bébé (pas seulement une solution unique), vous venez de le trouver; big ups Nostra
AndroidGecko
Vraiment un excellent travail, mais il est un peu en retard sur ma liste. Si vous pouviez dire la version la plus performante 'ImageLoader' ... ou peut-être parce que les 'DisplayImageOptions'.
dinigo
J'aime tellement cette grille mais je ne peux pas l'utiliser pour mon tutoriel. je suis un débutant d'androïde. Je fais une application sur gridview mais mon application était targetSdkVersion 15 donc j'ai besoin d'utiliser Asynctask pour le processus dans le thread d'arrière-plan. J'utilisais cette grille, mais cela ne fonctionne pas. comment puis-je l'utiliser dans targetSdkVersion 15?
kongkea du
Vous pouvez créer une question distincte avec la balise [universal-image-loader]
nostra13
3
Après avoir lu les documents officiels d'Android et essayé de faire ce genre de choses moi-même, je suis presque certain que cette bibliothèque est la fin du chargement d'images. Je ne peux pas voter assez.
Core
159

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 collect cache installer.
  • Un espace réservé Drawable pendant que vous téléchargez.

entrez la description de l'image ici

Thomas Ahle
la source
10
Cela fonctionne aussi bien en 2.1. N'utilisez simplement pas AndroidHttpClient.
Thomas Ahle
2
@ thomas-ahle Merci, j'ai vu AndroidHttpClient donner des erreurs en 2.1, car il est implémenté à partir de 2.2, mais je n'ai pas vraiment essayé de trouver autre chose pour le remplacer.
Adinia
4
@Adina Tu as raison, j'ai oublié. Cependant, rien dans la recette ne peut tout aussi bien être fait avec un HttpClient normal.
Thomas Ahle
J'ai entendu à plusieurs endroits que Google ne recommande pas de références logicielles car le noyau Android est très désireux de collecter ces références par rapport aux versions antérieures du système.
Muhammad Ahmed AbuTalib
109

Mise à jour: Notez que cette réponse est assez inefficace maintenant. Le Garbage Collector agit agressivement sur SoftReference et WeakReference, donc ce code n'est PAS adapté aux nouvelles applications. (Essayez plutôt des bibliothèques comme Universal Image Loader suggérées 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 de SoftReference sur le code de James. Malheureusement, SoftReferences a fait que mes images ont été récupérées trop rapidement. Dans mon cas, c'était bien sans le truc SoftReference, car la taille de ma liste est limitée et mes images sont petites.

Il y a une discussion il y a un an concernant les SoftReferences sur les groupes Google: lien vers le fil . Comme solution à la récupération de place trop précoce, ils suggèrent la possibilité de définir manuellement la taille du tas de machine virtuelle à l'aide de dalvik.system.VMRuntime.setMinimumHeapSize (), ce qui n'est pas très intéressant 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();
}
TalkLittle
la source
3
vous pouvez créer des générations comme la génération dure et la génération douce. vous pouvez fixer une heure d'effacer le cache effacera toutes les images qui n'ont pas été accédées en 3 secondes .. vous pouvez jeter un œil au projet google shelves
AZ_
developer.android.com/reference/java/lang/ref/… SoftReference doc a une note sur la mise en cache, voir la section "Éviter les références logicielles pour la mise en cache". La plupart des applications doivent utiliser un android.util.LruCache au lieu de références logicielles.
vokilam
J'admire votre code mais maintenant dans le nouvel Android O / S, il y a un ramassage des ordures «agressif». Tenir une référence faible n'a aucun sens pour moi.
j2emanue
@ j2emanue Vous avez raison, comme j'ai essayé de l'indiquer en haut de ma réponse, les SoftReferences sont récupérées trop vite. Je vais essayer de modifier cette réponse pour la rendre encore plus claire.
TalkLittle
97

Picasso

Utilisez la bibliothèque Picasso de Jake Wharton. (Une bibliothèque Perfect ImageLoading du développeur d'ActionBarSherlock)

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

Les images ajoutent le contexte et le flair visuel indispensables aux applications Android. Picasso permet un chargement d'image sans tracas dans votre application, souvent sur une seule 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:

Gestion du recyclage d'ImageView et annulation du téléchargement dans un adaptateur. Transformations d'images complexes avec une utilisation minimale de la mémoire. Mémoire automatique et mise en cache du disque.

Bibliothèque de Picasso Jake Wharton

Glide

Glide est un cadre de gestion des médias open source rapide et efficace pour Android qui englobe le décodage des médias, la mise en cache de la mémoire et du disque et la mise en commun des ressources dans une interface simple et facile à utiliser.

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

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

L'objectif principal de Glide est de rendre le défilement de toute sorte d'une liste d'images aussi fluide et rapide que possible, mais Glide est également efficace dans presque tous les cas où vous devez récupérer, redimensionner et afficher une image distante.

Bibliothèque de chargement d'images Glide

Fresque par Facebook

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

Fresco s'occupe du chargement et de 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.

Fresco Github

Dans Android 4.x et versions antérieures, Fresco place les 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 de la redoutable OutOfMemoryError.

Documentation Fresco

Ashwin S Ashok
la source
Picasso est une bibliothèque développée par Square
LordRaydenMK
83

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

  1. J'ai réalisé que travailler avec des drawables est plus rapide qu'avec des bitmaps, donc j'utilise des drawables à la place

  2. L'utilisation de SoftReference est excellente, mais cela rend l'image mise en cache à supprimer trop souvent, j'ai donc ajouté une liste liée qui contient des références d'images, empêchant l'image à supprimer, jusqu'à ce qu'elle atteigne une taille prédéfinie

  3. Pour ouvrir 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;
}
}
Pinhassi
la source
Fonctionne très bien! BTW il y a une faute de frappe dans le nom de la classe.
Mullins
5
Au cas où cela ferait gagner du temps à quelqu'un d'autre: 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
Merci beaucoup, c'est une belle implémentation. J'ai également mis un espace réservé différent lorsque le dessin est en cours de chargement afin que l'utilisateur puisse obtenir des commentaires.
Juan Hernandez
Je pense également qu'il vaut mieux utiliser une file d'attente LIFO dans le executorService (mThreadPool) au lieu du FIFO par défaut, donc les dernières images demandées (qui sont probablement les visibles) sont chargées en premier. Voir stackoverflow.com/questions/4620061/how-to-create-lifo-executor
Juan Hernandez
9
@MichaelReed, dans le cas où vous êtes un utilisateur Eclipse, je recommande d'utiliser Ctrl-Shift-O (c'est la lettre O, pas le chiffre 0). Il automatise le processus d'ajout d'importations et les organise pour vous. Si vous êtes sur un Mac, utilisez plutôt Command-Shift-O.
SilithCrowe
78

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

toobsco42
la source
Je suis désolé, je n'ai indiqué qu'une seule classe pour l'application Google IO (et je suis trop tard pour la modifier). Vous devriez vraiment étudier toutes leurs classes d'utilitaires de chargement et de mise en cache d'images que vous pouvez trouver dans le même package que la classe de cache .
mkuech
Quelqu'un recommanderait-il de récupérer les fichiers DiskLruCache, Image * .java du dossier util de l'application iosched pour aider à gérer le chargement / la mise en cache des images pour l'affichage de la liste? Je veux dire que cela vaut vraiment la peine de suivre les guides des développeurs en ligne sur le sujet, mais ces cours (de iosched) vont un peu plus loin avec le modèle.
Gautam
65

1. Picasso permet un chargement d'image sans tracas dans votre application, souvent sur une seule ligne de code!

Utilisez Gradle:

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

Une seule ligne de code!

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

2. Glide Une bibliothèque de chargement et de mise en cache d'images pour Android axée sur un défilement fluide

Utilisez Gradle:

repositories {
  mavenCentral() 
  google()
}

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

// Pour une vue simple:

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

3. fresco est un système puissant pour afficher des images dans les applications Android.Fresco s'occupe du chargement et de l'affichage des images, vous n'avez donc pas à le faire.

Premiers pas avec Fresco

chiragkyada
la source
Ce tutoriel peut vous aider davantage pour PICASOO: - androidtutorialshub.com/… et GLIDE: - androidtutorialshub.com/…
lalit vasan
52

J'ai écrit un tutoriel qui explique comment effectuer un chargement paresseux d'images dans une vue de liste. J'entre dans les détails des problèmes de recyclage et de concurrence. J'utilise également un pool de threads fixe pour éviter de générer beaucoup de threads.

Chargement d'images paresseux dans le didacticiel Listview

Ben Ruijl
la source
41

Pour ce faire, je lance un fil de discussion pour télécharger les images en arrière-plan et je lui donne un rappel pour chaque élément de la liste. Lorsqu'une image a fini de télécharger, elle appelle le rappel qui met à jour la vue de l'élément de liste.

Cependant, cette méthode ne fonctionne pas très bien lorsque vous recyclez les vues.

jasonhudgins
la source
utiliser un fil pour chaque image est l'approche que j'utilise également. Si vous séparez votre modèle de votre vue, vous pouvez conserver le modèle en dehors de l'activité (comme dans votre classe «application») pour les garder en cache. Méfiez-vous de manquer de ressources si vous avez beaucoup d'images.
James A Wilson
pouvez-vous développer. Je suis nouveau dans le développement Android. Merci pour les conseils cependant
lostInTransit
14
Démarrer un nouveau thread pour chaque image n'est pas une solution efficace. Vous pouvez vous retrouver avec beaucoup de threads en mémoire et geler l'interface utilisateur.
Fedor
Fedor, d'accord, j'utilise généralement une file d'attente et un pool de threads, c'est la meilleure façon d'aller imo.
jasonhudgins
32

Je veux juste ajouter un autre bon exemple, les adaptateurs XML . Comme il est utilisé par Google et j'utilise également la même logique pour éviter une erreur OutOfMemory.

Fondamentalement, ce ImageDownloader est votre réponse (car il couvre la plupart de vos besoins). Vous pouvez également en mettre en œuvre.

Arslan Anwar
la source
2
La classe ImageDownloader n'est pas respectée: voir la solution ci-dessous code.google.com/p/parleys-android-nextgen/issues/detail?id=1
Sam
29

Il s'agit d'un problème courant sur Android qui a été résolu de nombreuses manières par de nombreuses personnes. À mon avis, la meilleure solution que j'ai vue est la bibliothèque relativement nouvelle appelée Picasso . Voici les faits saillants:

  • Open source, mais dirigée par Jake Whartondes ActionBarSherlock renommée.
  • Charger des images de manière asynchrone à partir de ressources réseau ou d'application avec une seule ligne de code
  • ListViewDétection automatique
  • Mise en cache automatique du disque et de la mémoire
  • Peut faire des transformations personnalisées
  • Beaucoup d'options configurables
  • API super simple
  • Fréquemment mis à jour
howettl
la source
29

J'utilise NetworkImageView de la nouvelle bibliothèque Android Volley com.android.volley.toolbox.NetworkImageView, et cela semble fonctionner assez bien. Apparemment, c'est la même vue que celle utilisée dans Google Play et d'autres nouvelles applications Google. Vaut vraiment le détour.

droidment
la source
1
Je pense que c'est la meilleure solution - les autres réponses sont très anciennes - la volée est vraiment rapide et combinée avec jake warthons disklrucache c'est une solution parfaite - j'ai essayé beaucoup d'autres mais aucune n'est stable et rapide comme la volée
Alexander Sidikov Pfeif
26

Eh bien, le temps de chargement des images à partir d'Internet a de nombreuses solutions. Vous pouvez également utiliser la bibliothèque Android-Query . Il vous donnera toute l'activité requise. Assurez-vous de ce que vous voulez faire et lisez la page wiki de la bibliothèque. Et résolvez la restriction de chargement d'image.

Voici mon code:

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

Cela devrait résoudre votre problème de chargement paresseux.

Rahul Rawat
la source
Beau travail pour moi, mais besoin d'un fichier Jar à inclure dans votre projet. Vous pouvez télécharger ce fichier JAR à partir d'ici AQuery androidAQuery = new AQuery (this); le lien est: code.google.com/archive/p/android-query/downloads
Selim Raza
25

Je pense que ce problème est très populaire parmi les développeurs Android, et il existe de nombreuses bibliothèques de ce type qui prétendent résoudre ce problème, mais seulement quelques-unes d'entre elles semblent être sur la bonne voie. AQuery est une de ces bibliothèques, mais elle est meilleure que la plupart d'entre elles dans tous les aspects et mérite d'être essayée.

Ritesh Kumar Dubey
la source
22

Vous devez essayer ce chargeur universel est le meilleur. J'utilise ceci après avoir fait de nombreux RnD sur le chargement paresseux.

Chargeur d'images universel

Caractéristiques

  • Chargement d'image multithread (async ou sync)
  • Large personnalisation de la configuration d'ImageLoader (exécuteurs de threads, téléchargeur, décodeur, cache mémoire et disque, options d'affichage d'image, etc.)
  • De nombreuses options de personnalisation pour chaque appel d'image d'affichage (images de stub, commutateur de mise en cache, options de décodage, traitement et affichage Bitmap, etc.)
  • Mise en cache des images en mémoire et / ou sur disque (système de fichiers de l'appareil ou carte SD)
  • Processus de chargement d'écoute (y compris la progression du téléchargement)

Prise en charge d'Android 2.0+

entrez la description de l'image ici

Girel Patel
la source
20

Jetez un œil au Shutterbug , le port léger SDWebImage d'Applidium (une belle bibliothèque sur iOS) pour Android. Il prend en charge la mise en cache asynchrone, stocke les URL ayant échoué, gère bien la concurrence et des sous-classes utiles sont incluses.

Les demandes de tirage (et les rapports de bogues) sont également les bienvenus!

PatrickNLT
la source
16

DroidParts a ImageFetcher qui ne nécessite aucune configuration pour commencer.

  • Utilise un disque et en mémoire Moins récemment utilisé cache en mémoire (LRU) le .
  • Décode efficacement les images.
  • Prend en charge la modification des bitmaps dans le thread d'arrière-plan.
  • A un fondu enchaîné simple.
  • A un rappel de progression de chargement d'image.

Clone DroidPartsGram pour un exemple:

Entrez la description de l'image ici

yanchenko
la source
Salut, j'ai jeté un œil aux exemples de code, mais j'ai des problèmes avec ImageFetcher avec un ArrayAdapter, cela vous dérangerait-il de regarder ma question? stackoverflow.com/questions/21089147/… Merci =]
masha
16

Juste un petit conseil pour quelqu'un qui est indécis quant à la bibliothèque à utiliser pour les images à chargement paresseux:

Il existe quatre méthodes de base.

  1. DIY => Pas la meilleure solution mais pour quelques images et si vous voulez vous passer des tracas d'utilisation d'autres bibliothèques

  2. La bibliothèque de chargement paresseux de Volley => De gars sur Android. Il est beau et tout mais est mal documenté et est donc un problème à utiliser.

  3. Picasso: Une solution simple qui fonctionne, vous pouvez même spécifier la taille exacte de l'image que vous souhaitez apporter. Elle est très simple à utiliser mais peut ne pas être très "performante" pour les applications qui doivent traiter des quantités énormes d'images.

  4. UIL: La meilleure façon de charger des images paresseuses. Vous pouvez mettre en cache des images (vous avez bien sûr besoin d'une autorisation), initialiser le chargeur une fois, puis faire faire votre travail. La bibliothèque de chargement d'images asynchrones la plus mature que j'ai jamais vue jusqu'à présent.

Bijay Koirala
la source
15

Novoda possède également une excellente bibliothèque de chargement d'images paresseuses et de nombreuses applications comme Songkick, Podio, SecretDJ et ImageSearch utilisent leur bibliothèque.

Leur bibliothèque est hébergée ici sur Github et ils ont également un tracker de problèmes assez actif . Leur projet semble également être assez actif, avec plus de 300+ commits au moment de la rédaction de cette réponse.

Soham
la source
1
En fait, Novoda est une excellente bibliothèque mais ... parfois, vous n'avez pas besoin d'une immense bibliothèque et seulement d'une approche simple de la solution. C'est pourquoi LazyList dans Github est si bon, si vos applications ne montrent qu'une image dans une listView et ne sont pas la caractéristique principale de votre application, juste une autre activité que je préférerais utiliser quelque chose de plus léger. Sinon, si vous savez que vous devrez utiliser souvent et fait partie du noyau, essayez Novoda.
Nicolas Jafelle
13

Vérifiez ma fourchette de LazyList . Fondamentalement, j'améliore la LazyList en retardant l'appel de l'ImageView et crée deux méthodes:

  1. Lorsque vous devez mettre quelque chose comme "Chargement de l'image ..."
  2. Lorsque vous devez afficher l'image téléchargée.

J'ai également amélioré ImageLoader en implémentant un singleton dans cet objet.

Nicolas Jafelle
la source
12

Si vous souhaitez afficher la disposition Shimmer comme Facebook, il existe une bibliothèque Facebook officielle pour cela. FaceBook Shimmer Android

Il s'occupe de tout, il vous suffit de mettre votre code de conception souhaité de manière imbriquée dans un cadre chatoyant. Voici un exemple de code.

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

Et voici le code java pour cela.

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

Ajoutez cette dépendance dans votre fichier gradle.

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

Voici à quoi ça ressemble.Capture d'écran Android Shimmer

Zankrut Parmar
la source
11

Tous les codes ci-dessus ont leur propre valeur, mais avec mon expérience personnelle, essayez simplement avec Picasso.

Picasso est une bibliothèque spécialement conçue à cet effet, en fait, elle gère automatiquement le cache et toutes les autres opérations réseau.Vous devrez ajouter une bibliothèque dans votre projet et écrire une seule ligne de code pour charger l'image à partir de l'URL distante.

Veuillez visiter ici: http://code.tutsplus.com/tutorials/android-sdk-working-with-picasso--cms-22149

AKber
la source
9

Utilisez la bibliothèque glide. Cela a fonctionné pour moi et fonctionnera également pour votre code. Cela fonctionne aussi bien pour les images que pour les gifs.

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);
}
Saket Kumar
la source
7

Je peux recommander une manière différente qui fonctionne comme un charme: Android Query.

Vous pouvez télécharger ce fichier JAR ici

AQuery androidAQuery = new AQuery(this);

Par exemple:

androidAQuery.id(YOUR IMAGEVIEW).image(YOUR IMAGE TO LOAD, true, true, getDeviceWidth(), ANY DEFAULT IMAGE YOU WANT TO SHOW);

C'est très rapide et précis, et en utilisant cela, vous pouvez trouver de nombreuses autres fonctionnalités comme l'animation lors du chargement, l'obtention d'une image bitmap (si nécessaire), etc.

Pratik Dasa
la source
7

Essayez Aquery . Il a des méthodes incroyablement simples pour charger et mettre en cache des images de manière asynchrone.

user2779311
la source
4
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;
 }
}
Nikhil Gupta
la source
4

J'ai eu ce problème et j'ai implémenté lruCache. Je pense que vous avez besoin de l'API 12 et supérieure ou utilisez la bibliothèque de compatibilité v4. lurCache est une mémoire rapide, mais elle a également un budget, donc si vous avez peur de pouvoir utiliser un cache de disque ... Tout est décrit dans Caching Bitmaps .

Je vais maintenant fournir mon implémentation qui est un singleton que j'appelle de n'importe où comme ceci:

//Where the first is a string and the other is a imageview to load.

DownloadImageTask.getInstance().loadBitmap(avatarURL, iv_avatar);

Voici le code idéal pour mettre en cache, puis appelez ce qui précède dans getView d'un adaptateur lors de la récupération de l'image Web:

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);
        }
    }
}
j2emanue
la source