Jak leniwie ładować obrazy w ListView w Androidzie


1930

Używam a, ListViewaby wyświetlić niektóre obrazy i podpisy związane z tymi obrazami. Dostaję obrazy z Internetu. Czy istnieje sposób na leniwe ładowanie obrazów, więc podczas wyświetlania tekstu interfejs użytkownika nie jest blokowany, a obrazy są wyświetlane podczas pobierania?

Całkowita liczba zdjęć nie jest stała.


10
Możesz użyć AsyncImageView GreenDroida . Po prostu zadzwoń setUrl.
Pascal Dimassimo,

7
Użyłem tego. To wspaniałe wdrożenie. Zła wiadomość, że AsyncImageView jest częścią dużego projektu GreenDroid, dzięki czemu Twoja aplikacja jest większa nawet w przypadku, gdy potrzebujesz tylko AsyncImageView. Wygląda również na to, że projekt GreenDroid nie jest aktualizowany od 2011 roku.
borisstr

5
Możesz nawet wypróbować tę bibliotekę: Android-http-image-manager, który moim zdaniem jest najlepszy do asynchronicznego ładowania obrazów.
Ritesh Kumar Dubey,

34
Wystarczy użyć Picassa, zrobi to samo. „Picasso.with (yourContext) .load (img src / path / drawable here) .into (imageView tj. Twój cel);” Otóż ​​to!
Anuj Sharma

6
spróbuj użyć: github.com/nostra13/Android-Universal-Image-Loader , ta biblioteka jest bardzo szybka i wydajna w przypadku leniwego ładowania i buforowania obrazów
krunal patel

Odpowiedzi:


1086

Oto, co stworzyłem, aby przechowywać obrazy, które obecnie wyświetla moja aplikacja. Należy pamiętać, że używany tutaj obiekt „Log” to moje niestandardowe opakowanie wokół ostatniej klasy Log w systemie 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
Myślę, że powinieneś użyć SoftReferences, aby twój program nigdy nie spowodował wyjątku OutOfMemoryException. Ponieważ GC może wyczyścić miękkie odniesienia, gdy rośnie rozmiar sterty ... możesz zarządzać własnym pokoleniem, tak jak po kilku sekundach możesz umieścić swoje zdjęcia na tej liście, a przed załadowaniem powinieneś sprawdzić, czy jeśli obraz istnieje, nie pobieraj go ponownie, a raczej zbieraj z tej listy, a także umieszczając ją z powrotem na liście softref, a po pewnym czasie możesz wyczyścić swoją twardą listę :)
AZ_

36
Projekt Google Shelves to doskonały przykład. Zobacz, jak zrobili code.google.com/p/shelves
AZ_

12
Czy nie przegapisz zwrotu, gdy drawableMap zawiera obraz ... bez rozpoczynania wątku pobierania?
Karussell

5
Ten kod ma kilka problemów. Najpierw powinieneś buforować Drawables, które spowodują wyciek pamięci: stackoverflow.com/questions/7648740/… . Po drugie, sama pamięć podręczna nigdy nie jest czyszczona, więc będzie rosła wiecznie, to kolejny wyciek pamięci.
satur9nine


1024

Zrobiłem proste demo leniwej listy (znajdującej się na GitHub) z obrazkami.

Podstawowe użycie

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

Nie zapomnij dodać następujących uprawnień do pliku AndroidManifest.xml:

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

utwórz tylko jedną instancję programu ImageLoader i ponownie użyj go w aplikacji. W ten sposób buforowanie obrazów będzie znacznie wydajniejsze.

Może to komuś pomóc. Pobiera obrazy w tle wątku. Obrazy są buforowane na karcie SD i w pamięci. Implementacja pamięci podręcznej jest bardzo prosta i wystarcza do pokazu. Dekoduję obrazy za pomocą inSampleSize, aby zmniejszyć zużycie pamięci. Staram się również poprawnie obsługiwać widoki przetworzone.

Tekst alternatywny


