android - Caricamento lento delle immagini in ListView




image url (20)

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

Il numero totale di immagini non è fisso.


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 cosa vuoi fare e leggi la pagina wiki della libreria. E risolvi la restrizione del caricamento dell'immagine.

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


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 drawable è più veloce di quello con bitmap, quindi uso invece drawable

  2. L'utilizzo di SoftReference è ottimo, ma rende l'immagine memorizzata nella cache troppo spesso, quindi ho aggiunto un elenco collegato che contiene riferimenti alle immagini, impedendo l'eliminazione dell'immagine, 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;
}
}

Devi provare questo Universal Loader è il migliore. Sto usando questo dopo aver fatto molti RnD sul caricamento pigro.

github.com/nostra13/Android-Universal-Image-Loader

Caratteristiche

  • Caricamento immagine multithread (asincrono o sincronizzazione)
  • Ampia personalizzazione della configurazione di ImageLoader (thread executor, downloader, decoder, memoria e cache del disco, opzioni di visualizzazione delle immagini, ecc.)
  • Molte opzioni di personalizzazione per ogni chiamata di immagini di visualizzazione (immagini stub, interruttore di memorizzazione nella cache, opzioni di decodifica, elaborazione e visualizzazione di Bitmap, ecc.)
  • Memorizzazione nella cache dell'immagine in memoria e / o su disco (file system del dispositivo o scheda SD)
  • Ascolto del processo di caricamento (incluso lo stato di download)

Supporto per Android 2.0+


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

Ho scritto un tutorial che spiega come eseguire il caricamento lazy delle immagini in un listview. Vado in alcuni dettagli sulle questioni del riciclaggio e della concorrenza. Uso anche un pool di thread fisso per evitare di generare molti thread.

Caricamento lento delle immagini in Listview Tutorial



Penso che questo problema sia molto popolare tra gli sviluppatori Android, e ci sono un sacco di librerie del genere che pretendono di risolvere questo problema, ma solo alcune sembrano essere sul punto. AQuery è una di queste librerie, ma è meglio della maggior parte di esse in tutti gli aspetti e vale la pena provarci.


Questo è un problema comune su Android che è stato risolto in molti modi da molte persone. A mio parere, la soluzione migliore che ho visto è la relativamente nuova libreria chiamata Picasso . Ecco i punti salienti:

  • Open source, ma diretto da Jake Wharton di ActionBarSherlock .
  • Carica in modo asincrono le immagini da risorse di rete o di app con una sola riga di codice
  • Rilevamento automatico di ListView
  • Memorizzazione automatica del caching del disco e della memoria
  • Può fare trasformazioni personalizzate
  • Molte opzioni configurabili
  • API super semplice
  • Frequentemente aggiornato


Voglio solo aggiungere un altro buon esempio, XML Adapters . Come è utilizzato da Google e sto anche usando la stessa logica per evitare un errore OutOfMemory.

Fondamentalmente, questo ImageDownloader è la tua risposta (poiché copre la maggior parte delle tue esigenze). Alcuni si possono anche implementare in questo.


DroidParts ha ImageFetcher che richiede zero configurazione per iniziare.

  • Utilizza una cache LRU (disk Recently In Used ) e in memoria.
  • Decodifica in modo efficiente le immagini.
  • Supporta la modifica di bitmap nel thread in background.
  • Ha una dissolvenza incrociata semplice.
  • Ha richiamato il caricamento dell'immagine in corso.

Clone DroidPartsGram per un esempio:



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

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

C'è una discussione di un anno fa riguardante le SoftReferences su gruppi di google: link to thread . Come soluzione alla raccolta dei rifiuti troppo precoce, suggeriscono la possibilità di impostare manualmente la dimensione dell'heap della VM utilizzando dalvik.system.VMRuntime.setMinimumHeapSize (), che per me non è molto interessante.

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

Picasso

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

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

Le immagini aggiungono il contesto e il fascino visivo necessari alle applicazioni Android. Picasso consente il caricamento delle immagini senza problemi nell'applicazione, spesso in un'unica riga di codice!

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

Molte delle più comuni insidie ​​del caricamento delle immagini su Android sono gestite automaticamente da Picasso:

Gestione del riciclaggio di ImageView e cancellazione del download in un adattatore. Trasformazioni complesse dell'immagine 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 dei media open source veloce ed efficiente per Android che avvolge la decodifica dei media, la memoria e il caching del disco e il pooling delle risorse in un'interfaccia semplice e facile da usare.

Glide supporta il recupero, la decodifica e la visualizzazione di fermi immagine, immagini e GIF animate. Glide include una API flessibile che consente agli sviluppatori di collegarsi a quasi tutti gli stack di rete. Per impostazione predefinita, Glide utilizza uno stack basato su HttpUrlConnection personalizzato, ma include anche 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 è quello di far scorrere qualsiasi tipo di elenco di immagini il più agevole e veloce possibile, ma Glide è efficace anche in quasi tutti i casi in cui è necessario recuperare, ridimensionare e visualizzare un'immagine remota.

Glide Image Caricamento della libreria

Affresco su Facebook

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

Affresco si occupa del caricamento e della visualizzazione delle immagini, quindi non è necessario. Caricherà le immagini dalla rete, dall'archivio locale o dalle risorse locali e visualizzerà un segnaposto finché l'immagine non sarà arrivata. Ha due livelli di cache; uno in memoria e un altro nella memoria interna.

Affresco Github

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

Documentazione dell'affresco


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

Ci sono quattro modi di base.

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

  2. Libreria di caricamento pigro di Volley => Da ragazzi su Android. È bello e tutto ma è scarsamente documentato e quindi è un problema da usare.

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

  4. UIL: il modo migliore per scaricare le immagini pigre. È possibile memorizzare le immagini nella cache (è necessaria l'autorizzazione, ovviamente), inizializzare il caricatore una volta, quindi eseguire il lavoro. La più matura libreria di caricamento di immagini asincrone che abbia mai visto finora.


Tutti i codici di cui sopra hanno il loro valore, ma con la mia esperienza personale provate con Picasso.

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

Si prega di visitare qui: http://code.tutsplus.com/tutorials/android-sdk-working-with-picasso--cms-22149


Fai una prova con Android-Query . Ha metodi sorprendentemente semplici per caricare e memorizzare le immagini in modo asincrono.


Ho avuto questo problema e implementato lruCache. Credo che tu abbia bisogno dell'API 12 o superiore o che usi la libreria v4 compatibile. lurCache è una memoria veloce, ma ha anche un budget, quindi se sei preoccupato di ciò puoi usare una cache del disco ... Tutto è descritto in developer.android.com/training/displaying-bitmaps/… .

Ora fornirò la mia implementazione che è un singleton che chiamo da qualsiasi parte in questo modo:

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

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

Ecco il codice ideale da memorizzare nella cache e quindi richiamare 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);
        }
    }
}

Se vuoi visualizzare il layout Shimmer come Facebook, c'è una libreria ufficiale di Facebok per questo. FaceBook Shimmer Android

Si prende cura di tutto, Hai solo bisogno di mettere il codice di progettazione desiderato in modo annidato nel telaio shimmer. 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:[email protected]'

Ecco come sembra.


Usa la libreria di 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);
}




universal-image-loader