Come caricare le immagini in modo pigro in ListView in Android


1931

Sto usando a ListViewper visualizzare alcune immagini e didascalie associate a quelle immagini. Ricevo le immagini da Internet. C'è un modo per caricare le immagini in modo pigro, quindi mentre viene visualizzato il testo, l'interfaccia utente non viene bloccata e le immagini vengono visualizzate mentre vengono scaricate?

Il numero totale di immagini non è fisso.


10
È possibile utilizzare AsyncImageView di GreenDroid . Chiama e basta setUrl.
Pascal Dimassimo,

7
L'ho usato È un'implementazione meravigliosa. Cattive notizie che AsyncImageView fa parte di un grande progetto GreenDroid, che ingrandisce la tua applicazione anche nel caso in cui tutto ciò di cui hai bisogno è AsyncImageView. Inoltre, sembra, il progetto GreenDroid non viene aggiornato dal 2011.
Borisstr,

5
Puoi anche provare questa libreria: Android-http-image-manager che secondo me è il migliore per il caricamento asincrono delle immagini.
Ritesh Kumar Dubey,

34
Basta usare Picasso, farà tutto da solo. 'Picasso.with (yourContext) .load (img src / path / drawable here) .into (imageView cioè il tuo target);' Questo è tutto!
Anuj Sharma,

6
prova a usare: github.com/nostra13/Android-Universal-Image-Loader , questa libreria è molto veloce ed efficiente per il caricamento lento e la memorizzazione delle immagini nella cache
krunal patel

Risposte:


1087

Ecco cosa ho creato per contenere le immagini che la mia app sta attualmente visualizzando. Si noti che l'oggetto "Log" in uso qui è il mio wrapper personalizzato attorno alla classe Log finale all'interno di 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();
    }
}

104
Penso che dovresti usare SoftReferences in modo che il tuo programma non causi mai OutOfMemoryException. Dato che GC può cancellare i riferimenti soft quando le dimensioni dell'heap sono in aumento ... puoi gestire la tua generazione come dopo alcuni secondi puoi mettere le tue immagini in quell'elenco e prima di caricare dovresti controllare che se l'immagine esiste allora non scaricarla di nuovo piuttosto raccogli da quella lista e anche rimetterlo nella tua lista di softref e dopo qualche tempo puoi eliminare la tua lista dura :)
AZ_

36
Il progetto Google Shelves è un eccellente esempio di come hanno fatto code.google.com/p/shelves
AZ_

12
Non perdere un ritorno quando drawableMap contiene l'immagine ... senza iniziare il thread di recupero?
Karussell,

5
Questo codice ha diversi problemi. Innanzitutto è necessario memorizzare nella cache Drawable, che causerà una perdita di memoria: stackoverflow.com/questions/7648740/… . In secondo luogo la cache stessa non viene mai cancellata, quindi crescerà per sempre, questa è un'altra perdita di memoria.
sabato 9

10
nessuno ha sentito parlare di LRU Cache developer.android.com/training/displaying-bitmaps/…
Muhammad Babar,

1025

Ho fatto una semplice demo di un elenco pigro (che si trova su GitHub) con immagini.

Utilizzo di base

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

Non dimenticare di aggiungere le seguenti autorizzazioni al tuo AndroidManifest.xml:

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

crea solo un'istanza di ImageLoader e riutilizzala in tutta la tua applicazione. In questo modo la memorizzazione delle immagini nella cache sarà molto più efficiente.

Potrebbe essere utile a qualcuno. Scarica le immagini nel thread in background. Le immagini vengono memorizzate nella cache su una scheda SD e in memoria. L'implementazione della cache è molto semplice ed è appena sufficiente per la demo. Decodifico le immagini con inSampleSize per ridurre il consumo di memoria. Cerco anche di gestire correttamente le viste riciclate.

Testo alternativo


7
Grazie, sto usando una versione leggermente modificata del tuo codice quasi ovunque nel mio progetto: code.google.com/p/vimeoid/source/browse/apk/src/com/fedorvlasov/…
shaman.sir

3
Qualcuno ha esaminato il codice di Gilles Debunne come alternativa. Le due grandi differenze che vedo sono 1) nella memorizzazione nella cache della memoria rispetto alla scheda SD e 2) l'uso di AsyncTasks anziché un pool di thread. android-developers.blogspot.com/2010/07/…
Richard