7
Dziękuję, używam nieco zmodyfikowanej wersji twojego kodu prawie wszędzie w moim projekcie: code.google.com/p/vimeoid/source/browse/apk/src/com/fedorvlasov/…
shaman.sir

3
Czy ktoś spojrzał na kod Gillesa Debunne'a jako alternatywę. Dwie duże różnice, które widzę, to 1) buforowanie pamięci w porównaniu do karty SD i 2) użycie AsyncTasks zamiast puli wątków. android-developers.blogspot.com/2010/07/…
Richard

4
Występuje błąd, czasem jest uruchamiany: 10-13 09: 58: 46.738: ERROR / AndroidRuntime (24250): Nie przechwycony moduł obsługi: wyjście wątku z powodu nieprzechwyconego wyjątku 10-13 09: 58: 46.768: BŁĄD / AndroidRuntime (24250) : java.lang.ArrayIndexOutOfBoundsException: Indeks tablicy poza zakresem: 0 10-13 09: 58: 46.768: ERROR / AndroidRuntime (24250): at java.util.Vector.elementAt (Vector.java:331) 10-13 09: 58: 46.768: ERROR / AndroidRuntime (24250): at java.util.Vector.get (Vector.java:445) 10-13 09: 58: 46.768: ERROR / AndroidRuntime (24250): at com.my.app.image .ImageLoader $ PhotosQueue.Clean (ImageLoader.java:91)
Mikooos,

64
Chciałbym dodać dla początkujących, takich jak ja, że ​​jeśli chcesz dodać ten kod do własnego projektu, musisz dodać uprawnienia do zewnętrznej pamięci podręcznej w manifeście. Dziękujemy
Adam,

2
@BruceHill W najnowszym kodzie źródłowym nie ma zdjęć. Wygląda na to, że używasz bardzo starej wersji.
Fedor

551

Polecam instrument open source Universal Image Loader . Pierwotnie opiera się na projekcie LazyList Fedora Własowa i od tego czasu znacznie się poprawił.

  • Ładowanie obrazu wielowątkowego
  • Możliwość szerokiego dostrajania konfiguracji ImageLoadera (executory wątków, downlaoder, dekoder, pamięć podręczna i pamięć podręczna dysku, opcje wyświetlania obrazu i inne)
  • Możliwość buforowania obrazu w pamięci i / lub w systemie plików urządzenia (lub na karcie SD)
  • Możliwość „załadowania” procesu ładowania
  • Możliwość dostosowania każdego wywołania obrazu wyświetlanego za pomocą oddzielnych opcji
  • Obsługa widżetów
  • Obsługa Androida 2.0+


16
jeśli szukasz świetnego kodu „ładowania obrazu”, który jego twórca traktuje i utrzymuje jako swoje cenne dziecko (nie tylko jednorazowe rozwiązanie), właśnie go znalazłeś; big ups Nostra
AndroidGecko

Naprawdę świetna robota, ale trochę opóźnia mój widok listy. Jeśli potrafisz powiedzieć najlepszą kompilację „ImageLoader”… a może to dlatego, że „DisplayImageOptions”.
dinigo

Bardzo podoba mi się ten widok siatki, ale nie mogę go używać w moim samouczku. Jestem początkującym androidem. Tworzę aplikację o gridview, ale moją aplikacją był targetSdkVersion 15, więc muszę używać Asynctask do procesu w wątku w tle. Kiedyś korzystałem z tego widoku siatki, ale to nie działa. jak mogę go używać w targetSdkVersion 15?
kongkea

Możesz utworzyć osobne pytanie z tagiem [Universal-image-loader]
nostra13,

3
Po przeczytaniu oficjalnych dokumentów Androida i samodzielnym wykonaniu tego, jestem pewien, że ta biblioteka to koniec ładowania obrazu. Nie mogę głosować wystarczająco.
Rdzeń

158

Wielowątkowość dla wydajności , samouczek Gillesa Debunne.

Pochodzi z bloga programistów Androida. Sugerowany kod wykorzystuje:

  • AsyncTasks.
  • Twardy, ograniczony rozmiar, FIFO cache.
  • Miękka, łatwa do garbage collectprzechowywania pamięć podręczna.
  • Symbol zastępczy Drawable podczas pobierania.

