1. ChemDroid, 07.05.2011 #1
    ChemDroid

    ChemDroid Threadstarter Gast

    Hallo zusammen,

    meine App beinhaltet eine ListActivity, die aus Elementen besteht, die wiederum 3 TextViews beinhalten für Interpret, Titel und Album, sowie eine ImageView zur Darstellung eines Albumarts.
    Die Liste ist quasi so aufgebaut wie die Tracklist in der Standard Musik App.
    Die Daten werden aus MediaStore per Cursor abgefragt und der Cursor mit einem modifizierten SimpleCursorAdapter an die ListView gebunden.

    Hier ist der Code:
    Code:
    void setListAdapterOverride() {
        String[] fromColumns = new String[] {
                AudioColumns.ARTIST,
                MediaColumns.TITLE,
                AudioColumns.ALBUM,
                AudioColumns.ALBUM_ID
        };
        int[] toColumns = new int[] {
                R.id.tv_artist,
                R.id.tv_title,
                R.id.tv_album,
                R.id.iv_albumArt
        };
        cursorAdapter = new customAdapter(getBaseContext(), R.layout.listviewitem, cursor, fromColumns, toColumns);
        setListAdapter(cursorAdapter);
        if (MyDebug.Log)
            Log.d("Activity", "ListAdapter gesetzt");
    }
    
    class customAdapter extends SimpleCursorAdapter {
    
        int layout;
        Cursor cursor;
        String[] from;
        int[] to;
    
        public customAdapter(Context context, int layout, Cursor c,
                String[] from, int[] to) {
            super(context, layout, c, from, to);
            this.layout = layout;
            this.cursor = c;
            this.from = from;
            this.to = to;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View row = convertView;
            if (row == null) {
                LayoutInflater inflater = getLayoutInflater();
                row = inflater.inflate(R.layout.listviewitem, parent, false);
                if (MyDebug.Log)
                    Log.d("Activity", "Inflate");
            }
            cursor.moveToPosition(position);
            TextView artist = (TextView) row.findViewById(to[0]);
            TextView title = (TextView) row.findViewById(to[1]);
            TextView album = (TextView) row.findViewById(to[2]);
            ImageView albumArt = (ImageView) row.findViewById(to[3]);
            artist.setText(cursor.getString(cursor.getColumnIndex(from[0])));
            title.setText(cursor.getString(cursor.getColumnIndex(from[1])));
            album.setText(cursor.getString(cursor.getColumnIndex(from[2])));
    
            Cursor albumArtCursor = contentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, new String[] { MediaStore.Audio.Albums._ID, MediaStore.Audio.Albums.ALBUM_ART }, MediaStore.Audio.Albums._ID + "='" + cursor.getInt(cursor.getColumnIndex(from[3])) + "'", null, null);
            albumArtCursor.moveToFirst();
            String albumArtUri = albumArtCursor.getString(albumArtCursor.getColumnIndex(MediaStore.Audio.Albums.ALBUM_ART));
            if (albumArtUri == null) albumArtUri = "default";
            if (!imageCache.containsKey(albumArtUri)) {
                Bitmap albumArtBitmap;
                if (!albumArtUri.equals("default")) {
                    Options opts = new Options();
                    opts.inJustDecodeBounds = true;
                    BitmapFactory.decodeFile(albumArtUri, opts);
                    Integer[] bitmapSize = new Integer[] { opts.outWidth, opts.outHeight };
                    Integer scaleFactor = 1;
                    while ((bitmapSize[0]/2 > 50) && (bitmapSize[1]/2 > 50)) {
                        scaleFactor++;
                        bitmapSize[0] /= 2;
                        bitmapSize[1] /= 2;
                    }
                    opts = new Options();
                    opts.inSampleSize = scaleFactor;
                    albumArtBitmap = BitmapFactory.decodeFile(albumArtUri, opts);
                } else {
                    albumArtBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_mp_song_list);
                }
                imageCache.put(albumArtUri, albumArtBitmap);
            }
            albumArt.setImageBitmap(imageCache.get(albumArtUri));
            return row;
        }
    }
    imageCache ist eine HashMap<String, Bitmap> und dient als Cache für die Albumarts, die schon über decodeFile bearbeitet wurden, sodass sie nicht immer wieder neu decodiert werden müssen.

    In einer vorherigen Version des Codes fehlte dieser imageCache nocht, d.h. das Bild wurde jedes mal aus der Datei selbst decodiert. Das machte sich vor allem dadurch bemerkbar, dass die ListView beim scrollen extrem ruckelte.

    Nach Hinzufügen des imageCache ruckelt die ListView zwar nicht mehr so stark wie vorher, aber ein merkliches ruckeln ist dennoch sichtbar.

    Wenn ich die Tracklist in der Musik App anschaue fällt mir auf, dass sie eigentlich gar nicht ruckelt.

    Hat jemand von euch eine Idee wie ich den Code, bzw. das ganze Vorhaben noch weiter optimieren kann?

    Vielen Dank ;)
     
  2. sebastian, 07.05.2011 #2
    sebastian

    sebastian Fortgeschrittenes Mitglied

    ChemDroid bedankt sich.
  3. swordi, 07.05.2011 #3
    swordi

    swordi Gewerbliches Mitglied

    evtl das laden des bildes in einen thread auslagern?
     
  4. ChemDroid, 07.05.2011 #4
    ChemDroid

    ChemDroid Threadstarter Gast

    Hm also noch nen ViewHolder einbauen... Danke werde ich mal ausprobieren.

    Das laden des Bildes in nen externen Thread verlegen habe ich schon ausprobiert, aber leider kann nur der UI-Thread die ImageView ändern...
     
  5. Mort, 07.05.2011 #5
    Mort

    Mort Android-Lexikon

    Handler.post() könnte helfen... Also "<visibility> Handler handler = new Handler();" als Member-Variable, dann im Ladethread handler.post( new Runnable(){....} ); aufrufen. Der Runnable-Code wird dann bei nächster Gelegenheit im UI-Thread ausgeführt.
    Allerdings bleibt noch das Problem, dass die Item-View ggf. schon gar nicht mehr existiert oder für ein anderes Item verwendet wird bis der Bildlade-Thread fertig ist, gerade bei schnellem Scrollen.
    Der Bild-Cache kann sich übrigens schnell als gelungener Schuss ins Knie entpuppen, v.a. auf etwas älteren/schwächeren Geräten, bei denen der Heap pro App auf 16MB begrenz ist. Bei 'ner etwas größeren Liste ist ein OutOfMemory vorprogrammiert...
     
  6. Haggy, 07.05.2011 #6
    Haggy

    Haggy Android-Experte

    Mort hat das ganze schon richtig erkannt. Möchte nur noch hinzufügen, dass du zu viel in deiner getView()-Methode machst. Zu viel was den UI-Thread beschäftigt. Um das richtig smooth zu bekommen, wirst du nicht drum rum kommen, das Suchen, Laden und Dekodieren des Bildes in einen Thread auszulagern und per MessageHandler den UI-Thread darüber zu informieren, dass das gewünschte Bild nun im imageCache zur Verfügung steht (als Bitmap gleich am besten). So bleibt die Liste auf Zack und die Bilder erscheinen wenn sie geladen sind (solange eben nichts oder ein statischer Platzhalter). Richtig ist aber, dass du auf jeden Fall prüfen musst, ob deine ImageView noch existent ist. Ich würde hierbei davon abraten, die ImageView in irgendeiner Form an den Cache durchzureichen. Das erzeugt nur Speicherlecks. Eleganter ist, eine eindeutige ID als Tag zu setzen und wenn das Bild geladen ist, zu überprüfen ob das Tag noch dasselbe ist. So kannst du erkennen ob du die richtige ImageView vor dir hast. Dann noch was Allgemeines zu Caches, vor Allem bei großen Daten wie Bilder - vermeide, wenn möglich, einfache HashMaps oder Arrays als Cache. Da geht dir, wie Mort schon meinte, schneller der Speicher aus als du scrollen kannst. Elegante Alternative sind Softreferences oder WeakReferences. Android hat dir bereits eine WeakHashmap gebaut, die sich prima für Caches eignet (Doku lesen nicht vergessen).
     
    ChemDroid bedankt sich.
  7. ChemDroid, 09.05.2011 #7
    ChemDroid

    ChemDroid Threadstarter Gast

    Vielen Dank für eure Antworten :)

    Ich habe es nun mit Hilfe der LazyList aus diesem Beitrag gelöst.

    Der Code ist ein wenig zu lang um ihn hier zu posten, also falls sich jemand für den Code interessiert, twittert mir einfach oder schreibt mir ne Nachricht, ich melde mich dann ;)
     
Die Seite wird geladen...
Ähnliche Themen - Wie verhindere ich Forum Datum
Wie verhindere ich die mehrfach Initialisierung von onCreate ? Android App Entwicklung 18.02.2017
[SINNLOS] Temporäre Spielwiese für die neue AH-App - einfach ignorieren Android App Entwicklung Dienstag um 13:31 Uhr
[OFFEN] Unions wie in C, C++? Android App Entwicklung 08.05.2017
[OFFEN] Rückkehr aus Dialog Fragment. Wie? Android App Entwicklung 29.04.2017
[OFFEN] Wie kann ich mehrere Marker in eine MapView laden Android App Entwicklung 25.03.2017
[OFFEN] Wie kann ich den Hintergrund abhängig vom Vordergrund Bild dynamisch veränderbar machen? Android App Entwicklung 24.03.2017
Wie bekomme ich Map Markers von einer SQL Datenbank Android App Entwicklung 11.03.2017
Wie lese ich den USB Port aus ? Android App Entwicklung 03.03.2017
Wie realisiere ich eine zeitgesteuerte Berechnung und die Anzeige von PDF's Android App Entwicklung 21.02.2017
Wie indizere ich effektiv Datensätze ? Android App Entwicklung 21.02.2017