4
Si è verificato un errore, a volte viene generato: 10-13 09: 58: 46.738: ERRORE / AndroidRuntime (24250): Gestore non rilevato: uscita principale thread a causa di eccezione non rilevata 10-13 09: 58: 46.768: ERRORE / AndroidRuntime (24250) : java.lang.ArrayIndexOutOfBoundsException: indice array fuori intervallo: 0 10-13 09: 58: 46.768: ERROR / AndroidRuntime (24250): at java.util.Vector.elementAt (Vector.java:331) 10-13 09: 58: 46.768: ERRORE / AndroidRuntime (24250): su java.util.Vector.get (Vector.java:445) 10-13 09: 58: 46.768: ERRORE / AndroidRuntime (24250): su com.my.app.image .ImageLoader $ PhotosQueue.Clean (ImageLoader.java:91)
Mikooos,

64
Vorrei aggiungere per i neofiti come me che se si desidera aggiungere questo codice al proprio progetto, è necessario aggiungere autorizzazioni cache esterne nel manifest. Grazie
Adam,

2
@BruceHill Non ci sono photosQueue nell'ultimo codice sorgente. Sembra che tu stia usando una versione molto vecchia.
Fedor,

552

Raccomando Universal Image Loader per strumento open source . È originariamente basato sul progetto LazyList di Fedor Vlasov e da allora è stato notevolmente migliorato.

  • Caricamento di immagini multithread
  • Possibilità di ampia regolazione della configurazione di ImageLoader (esecutori di thread, downlaoder, decoder, memoria e cache del disco, opzioni di visualizzazione dell'immagine e altro)
  • Possibilità di memorizzazione nella cache delle immagini in memoria e / o sul sistema di file del dispositivo (o scheda SD)
  • Possibilità di "ascoltare" il processo di caricamento
  • Possibilità di personalizzare ogni chiamata dell'immagine visualizzata con opzioni separate
  • Supporto per widget
  • Supporto per Android 2.0+


16
se stai cercando un ottimo codice di "caricamento delle immagini" che il suo creatore tratta e mantiene come il suo prezioso bambino (non solo una soluzione una volta), lo hai appena trovato; big up Nostra
AndroidGecko

Davvero un ottimo lavoro, ma è un po 'in ritardo rispetto alla mia lista. Se potessi dire la build "ImageLoader" con le migliori prestazioni ... o forse è perché "DisplayImageOptions".
dinigo,

Adoro questo gridview ma non posso usarlo per il mio tutorial. sono un principiante di Android. Faccio domanda su gridview ma la mia applicazione era targetSdkVersion 15, quindi ho bisogno di usare Asynctask per il processo in thread in background. Usavo questo gridview ma non funziona. come posso usarlo in targetSdkVersion 15?
kongkea,

Puoi creare una domanda separata con il tag [universal-image-loader]
nostra13

3
Dopo aver letto i documenti Android ufficiali e aver provato a fare queste cose da solo, sono abbastanza sicuro che questa libreria sia la fine del caricamento delle immagini. Non è possibile votare abbastanza.
Core

159

Multithreading For Performance , un tutorial di Gilles Debunne.

Questo è dal blog degli sviluppatori Android. Il codice suggerito utilizza:

  • AsyncTasks.
  • Un disco, dimensioni limitate, FIFO cache.
  • Una garbage collectcache morbida e di facile utilizzo.
  • Un segnaposto Drawable durante il download.

inserisci qui la descrizione dell'immagine


10
Funziona bene anche in 2.1. Basta non usare AndroidHttpClient.
Thomas Ahle,

2
@ thomas-ahle Grazie, ho visto AndroidHttpClient dare errori in 2.1, dato che è stato implementato da 2.2, ma non ho davvero cercato di trovare qualcos'altro per sostituirlo.
Adinia,

4
@Adina Hai ragione, ho dimenticato. Tuttavia, non c'è nulla nella ricetta che non possa essere fatto altrettanto bene con un normale HttpClient.
Thomas Ahle,

Ho sentito in diversi luoghi che Google non raccomanda riferimenti soft perché il kernel Android è molto desideroso di raccogliere questi riferimenti rispetto alle versioni precedenti del sistema.
Muhammad Ahmed Abu Talib

109

Aggiornamento: nota che questa risposta è piuttosto inefficace ora. Garbage Collector agisce in modo aggressivo su SoftReference e WeakReference, quindi questo codice NON è adatto per le nuove app. (Invece, prova le librerie come Universal Image Loader suggerito in altre risposte.)

Grazie a James per il codice e Bao-Long per il suggerimento di utilizzare SoftReference. Ho implementato le modifiche di SoftReference sul codice di James. Sfortunatamente SoftReferences ha causato la raccolta dei rifiuti delle mie immagini troppo rapidamente. Nel mio caso andava bene senza le cose SoftReference, perché la dimensione della mia lista è limitata e le mie immagini sono piccole.

C'è una discussione di un anno fa sulle SoftReferences sui gruppi di Google: link al thread . Come soluzione per la garbage collection troppo precoce, suggeriscono la possibilità di impostare manualmente la dimensione dell'heap della VM usando dalvik.system.VMRuntime.setMinimumHeapSize (), che non è molto interessante per me.

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

3
puoi creare generazioni come la generazione dura e la softgeneration. puoi correggere un tempo in cui la cache cancella tutte le immagini a cui non è stato effettuato l'accesso in 3 secondi .. puoi dare un'occhiata al progetto Google shelf
AZ_18

developer.android.com/reference/java/lang/ref/… Il documento SoftReference contiene una nota sulla memorizzazione nella cache, vedere la sezione "Evitare riferimenti soft per la memorizzazione nella cache". La maggior parte delle applicazioni dovrebbe usare un android.util.LruCache invece di riferimenti software.
Vokilam,

Ammiro il tuo codice ma ora nel nuovo Android O / S c'è la raccolta dei rifiuti "aggressiva". Tenere un riferimento debole non ha alcun senso per me.
j2emanue,

@ j2emanue Hai ragione, come ho cercato di indicare nella parte superiore della mia risposta, i SoftReferences vengono raccolti troppo rapidamente. Proverò a modificare questa risposta per renderla ancora più chiara.
TalkLittle

97

Picasso

Usa la libreria Picasso di Jake Wharton. (Una libreria Perfect ImageLoading dallo sviluppatore di ActionBarSherlock)

Una potente libreria di download e memorizzazione nella cache di immagini per Android.

Le immagini aggiungono il contesto e il talento visivo necessari alle applicazioni Android. Picasso consente il caricamento delle immagini senza problemi nella tua applicazione, spesso in una riga di codice!

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

Molte insidie ​​comuni nel caricamento delle immagini su Android sono gestite automaticamente da Picasso:

Gestione del riciclaggio di ImageView e annullamento del download in un adattatore. Trasformazioni di immagini complesse con un utilizzo minimo della memoria. Memoria automatica e memorizzazione nella cache del disco.

Biblioteca di Picasso Jake Wharton

planata

Glide è un framework di gestione multimediale open source rapido ed efficiente per Android che racchiude la decodifica multimediale, la memorizzazione nella cache e la memoria del disco e il pool di risorse in un'interfaccia semplice e facile da usare.

Glide supporta il recupero, la decodifica e la visualizzazione di immagini fisse video, immagini e GIF animate. Glide include un'API flessibile che consente agli sviluppatori di collegarsi a quasi tutti gli stack di rete. Per impostazione predefinita, Glide utilizza uno stack personalizzato basato su HttpUrlConnection, ma include anche le librerie di utilità plug-in nel progetto Volley di Google o nella libreria OkHttp di Square.

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

L'obiettivo principale di Glide è rendere lo scorrimento di qualsiasi tipo di elenco di immagini il più fluido e veloce possibile, ma Glide è efficace anche in quasi tutti i casi in cui è necessario recuperare, ridimensionare e visualizzare un'immagine remota.

Libreria di caricamento immagini Glide

Affresco di Facebook

Fresco è un potente sistema per la visualizzazione di immagini in applicazioni Android.

Fresco si occupa del caricamento e della visualizzazione delle immagini, quindi non è necessario. Caricherà le immagini dalla rete, dalla memoria locale o dalle risorse locali e visualizzerà un segnaposto fino all'arrivo dell'immagine. Ha due livelli di cache; uno in memoria e un altro nella memoria interna.

Affresco Github

In Android 4.xe versioni precedenti, Fresco inserisce le immagini in una regione speciale di memoria Android. Ciò consente alla tua applicazione di funzionare più velocemente e di subire il temuto OutOfMemoryError molto meno spesso.

Documentazione sull'affresco


Picasso è una biblioteca sviluppata da Square
LordRaydenMK

83

Caricatore ad alte prestazioni - dopo aver esaminato i metodi suggeriti qui, ho usato la soluzione di Ben con alcune modifiche -

  1. Mi sono reso conto che lavorare con i disegni è più veloce che con le bitmap, quindi uso invece i disegni

  2. L'uso di SoftReference è fantastico, ma rende l'immagine memorizzata nella cache per essere eliminata troppo spesso, quindi ho aggiunto un elenco collegato che contiene i riferimenti alle immagini, impedendo all'immagine di essere eliminata, fino a raggiungere una dimensione predefinita

  3. Per aprire InputStream ho usato java.net.URLConnection che mi permette di usare la cache web (devi prima impostare una cache di risposta, ma questa è un'altra storia)

Il mio codice:

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

Funziona alla grande! A proposito c'è un refuso nel nome della classe.
Mullins,

5
Nel caso in cui si risparmia qualcun altro il tempo: 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

Grazie mille, questa è una bella implementazione. Ho anche messo un segnaposto diverso per quando viene caricato il disegno in modo che l'utente possa ricevere un feedback.
Juan Hernandez,

Inoltre penso che sia meglio usare una coda LIFO inecutorService (mThreadPool) invece di FIFO predefinito, quindi le ultime immagini richieste (che probabilmente sono quelle visibili) vengono caricate per prime. Vedi stackoverflow.com/questions/4620061/how-to-create-lifo-executor
Juan Hernandez

9
@MichaelReed, nel caso tu sia un utente Eclipse, ti consiglio di usare Ctrl-Shift-O (questa è la lettera O, non il numero 0). Automatizza il processo di aggiunta delle importazioni e le organizza per te. Se sei su un Mac, usa invece Command-Shift-O.
SilithCrowe,

78

Ho seguito questo corso di formazione su Android e penso che svolga un lavoro eccellente nel download delle immagini senza bloccare l'interfaccia utente principale. Gestisce anche la memorizzazione nella cache e la gestione dello scorrimento di molte immagini: caricamento di bitmap di grandi dimensioni in modo efficiente


Mi dispiace, ho indicato una sola classe per l'app Google IO (e sono troppo tardi per modificarla). Dovresti davvero studiare tutte le classi di utilità per il caricamento e la memorizzazione nella cache delle immagini che puoi trovare nello stesso pacchetto della classe cache .
mkuech,

Qualcuno consiglierebbe di prendere i file DiskLruCache, Image * .java dalla cartella util dell'app iosched per aiutare a gestire il caricamento / la memorizzazione delle immagini per la visualizzazione elenco? Voglio dire, vale sicuramente la pena seguire le guide per sviluppatori online sull'argomento, ma queste classi (da iosched) vanno un po 'oltre con lo schema.
Gautam,

65

1. Picasso consente il caricamento delle immagini senza problemi nell'applicazione, spesso in una riga di codice!

Usa Gradle:

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

Solo una riga di codice!

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

2. Glide Una libreria di caricamento delle immagini e memorizzazione nella cache per Android incentrata sullo scorrimento regolare

Usa Gradle:

repositories {
  mavenCentral() 
  google()
}

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

// Per una vista semplice:

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

3. fresco è un potente sistema per la visualizzazione di immagini in applicazioni Android. Fresco si occupa del caricamento e della visualizzazione delle immagini, quindi non è necessario.

Introduzione a Fresco


Questo tutorial può aiutarti di più per PICASOO: - androidtutorialshub.com/… e GLIDE: - androidtutorialshub.com/…
lalit vasan


41

Il modo in cui lo faccio è lanciando un thread per scaricare le immagini in background e consegnarle un callback per ogni voce dell'elenco. Al termine del download di un'immagine, chiama il callback che aggiorna la vista per l'elemento dell'elenco.

Questo metodo non funziona molto bene quando si riciclano le viste.


usare un thread per ogni immagine è anche l'approccio che uso. Se si separa il modello dalla vista, è possibile mantenere il modello all'esterno dell'Attività (come nella classe "applicazione") per mantenerlo nella cache. Fai attenzione a rimanere senza risorse se hai molte immagini.
James A Wilson,

puoi per favore elaborare. Sono nuovo allo sviluppo Android. Grazie per i suggerimenti però
lostInTransit,

14
L'avvio di un nuovo thread per ogni immagine non è una soluzione efficace. Puoi finire con molti thread in memoria e un'interfaccia utente bloccata.
Fedor

Fedor, d'accordo, di solito uso una coda e un pool di thread, questo è il modo migliore per andare imo.
jasonhudgins,


29

Questo è un problema comune su Android che è stato risolto in molti modi da molte persone. Secondo me la migliore soluzione che ho visto è la biblioteca relativamente nuova chiamata Picasso . Ecco i punti salienti:

  • Open source, ma guidato dalla fama Jake Whartondi ActionBarSherlock .
  • Caricare in modo asincrono le immagini dalla rete o dalle risorse dell'app con una riga di codice
  • ListViewRilevazione automatica
  • Memorizzazione automatica della cache su disco e memoria
  • Può fare trasformazioni personalizzate
  • Molte opzioni configurabili
  • API super semplice
  • Frequentemente aggiornato

29

Sto usando NetworkImageView dalla nuova libreria Volley Android com.android.volley.toolbox.NetworkImageViewe sembra funzionare abbastanza bene. Apparentemente, questa è la stessa vista utilizzata in Google Play e in altre nuove applicazioni Google. Sicuramente vale la pena dare un'occhiata.


1
Penso che questa sia la soluzione migliore - le altre risposte sono molto vecchie - il volley è davvero veloce e combinato con jake warthons disklrucache è una soluzione perfetta - ne ho provati molti altri ma nessuno è stabile e veloce come il volley
Alexander Sidikov Pfeif

26

Bene, il tempo di caricamento delle immagini da Internet ha molte soluzioni. Puoi anche utilizzare la libreria Android-Query . Ti darà tutta l'attività richiesta. Assicurati di cosa vuoi fare e leggi la pagina wiki della biblioteca. E risolvi la limitazione del caricamento delle immagini.

Questo è il mio codice:

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

Dovrebbe risolvere il tuo problema di caricamento lento.


Buon lavoro per me, ma è necessario un file Jar da includere nel progetto. Puoi scaricare quel file JAR da qui AQuery androidAQuery = new AQuery (this); link Is: code.google.com/archive/p/android-query/downloads
Selim Raza

25

Penso che questo problema sia molto popolare tra gli sviluppatori Android, e ci sono molte librerie di questo tipo che affermano di risolverlo, ma solo alcune sembrano essere sul punto. AQuery è una di queste librerie, ma è migliore della maggior parte di esse sotto tutti gli aspetti e vale la pena provarla.


22

È necessario provare questo caricatore universale è il migliore. Sto usando questo dopo aver fatto molti RnD su caricamento lento.

Caricatore di immagini universale

Caratteristiche

  • Caricamento di immagini multithread (asincrono o sincronizzato)
  • Ampia personalizzazione della configurazione di ImageLoader (eseguitori di thread, downloader, decoder, memoria e cache del disco, opzioni di visualizzazione delle immagini, ecc.)
  • Molte opzioni di personalizzazione per ogni chiamata di immagine di visualizzazione (immagini stub, interruttore di memorizzazione nella cache, opzioni di decodifica, elaborazione e visualizzazione di bitmap, ecc.)
  • Memorizzazione nella cache delle immagini in memoria e / o su disco (file system del dispositivo o scheda SD)
  • Processo di caricamento dell'ascolto (incluso l'avanzamento del download)

Supporto per Android 2.0+

inserisci qui la descrizione dell'immagine


20

Dai un'occhiata a Shutterbug , la leggera porta SDWebImage (una bella libreria su iOS) di Applidium per Android. Supporta la cache asincrona, memorizza gli URL non riusciti, gestisce bene la concorrenza e sono incluse utili sottoclassi.

Anche le richieste pull (e le segnalazioni di bug) sono benvenute!


16

DroidParts ha ImageFetcher che richiede zero configurazione per iniziare.

  • Utilizza un disco e una cache LRU ( Almeno recentemente utilizzati ) in memoria.
  • Decodifica in modo efficiente le immagini.
  • Supporta la modifica di bitmap nel thread in background.
  • Ha una dissolvenza incrociata semplice.
  • Richiama avanzamento caricamento immagini.

Clone DroidPartsGram per un esempio:

Inserisci qui la descrizione dell'immagine


Ciao, ho dato un'occhiata agli esempi di codice ma sto riscontrando problemi con ImageFetcher con un ArrayAdapter, ti dispiacerebbe guardare la mia domanda? stackoverflow.com/questions/21089147/… Grazie =]
masha

16

Solo un breve suggerimento per chi è indeciso su quale libreria usare per le immagini a caricamento lento:

Ci sono quattro modi di base.

  1. Fai da te => Non è la soluzione migliore ma per alcune immagini e se vuoi andare senza il fastidio di usare altre librerie

  2. Volley's Lazy Caricamento libreria => Da ragazzi di Android. È bello e tutto ma è scarsamente documentato e quindi è un problema da usare.

  3. Picasso: una soluzione semplice che funziona, puoi anche specificare la dimensione esatta dell'immagine che vuoi inserire. È molto semplice da usare ma potrebbe non essere molto "performante" per le app che devono gestire enormi quantità di immagini.

  4. UIL: il modo migliore per caricare le immagini in modo pigro. Puoi memorizzare nella cache le immagini (ovviamente hai bisogno dell'autorizzazione), inizializzare il caricatore una volta, quindi completare il lavoro. La libreria di caricamento di immagini asincrone più matura che abbia mai visto finora.


15

Novoda ha anche un'ottima libreria di caricamento di immagini pigre e molte app come Songkick, Podio, SecretDJ e ImageSearch usano la loro libreria.

La loro biblioteca è ospitata qui su Github e hanno anche un tracker di problemi piuttosto attivo . Anche il loro progetto sembra essere piuttosto attivo, con oltre 300+ commit al momento di scrivere questa risposta.


1
In realtà Novoda è una grande biblioteca ma ... a volte non è necessaria una grande biblioteca e solo un semplice approccio alla soluzione. Ecco perché LazyList in Github è così buono, se le tue app mostrano solo un'immagine in un listView e non è la caratteristica principale della tua app, solo un'altra attività che preferirei usare qualcosa di più leggero. Altrimenti, se sai che dovrai usare spesso ed è parte del core, prova Novoda.
Nicolas Jafelle,

13

Controlla il mio fork di LazyList . Fondamentalmente, posso migliorare LazyList ritardando la chiamata di ImageView e creare due metodi:

  1. Quando devi inserire qualcosa come "Caricamento immagine in corso ..."
  2. Quando è necessario mostrare l'immagine scaricata.

Ho anche migliorato ImageLoader implementando un singleton in questo oggetto.


12

Se si desidera visualizzare il layout Shimmer come Facebook, esiste una libreria Facebook ufficiale per questo. FaceBook Shimmer Android

Si prende cura di tutto, devi solo mettere il tuo codice di progettazione desiderato in modo nidificato in una cornice luccicante. Ecco un codice di esempio.

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

Ed ecco il codice Java per questo.

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

Aggiungi questa dipendenza nel tuo file gradle.

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

Ecco come appare.Schermata di Android Shimmer


11

Tutto il codice sopra riportato ha il suo valore, ma con la mia esperienza personale provalo con Picasso.

Picasso è una libreria appositamente per questo scopo, infatti gestirà automaticamente la cache e tutte le altre operazioni di rete. Dovrai aggiungere la libreria nel tuo progetto e scrivere una sola riga di codice per caricare l'immagine dall'URL remoto.

Visitate qui: http://code.tutsplus.com/tutorials/android-sdk-working-with-picasso--cms-22149


9

Usa la libreria glide. Ha funzionato per me e funzionerà anche per il tuo codice. Funziona sia per le immagini che per le gif.

ImageView imageView = (ImageView) findViewById(R.id.test_image); 
    GlideDrawableImageViewTarget imagePreview = new GlideDrawableImageViewTarget(imageView);
    Glide
            .with(this)
            .load(url)
            .listener(new RequestListener<String, GlideDrawable>() {
                @Override
                public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {                       
                    return false;
                }

                @Override
                public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
                    return false;
                }
            })
            .into(imagePreview);
}

7

Posso consigliare un modo diverso che funziona come un fascino: Android Query.

Puoi scaricare quel file JAR da qui

AQuery androidAQuery = new AQuery(this);

Come esempio:

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

È molto veloce e preciso e usando questo puoi trovare molte più funzioni come l'animazione durante il caricamento, ottenere una bitmap (se necessario), ecc.


7

Dare Aquery una prova. Ha metodi incredibilmente semplici per caricare e memorizzare nella cache le immagini in modo asincrono.



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

4

Ho avuto questo problema e ho implementato lruCache. Credo che tu abbia bisogno dell'API 12 e successive o usi la libreria compatibile v4. lurCache è una memoria veloce, ma ha anche un budget, quindi se sei preoccupato di poter usare una cache del disco ... È tutto descritto in Bitmap della cache .

Ora fornirò la mia implementazione, che è un singleton che chiamo da qualsiasi luogo come questo:

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

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

Ecco il codice ideale per memorizzare nella cache e quindi chiamare quanto sopra in getView di un adattatore quando si recupera l'immagine 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);
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.