wprowadź opis zdjęcia tutaj


10
Działa dobrze również w wersji 2.1. Po prostu nie używaj AndroidHttpClient.
Thomas Ahle,

2
@ thomas-ahle Dziękuję, widziałem, że AndroidHttpClient dawał błędy w wersji 2.1, ponieważ został zaimplementowany w wersji 2.2, ale tak naprawdę nie próbował znaleźć czegoś innego, aby go zastąpić.
Adinia

4
@Adina Masz rację, zapomniałem. Jednak w przepisie nie ma nic, czego nie można zrobić równie dobrze z normalnym klientem HttpClient.
Thomas Ahle,

Słyszałem w kilku miejscach, że Google nie zaleca miękkich referencji, ponieważ jądro Androida bardzo chętnie gromadzi te referencje w porównaniu do wcześniejszych wersji systemu.
Muhammad Ahmed AbuTalib

108

Aktualizacja: Pamiętaj, że ta odpowiedź jest teraz dość nieskuteczna. Garbage Collector działa agresywnie na SoftReference i WeakReference, więc ten kod NIE jest odpowiedni dla nowych aplikacji. (Zamiast tego wypróbuj biblioteki takie jak Universal Image Loader sugerowane w innych odpowiedziach).

Podziękowania dla Jamesa za kod i Bao-Long za sugestię użycia SoftReference. Zaimplementowałem zmiany SoftReference w kodzie Jamesa. Niestety SoftReferences spowodowało, że moje obrazy były zbyt szybko usuwane. W moim przypadku było dobrze bez elementów SoftReference, ponieważ mój rozmiar listy jest ograniczony, a moje obrazy są małe.

Rok temu była dyskusja na temat SoftRefereferencji w grupach google: link do wątku . Jako rozwiązanie zbyt wczesnego wyrzucania elementów bezużytecznych sugerują możliwość ręcznego ustawienia wielkości sterty maszyny wirtualnej za pomocą dalvik.system.VMRuntime.setMinimumHeapSize (), co nie jest dla mnie zbyt atrakcyjne.

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
możesz tworzyć pokolenia takie jak pokolenie twarde i softgeneracja. możesz naprawić wyczyszczoną pamięć podręczną, która wyczyści wszystkie obrazy, do których nie uzyskano dostępu w ciągu 3 sekund. możesz zajrzeć do projektu półek Google
AZ_

developer.android.com/reference/java/lang/ref/… Dokument SoftReference zawiera notatkę na temat buforowania, zobacz sekcję „Unikaj miękkich odniesień do buforowania”. Większość aplikacji powinna używać android.util.LruCache zamiast miękkich odniesień.
vokilam

Podziwiam twój kod, ale teraz w nowym systemie operacyjnym Android jest „agresywne” zbieranie śmieci. Trzymanie słabego odniesienia nie ma dla mnie żadnego sensu.
j2emanue

@ j2emanue Masz rację, jak starałem się wskazać na górze mojej odpowiedzi, SoftRefereferencje są zbyt szybko usuwane. Spróbuję edytować tę odpowiedź, aby było jeszcze jaśniej.
TalkLittle

96

Picasso

Skorzystaj z biblioteki Picassa Jake'a Whartona. (A Perfect ImageLoading Library od twórcy ActionBarSherlock)

Potężna biblioteka do pobierania i buforowania obrazów dla Androida.

Obrazy nadają aplikacjom na Androida bardzo potrzebny kontekst i wizualny styl. Picasso umożliwia bezproblemowe ładowanie obrazu w aplikacji - często w jednym wierszu kodu!

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

Picasso często obsługuje wiele typowych pułapek ładowania obrazu na Androida:

Obsługa recyklingu ImageView i anulowania pobierania w adapterze. Złożone transformacje obrazu przy minimalnym zużyciu pamięci. Automatyczne buforowanie pamięci i dysku.

Biblioteka Picassa Jake'a Whartona

Poślizg

