Galerie performance

  • Hi Leute,


    ich bastel gerade an einer Bildergalerie, die Teil meiner neuen App wird. Dabei zeige ich sowohl lokal gespeicherte als auch online liegende Bilder an. Momentan habe ich das ganze wie folgt aufgebaut:


    Ich erzeuge im Prinzip pro Kategorie eine GridView, in die ich ImageViews lege. Die ImageViews befüttere ich mit Bitmaps, die ich mithilfe der BitmapFactory verkleinere. Wenn man dann auf ein Bild Klickt wird die große Variante davon in einen ViewPager geladen.


    Habe die Aufrufe zum Bilder holen in AsyncTasks ausgelagert sowie die komplette Abarbeitung in einen eigenen Thread, um den Mainthread zu entlasten und der Meldung zu entgehen, der Mainthread würde zu viel tun und verwirft dann ein paar Elemente. Jedoch dauert das ganze zu lange und ist zu ineffizient.


    Wie wäre hier denn an sich der bevorzugte weg, wie geht man bei sowas eigentlich vor? Hab mich glaub in ein Thread und AsyncTask wirrwarr verstrickt, aus dem ich allein gerade nicht mehr heraus finde.



    Mit freundlichen Grüßen,
    matze

  • Den Link werde ich mir auch noch mal genauer anschauen - so ein ähnliches Problem hab ich auch, aber mit meiner Lösung bin ich noch nicht zufrieden.


    Aber einen Hinweis zum Stichwort "AsyncTasks": wenn du gleichzeitig alle Tasks startest, dann werden die wohl alle ziemlich beschäftigt sein und irgenwann mal fertig werden. Da solltest du lieber eine AsyncTask machen, die eine Warteschlange benutzt um eine Liste von Bilder zu verwursten. Dann kommen die alle der Reihe nach und es sieht flüssiger aus.


    Wenn du dann noch die Zahl der Prozessoren abfragst, kannst du ja ein paar mehr AsyncTasks machen, um die Last zu verteilen - da kannst du gerne rumtesten, was am schnellsten läuft.

  • Hmjoa, momentan isses etwas subptimal. Ich start beim Drücken auf den Menüpunkt einen Thread, der ne for-schleife durch rödelt und pro Durchlauf eine Kategorie zusammen baut. Im Prinzip wird nur eine ArrayList mit Beans dem Adapter der GridView übergeben, der dann in der View-Methode (hab den Code grad nich da, verzeiht dass mir der genaue Name der Methode nicht einfällt) eine ImageView zusammen baut und sich mittels einer Methode, die einen AsyncTask startet, entweder ein Bitmap aus dem assets-Ordner oder ein Bitmap von einer URL aus spuckt. Das werf ich dann in die ImageView und gibs zurück.


    Also im Endeffekt 1 Bild 1 AsyncTask. Aber halt nie mehrere AsyncTasks nebeneinander. mir fällt gerade auf wieso starte ich nicht pro Kategorie einen AsyncTask, werfe den aber in einen Pool, der maximal anz_cpus gleichzeitig ausführt .... hmmm, muss ich morgen mal testen ;)


    Danke Uwe.


    Werd ich morgen genauer experimentieren, was da am flüssigsten läuft. Leider hab ich nur das eine Galaxy Nexus zum testen da, *grml* (zwegs cpu prozis)

  • Für mich ist beim Testen immer mein billiges Galaxy Y das Maß der Dinge - meine Apps sollen ja auch auf langsamen Geräten gut laufen.


    Aber die Zahl der AsyncTasks ist wirklich eine Sache, wo man experimentieren sollte - auch wenn nur ein Prozessor da ist. Beim Zugriff auf die SD-Karte macht das vielleicht nicht so viel Unterschied, aber bei Netzwerkzugriffen kann das schon mal viel ändern (lange Reaktionszeit des Servers bei kleinen Datenmengen).

  • Kurzer Zwischenstand:


    Auf den ersten Blick macht dieser Android Universal Image Loader wesentlich mehr Probleme als er löst. Von sauberem Exception-Handling kann wohl kaum die rede sein :( aber so leicht gebe ich noch nicht auf


    EDIT:
    Nach ausführlichem Fluchen und wilden Beschimpfungen meinerseits zeigt er nun tatsächlich etwas an. Das Teil zerlegt es bei jeder Gelegenheit komplett mit einer NullPointerException mit der man überhaupt nichts anfangen kann. Debuggt man etwas im Source herum sieht man jedoch, dass man z.B. der ImageView, die man übergibt, LayoutParams verpassen muss, weil die ausgelesen und nicht auf null abgefragt werden (ohman). Lauter solche scherze und kleinkram der enormst auf die nerven geht ...
    Das ganze funktioniert nun beim ersten Aufruf, jedoch beim zweiten haut er mir OutOfMemoryExceptions um die Ohren. Ich vermute mal ich konnte ihm noch nicht begreiflich machen, dass er die Bilder zu cachen hat.


    Ich meld mich wieder ;)


    Edit 2:
    Ich gebs auf, das hat keinen Sinn. Da gibts die Option CacheOnDisc und beim Laden des Bildes schaut er nur im Memory nach ob es dort ist. Da bin ich besser beraten, wenn ich mir meinen eigenen Cache baue, da weiß ich wenigstens an welcher Stelle das Ding seine Schwächen hat.


    Danke für den Tipp killphil aber ich hab zu wenig Nerven für das Teil.

  • Hoi,


    habe es jetzt soweit auf meinem Galaxy Nexus lauffähig, im Emulator schmiert er ab ...


    Diese Stelle

    Code
    BitmapFactory.Options options = new BitmapFactory.Options();
    options = new BitmapFactory.Options();
    options.inSampleSize = sampleSize;
    options.inDither = false; 
    options.inPreferQualityOverSpeed = false; 
    options.inPurgeable = true;
    
    
    Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);


    Erzeugt folgende Fehlermeldung:


    Die Frage ist, wie umgehe ich das ... hab die Bilder in ein paar Auflösungen vorliegen aber entscheide je nach device width eh schon, welche am besten ist, damit es nicht zu pixelig wird.



    Gruß,
    matze

  • Hallo matze,


    schade das du so Probleme hast, ich hab den UniversalImageLoader "damals" nur für ein Projekt benutzt wo Gallerien nachgeladen werden (ganz klassisch -< Listview mit Image -> GridView mit vielen Images -> Detailview mit Image zum Swipen) Ich sage damals, weil ich eben noch mal geschaut habe, da war es Version 1.6.2. (und die App brauchte bisher noch kein Update).


    Das Einbinden war relativ einfach (Ableitung Baseactivity) und ich hatte nicht solche gravierenden Probleme wie du. Muss mir wahrscheinlich mal die neue Version anschauen eventl haben Sie da etwas verschlimmbessert ....

  • Hoi,


    das Problem ist wohl, wenn man weiß was man setzen muss damit es nicht kracht, läuft das ganze womöglich. Nur könnte man auch einfach auf null abfragen und brauchbare Exceptions werfen oder wenigstens was ins Javadoc schreiben ... aber gut, hilft nicht, kost ja auch nichts.



    Hab mir für meinen Plan ein paar Dinge abschauen können, so übergebe ich z.B. beim setzen des Images die ImageView auch einfach mit und setze dann das Bitmap im nachhinein, wenn der AsyncTask so weit ist. Das hat den coolen effekt, dass wenn man auf die Bilder-Galerie klickt die nach und nach auf ploppen, gefällt mir eigentlich gut.


    Meinen OutOfMemoryError konnte ich etwas eingrenzen, ich dussel hab hier mal einen Cursor nicht geschlossen, dort mal ein Bitmap nicht recycled ... jetzt kann ich die Galerie betreten und auch ein paar Bilder in einer größeren Ansicht betrachten und dank ViewPager innerhalb der Kategorie nach links und rechts durch wischen. Jedoch nach dem 5 - 10. wischen kapituliert er wieder mit OutOfMemory. Glaub mein

    Code
    @Override
        public void destroyItem(View collection, int position, Object view) {
        	((GalleryView) collection).removeView((LinearLayout) view);
        }


    gibt die Resourcen im LinearLayout nicht direkt frei ... werde mal sehen ob ich irgendwie bis zu meiner ImageView und dessen Bitmap durch komm, um dort nochmal ein .recycle() aufzurufen.



    Generell habe ich das ganze jetzt so verwirklicht:
    In meinem SplashScreen stoße ich in meinem ImageLoader eine Methode an, die alle Images cachen soll, die nicht schon gecached wurden. Konkret frage ich ab, ob in meiner SQLite Tabelle hierzu bereits ein Blob vorliegt. Tut er das geht es weiter, tut er das nicht erzeuge ich mithilfe der BitmapFactory je nachdem ein Thumbnail oder die große Vorschau davon und speichere den Blob in die Datenbank. Danach recycle ich das Bitmap, da ich es gerade nicht mehr brauche, von hand.
    Gehe ich dann in der App auf die Galerie frage ich mit demselben ImageLoader nach dem Bitmap, der greift dann direkt in die Datenbank, holt sich das Blob und erstellt daraus ein Bitmap, das er der ImageView einpflanzt.



    Bin an sich noch etwas gemischter Gefühle, selbst wenn ich das ganze jetzt so zum laufen bekomme, was passiert wenn 100 Bilder dazu kommen .... muss leider sagen in iOS ist das ganze etwas unproblematischer irgendwie. Da weiß man halt einfach mit was für ner Hardware der User daher steigt und muss es nicht auspendeln ...



    Gruß,
    matze

  • Hoi,


    kleiner Nachtrag, hab das ganze jetzt an sich so weit durch, dass es offenbar nur noch an einer Stelle scheitert, und das ist dieser Schnipsel

    Code
    return BitmapFactory.decodeByteArray(blob, 0, blob.length);


    Man liest immer wieder, dass die BitmapFactory eigene VM Arguments für seinen verfügbaren RAM hat und dieser recht gering ist. Finde aber leider auch keinen anderen Weg, aus einem byte[] ein Bitmap zu bekommen. Hat da jemand eine Idee?


    Bei meinen weiteren Recherchen bin ich nun auf folgenden Artikel gestoßen
    http://developer.android.com/t…laying-bitmaps/index.html


    Und mir wirft sich die Frage auf, ob ich meinen über die SQLite Datenbank gehandelter Cache durch den im Artikel erwähnten LruCache ersetzen soll oder vielleicht auch miteinander kombinieren indem ich im SplashScreen den LruCache aus dem DB-Cache heraus aufbaue ... was meint ihr?


    Des weiteren werde ich noch genauer untersuchen, inwieweit ich meine Bitmaps off the UI Thread auf die dort empfohlene Weise handle. Ich benutze an sich bereits AsynkTasks dafür, im nachhinein, wenn der Executor es zu lässt, mein Bitmap zu verwursten und in die ImageView zu werfen. Allerdings handle ich nichts dergleichen, was deren AsyncDrawable betrifft. Werd ich gleich mal ausprobieren.


    Ich meld mich wieder ;)


    Gruß,
    matze



    EDIT:


    Glaub bin grad auf des Rätsels Lösung gestoßen. Hab als Minimum SDK 10 eingestellt und der Emulator steht auch auf 10. Mein Galaxy Nexus hat 4.2.2 also 17 oder so (egal, auf jeden Fall mehr xD) und irgendwo bei Honeycomb hat sich bissl was geändert bzgl. Bitmap umd ImageView. Vorher soll man für das Bitmap explizit ein recycle aufrufen, später ist es nicht mehr so wichtig. Hab mich jetzt mal im destroyItem meines ViewPagers bis zum Bitmap durch gewühlt und ein recycle aufgerufen. Und siehe da, der Emulator schmiert nicht mehr weg!


    Haut mich bitte nicht für den Code, es Funktioniert der Rest ist mir jetzt erstmal egal :D



    Gruß,
    matze


    EDIT 2:


    Ach ja, hab auch in meinem ViewPager die Zeile

    Java
    this.setOffscreenPageLimit(1);


    eingefügt, damit er sich nicht so viele Views aufhebt.




    EDIT 2:


    Ich setz diesen Thread hier mal auf erledigt. Hab mir heute das HTC Wildfire S einer Bekannten unter den Nagel gerissen und damit getestet. Auch in diversen verschieden eingestellten Emulatoren ging es dann stabil. Dreh und Angelpunkt ist der VM Heap, der bei älteren Geräten um 2.3.3 bei 16 MB liegt, später dann 24, 32 ... mein Galaxy Nexus hat meine ich 64MB.


    Wenn jemand tieferes Interesse an meinem jetzigen Code hat oder auch noch Fragen / Anmerkungen / Verbesserungsvorschläge bin ich auf jeden Fall sehr aufgeschlossen.


    Gruß,
    matze

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!