Glide to szybka i wydajna platforma zarządzania multimediami typu open source dla systemu Android, która otacza dekodowanie multimediów, pamięć podręczną i pamięć podręczną dysków oraz pule zasobów w prosty i łatwy w użyciu interfejs.

Glide obsługuje pobieranie, dekodowanie i wyświetlanie zdjęć, obrazów i animowanych plików GIF. Glide zawiera elastyczny interfejs API, który pozwala programistom na podłączenie do prawie każdego stosu sieciowego. Domyślnie Glide używa niestandardowego stosu opartego na HttpUrlConnection, ale zawiera także biblioteki narzędziowe podłączone do projektu Volley firmy Google lub biblioteki OkHttp Square.

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

Głównym celem Glide jest sprawienie, aby przewijanie dowolnej listy obrazów było tak płynne i szybkie, jak to możliwe, ale Glide działa również w prawie każdym przypadku, gdy trzeba pobrać, zmienić rozmiar i wyświetlić zdalny obraz.

Glide Image Loading Library

Fresk przez Facebook

Fresco to potężny system do wyświetlania obrazów w aplikacjach na Androida.

Fresco dba o ładowanie i wyświetlanie obrazu, więc nie musisz. Będzie ładować obrazy z sieci, pamięci lokalnej lub zasobów lokalnych i wyświetlać symbol zastępczy, dopóki obraz nie dotrze. Ma dwa poziomy pamięci podręcznej; jeden w pamięci, a drugi w pamięci wewnętrznej.

Fresk Github

W Androidzie 4.x i starszych Fresco umieszcza obrazy w specjalnym regionie pamięci Androida. Dzięki temu Twoja aplikacja działa szybciej - i znacznie rzadziej cierpi przerażający OutOfMemoryError.

Dokumentacja fresku


Picasso to biblioteka opracowana przez Square
LordRaydenMK

82

Program ładujący o wysokiej wydajności - po zbadaniu zaproponowanych tutaj metod użyłem rozwiązania Bena z pewnymi zmianami -

  1. Zdałem sobie sprawę, że praca z drawableami jest szybsza niż z mapami bitowymi, więc zamiast tego używam drawable

  2. Korzystanie z SoftReference jest świetne, ale powoduje, że obraz w pamięci podręcznej jest zbyt często usuwany, dlatego dodałem połączoną listę, która zawiera odwołania do obrazów, zapobiegając usunięciu obrazu, dopóki nie osiągnie określonego rozmiaru

  3. Aby otworzyć InputStream, użyłem java.net.URLConnection, który pozwala mi korzystać z pamięci podręcznej (najpierw musisz ustawić pamięć podręczną odpowiedzi, ale to już inna historia)

Mój kod:

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

Działa świetnie! BTW jest literówka w nazwie klasy.
Mullins,

5
W przypadku, gdy oszczędza to komuś czas: 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

Dziękuję bardzo, jest to miłe wdrożenie. Umieściłem również inny symbol zastępczy, gdy ładowany jest drawable, aby użytkownik mógł uzyskać informację zwrotną.
Juan Hernandez,

Myślę też, że lepiej jest użyć kolejki LIFO w executorService (mThreadPool) zamiast domyślnego FIFO, więc najpierw ładowane są ostatnie żądane obrazy (prawdopodobnie te widoczne). Zobacz stackoverflow.com/questions/4620061/how-to-create-lifo-executor
Juan Hernandez

9
@MichaelReed, jeśli jesteś użytkownikiem Eclipse, zalecam użycie Ctrl-Shift-O (to jest litera O, a nie cyfra 0). Automatyzuje proces dodawania importów i organizuje je dla Ciebie. Jeśli korzystasz z komputera Mac, użyj Command-Shift-O.
SilithCrowe

78

Przeszedłem szkolenie Android i myślę, że świetnie sobie radzi z pobieraniem obrazów bez blokowania głównego interfejsu użytkownika. Obsługuje również buforowanie i radzenie sobie z przewijaniem wielu obrazów: Wydajne ładowanie dużych map bitowych


Przepraszam, wskazałem tylko jedną klasę dla aplikacji Google IO (i spóźniłem się na edycję). Powinieneś naprawdę przestudiować wszystkie klasy narzędzi do ładowania i buforowania obrazów, które można znaleźć w tym samym pakiecie co klasa pamięci podręcznej .
mkuech

Czy ktoś poleciłby pobranie plików DiskLruCache, Image * .java z folderu util aplikacji iosched, aby pomóc w ładowaniu / buforowaniu obrazów w widoku listy? Mam na myśli, że zdecydowanie warto śledzić internetowe przewodniki programistów na ten temat, ale te klasy (od iosched) idą nieco dalej z tym schematem.
Gautam,

65

1. Picasso umożliwia bezproblemowe ładowanie obrazu w aplikacji - często w jednym wierszu kodu!

Użyj Gradle:

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

Tylko jedna linia kodu!

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

2. Szybkość Biblioteka ładowania i buforowania obrazu dla Androida skupia się na płynnym przewijaniu

Użyj Gradle:

repositories {
  mavenCentral() 
  google()
}

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

// Dla prostego widoku:

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

3. Fresk to potężny system do wyświetlania obrazów w aplikacjach na Androida. Fresk dba o ładowanie i wyświetlanie obrazów, więc nie musisz.

Pierwsze kroki z Fresco


Ten samouczek może ci pomóc bardziej w PICASOO: - androidtutorialshub.com/... i GLIDE: - androidtutorialshub.com/…
lalit vasan


41

Robię to, uruchamiając wątek, aby pobrać obrazy w tle i przekazać mu oddzwonienie dla każdego elementu listy. Po zakończeniu pobierania obrazu wywołuje funkcję oddzwaniania, która aktualizuje widok elementu listy.

Ta metoda nie działa jednak zbyt dobrze, gdy przetwarzasz widoki.


Używanie wątku do każdego obrazu jest również podejściem, którego używam. Jeśli oddzielisz swój model od widoku, możesz zachować model poza Aktywnością (jak w klasie „aplikacji”), aby zachować pamięć podręczną. Uważaj na wyczerpywanie się zasobów, jeśli masz wiele obrazów.
James A Wilson

czy możesz proszę opracować. Jestem nowy w rozwoju Androida. Dzięki za wskazówki
lostInTransit

14
Rozpoczęcie nowego wątku dla każdego obrazu nie jest skutecznym rozwiązaniem. Możesz skończyć z wieloma wątkami w pamięci i zamrażaniem interfejsu użytkownika.
Fedor

Fedor, zgadzam się, zwykle używam kolejki i puli wątków, to najlepszy sposób, aby przejść do imo.
jasonhudgins


29

Jest to powszechny problem w Androidzie, który został rozwiązany na wiele sposobów przez wiele osób. Moim zdaniem najlepszym rozwiązaniem, jakie widziałem, jest stosunkowo nowa biblioteka Picasso . Oto najważniejsze informacje:

  • Open source, ale na czele z Jake Whartono ActionBarSherlock sławy.
  • Asynchronicznie ładuj obrazy z zasobów sieci lub aplikacji za pomocą jednego wiersza kodu
  • Automatyczne ListViewwykrywanie
  • Automatyczne buforowanie dysku i pamięci
  • Może wykonywać niestandardowe transformacje
  • Wiele konfigurowalnych opcji
  • Super prosty interfejs API
  • Często aktualizowane

29

Korzystam z NetworkImageView z nowej biblioteki Android Volley Library com.android.volley.toolbox.NetworkImageViewi wydaje się, że działa całkiem dobrze. Najwyraźniej jest to ten sam widok, który jest używany w Google Play i innych nowych aplikacjach Google. Zdecydowanie warto to sprawdzić.


1
Myślę, że to najlepsze rozwiązanie - pozostałe odpowiedzi są bardzo stare - siatkówka jest naprawdę szybka i w połączeniu z jake warthons disklrucache to idealne rozwiązanie - próbowałem wielu innych, ale żadna nie jest stabilna i szybka jak siatkówka
Alexander Sidikov Pfeif

26

Czas ładowania obrazu z Internetu ma wiele rozwiązań. Możesz także skorzystać z biblioteki Android-Query . Da ci wszystkie wymagane działania. Upewnij się, co chcesz zrobić, i przeczytaj stronę wiki biblioteki. I rozwiń ograniczenie ładowania obrazu.

To jest mój kod:

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

To powinno rozwiązać twój leniwy problem z ładowaniem.


Dobra robota dla mnie, ale potrzebujesz pliku Jar do włączenia do twojego projektu. Możesz pobrać ten plik JAR stąd AQuery androidAQuery = new AQuery (this); link Jest: code.google.com/archive/p/android-query/downloads
Selim Raza

25

Myślę, że ten problem jest bardzo popularny wśród programistów Androida i istnieje wiele takich bibliotek, które twierdzą, że rozwiązują ten problem, ale tylko niektóre z nich wydają się być naznaczone. AQuery jest jedną z takich bibliotek, ale jest lepsza niż większość z nich we wszystkich aspektach i warto spróbować.


22

Musisz wypróbować ten uniwersalny moduł ładujący. Używam tego po zrobieniu wielu RnD na leniwe ładowanie.

Universal Image Loader

funkcje

  • Ładowanie obrazu wielowątkowego (asynchronizacja lub synchronizacja)
  • Szerokie dostosowanie konfiguracji ImageLoadera (narzędzia do wykonywania wątków, downloader, dekoder, pamięć podręczna i pamięć podręczna dysku, opcje wyświetlania obrazu itp.)
  • Wiele opcji dostosowywania dla każdego wywołania obrazu wyświetlanego (obrazy pośrednie, przełącznik buforowania, opcje dekodowania, przetwarzanie i wyświetlanie bitmapy itp.)
  • Buforowanie obrazu w pamięci i / lub na dysku (system plików urządzenia lub karta SD)
  • Proces ładowania słuchania (w tym postęp pobierania)

Obsługa Androida 2.0+

wprowadź opis zdjęcia tutaj


20

Spójrz na Shutterbug , lekki SDWebImage Applidium (ładna biblioteka na iOS) na Androida. Obsługuje buforowanie asynchroniczne, przechowuje nieudane adresy URL, dobrze obsługuje współbieżność i zawiera przydatne podklasy.

Zgłoszenia ściągania (i zgłoszenia błędów) są również mile widziane!


16

DroidParts ma ImageFetcher, który wymaga zerowej konfiguracji, aby rozpocząć.

  • Korzysta z pamięci podręcznej dysku i pamięci ostatnio używanej (LRU).
  • Skutecznie dekoduje obrazy.
  • Obsługuje modyfikowanie bitmap w wątku tła.
  • Ma prosty cross-fade.
  • Ma wywołanie zwrotne postępu ładowania obrazu.

Klonuj DroidPartsGram na przykład:

Wpisz opis zdjęcia tutaj


Cześć, rzuciłem okiem na przykłady kodu, ale mam problemy z używaniem ImageFetchera z ArrayAdapter, czy mógłbyś spojrzeć na moje pytanie? stackoverflow.com/questions/21089147/… Dzięki =]
masha

16

Krótka wskazówka dla kogoś, kto nie może się zdecydować, jakiej biblioteki użyć do leniwego ładowania obrazów:

Istnieją cztery podstawowe sposoby.

  1. DIY => Nie najlepsze rozwiązanie, ale dla kilku zdjęć i jeśli chcesz przejść bez kłopotów z używaniem innych bibliotek

  2. Biblioteka Leniwego ładowania Volleya => Od facetów z Androida. Jest ładny i wszystko, ale jest słabo udokumentowane, a zatem jest problem w użyciu.

  3. Picasso: Proste rozwiązanie, które po prostu działa, możesz nawet określić dokładny rozmiar obrazu, który chcesz wprowadzić. Jest bardzo prosty w użyciu, ale może nie być zbyt „wydajny” w przypadku aplikacji, które mają do czynienia z ogromną ilością obrazów.

  4. UIL: Najlepszy sposób na leniwe ładowanie zdjęć. Możesz buforować obrazy (oczywiście potrzebujesz zgody), zainicjować moduł ładujący raz, a następnie wykonać swoją pracę. Najbardziej dojrzała asynchroniczna biblioteka ładująca obrazy, jaką kiedykolwiek widziałem.


15

Novoda ma również świetną bibliotekę do ładowania obrazów, a wiele aplikacji, takich jak Songkick, Podio, SecretDJ i ImageSearch, korzysta z ich biblioteki.

Ich biblioteka znajduje się tutaj na Github, a także mają dość aktywny moduł do śledzenia problemów . Ich projekt wydaje się być również dość aktywny, z ponad 300 zobowiązaniami w momencie pisania tej odpowiedzi.


1
W rzeczywistości Novoda to świetna biblioteka, ale ... czasami nie potrzebujesz ogromnej biblioteki, a jedynie proste podejście do rozwiązania. Dlatego LazyList w Github jest tak dobry, jeśli twoje aplikacje pokazują tylko obraz w widoku listy i nie są główną funkcją twojej aplikacji, to kolejna czynność, którą wolałbym użyć czegoś lżejszego. W przeciwnym razie, jeśli wiesz, że będziesz musiał często używać i jest częścią rdzenia, wypróbuj Novoda.
Nicolas Jafelle,

13

Sprawdź mój rozwidlenie LazyList . Zasadniczo poprawiam LazyList opóźniając wywołanie ImageView i tworzę dwie metody:

  1. Gdy musisz wstawić coś w rodzaju „Ładowanie obrazu ...”
  2. Kiedy musisz pokazać pobrany obraz.

Ulepszyłem także ImageLoader, implementując singleton w tym obiekcie.


12

Jeśli chcesz wyświetlić układ Shimmera jak Facebook, istnieje oficjalna biblioteka Facebooka. FaceBook Shimmer Android

Dba o wszystko, wystarczy umieścić żądany kod projektowy w sposób zagnieżdżony w migotliwej ramce. Oto przykładowy kod.

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

A oto kod Java.

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

Dodaj tę zależność do pliku ocen.

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

Oto jak to wygląda.Zrzut ekranu aplikacji Shimmer na Androida


11

Wszystkie powyższe kody mają swoją wartość, ale z moim osobistym doświadczeniem po prostu spróbuj z Picasso.

Picasso jest biblioteką specjalnie do tego celu, w rzeczywistości automatycznie zarządza pamięcią podręczną i wszystkimi innymi operacjami sieciowymi. Musisz dodać bibliotekę do swojego projektu i po prostu napisać jeden wiersz kodu, aby załadować obraz ze zdalnego adresu URL.

Proszę odwiedzić tutaj: http://code.tutsplus.com/tutorials/android-sdk-working-with-picasso--cms-22149


9

Skorzystaj z biblioteki szybownictwa. To zadziałało dla mnie i będzie działać również dla twojego kodu. Działa zarówno z obrazami, jak i gifami.

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

Mogę polecić inny sposób, który działa jak urok: Zapytanie na Androida.

Możesz pobrać ten plik JAR stąd

AQuery androidAQuery = new AQuery(this);

Jako przykład:

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

Jest bardzo szybki i dokładny, a dzięki temu można znaleźć wiele innych funkcji, takich jak animacja podczas ładowania, uzyskanie mapy bitowej (w razie potrzeby) itp.




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

Miałem ten problem i zaimplementowałem lruCache. Uważam, że potrzebujesz interfejsu API 12 lub nowszego lub użyj biblioteki kompatybilności v4. lurCache to szybka pamięć, ale ma również budżet, więc jeśli martwisz się, że możesz użyć dyskietki ... Wszystko to opisano w Buforowanie map bitowych .

Przedstawię teraz moją implementację, która jest singletonem, do którego dzwonię z dowolnego miejsca:

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

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

Oto idealny kod do buforowania, a następnie wywołania powyższego w getView adaptera podczas pobierania obrazu sieciowego:

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);
        }
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.