Qual è il modo migliore per iterare un cursore Android?


291

Vedo spesso il codice che comporta l'iterazione sul risultato di una query del database, l'esecuzione di operazioni con ciascuna riga e il passaggio alla riga successiva. Esempi tipici sono i seguenti.

Cursor cursor = db.rawQuery(...);
cursor.moveToFirst();
while (cursor.isAfterLast() == false) 
{
    ...
    cursor.moveToNext();
}
Cursor cursor = db.rawQuery(...);
for (boolean hasItem = cursor.moveToFirst(); 
     hasItem; 
     hasItem = cursor.moveToNext()) {
    ...
}
Cursor cursor = db.rawQuery(...);
if (cursor.moveToFirst()) {
    do {
        ...                 
    } while (cursor.moveToNext());
}

Tutto questo mi sembra eccessivamente prolisso, ognuno con più chiamate ai Cursormetodi. Sicuramente ci deve essere un modo più ordinato?


1
Qual era lo scopo di questo? Hai risposto tu stesso entro un minuto dalla pubblicazione ...
— Barak,

10
Ho risposto contemporaneamente a chiederlo.
— Graham Borland,

1
Ah, non avevo mai visto quel link prima. Sembrava sciocco porre una domanda a cui apparentemente avevi già una risposta.
— Barak,

5
@ Barak: Penso che sia fantastico che abbia pubblicato il post - ora conosco un modo leggermente più ordinato di fare qualcosa, che altrimenti non avrei conosciuto.
— George

4
Mi sembra chiaro che tu l'abbia pubblicato per aiutare chiunque possa venire a cercare. Props per questo, e grazie per il suggerimento utile!
— muttley91,

Risposte:


515

Il modo più semplice è questo:

while (cursor.moveToNext()) {
    ...
}

Il cursore inizia prima della prima riga del risultato, quindi sulla prima iterazione questo si sposta sul primo risultato se esiste . Se il cursore è vuoto o l'ultima riga è già stata elaborata, il loop si chiude in modo ordinato.

Naturalmente, non dimenticare di chiudere il cursore una volta terminato, preferibilmente in una finallyclausola.

Cursor cursor = db.rawQuery(...);
try {
    while (cursor.moveToNext()) {
        ...
    }
} finally {
    cursor.close();
}

Se scegli come target API 19+, puoi utilizzare try-with-resources.

try (Cursor cursor = db.rawQuery(...)) {
    while (cursor.moveToNext()) {
        ...
    }
}

19
quindi se volessi fare questa iterazione con un cursore in una posizione abritraria in anticipo, useresti cursore.moveToPosition (-1) prima del ciclo while?
— Sam,

43
non dimenticare di chiuderlo!
— simon

13
Una query del database SQLite non restituirà mai null. Restituirà un cursore vuoto se non viene trovato alcun risultato. Le query ContentProvider possono talvolta restituire null, tuttavia.
— Graham Borland,

8
Solo per aggiungere alcuni centesimi ... Non controllare se il cursore ha dati chiamando moveToFirst () prima di andare a scorrere il cursore - perderai la prima voce
— AAverin

47
Se si utilizza a CursorLoader, assicurarsi di chiamare cursor.moveToPosition(-1)prima dell'iterazione, poiché il caricatore riutilizza il cursore quando cambia l'orientamento dello schermo. Ho trascorso un'ora a rintracciare questo problema!
— Vicky Chijwani,

111

Il modo più bello che ho trovato per passare attraverso un cursore è il seguente:

Cursor cursor;
... //fill the cursor here

for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
    // do what you need with the cursor here
}

Non dimenticare di chiudere il cursore dopo

EDIT: la soluzione data è ottima se hai mai bisogno di iterare un cursore di cui non sei responsabile. Un buon esempio potrebbe essere, se stai prendendo un cursore come argomento in un metodo e devi scansionare il cursore per un dato valore, senza preoccuparti della posizione corrente del cursore.


8
Perché chiamare tre metodi diversi, quando puoi farlo con uno solo? Perché pensi che sia meglio?
— Graham Borland,

11
Questo è il modo più sicuro di procedere se stai ricaricando un cursore preesistente e vuoi essere sicuro che la tua iterazione inizi dall'inizio.
— Michael Eilers Smith,

9
Sono d'accordo che sia più chiaro dell'alternativa più semplice. In genere preferisco la chiarezza alla brevità. Una variazione simile con while loop - android.codota.com/scenarios/51891850da0a87eb5be3cc22/…
— drorw

Avendo giocato un po 'di più con i cursori, ho aggiornato la mia risposta. Il codice dato sicuramente non è il modo più efficiente per iterare un cursore, ma ha i suoi usi. (vedi la modifica)
— Alex Styl,

@AlexStyl Sì, lo fa davvero! Questo mi ha salvato la sanità mentale!
— Alessandro,

45

Vorrei solo sottolineare una terza alternativa che funziona anche se il cursore non si trova nella posizione iniziale:

if (cursor.moveToFirst()) {
    do {
        // do what you need with the cursor here
    } while (cursor.moveToNext());
}

1
C'è un controllo ridondante. Puoi sostituire if + do-while, con un semplice while come indicato nella soluzione accettata, che è anche più semplice / più leggibile.
— mtk

6
@mtk no, non è ridondante, questo è il punto - se il cursore viene riutilizzato, potrebbe essere in una posizione, quindi la necessità di chiamare esplicitamente moveToFirst
— Mike Repass

Questo è utile solo se hai un'istruzione else con registrazione; altrimenti la risposta di Graham Borland è più concisa.
— RDS

5
Come sottolinea il commento di Vicky Chijwani , nell'uso del mondo reale la risposta di Graham Borland non è sicura e richiede moveToPosition(-1). Quindi entrambe le risposte sono ugualmente consise in quanto hanno entrambe due chiamate cursore. Penso che questa risposta sia solo la risposta di Borland in quanto non richiede il -1numero magico.
— Benjamin,

In realtà penso che sia utile nel caso in cui tu voglia sapere che il cursore non aveva valori in quanto è facile e ha senso aggiungere un altro all'istruzione if qui.
— chacham15,

9

Che ne dici di usare foreach loop:

Cursor cursor;
for (Cursor c : CursorUtils.iterate(cursor)) {
    //c.doSth()
}

Tuttavia la mia versione di CursorUtils dovrebbe essere meno brutta, ma chiude automaticamente il cursore:

public class CursorUtils {
public static Iterable<Cursor> iterate(Cursor cursor) {
    return new IterableWithObject<Cursor>(cursor) {
        @Override
        public Iterator<Cursor> iterator() {
            return new IteratorWithObject<Cursor>(t) {
                @Override
                public boolean hasNext() {
                    t.moveToNext();
                    if (t.isAfterLast()) {
                        t.close();
                        return false;
                    }
                    return true;
                }
                @Override
                public Cursor next() {
                    return t;
                }
                @Override
                public void remove() {
                    throw new UnsupportedOperationException("CursorUtils : remove : ");
                }
                @Override
                protected void onCreate() {
                    t.moveToPosition(-1);
                }
            };
        }
    };
}

private static abstract class IteratorWithObject<T> implements Iterator<T> {
    protected T t;
    public IteratorWithObject(T t) {
        this.t = t;
        this.onCreate();
    }
    protected abstract void onCreate();
}

private static abstract class IterableWithObject<T> implements Iterable<T> {
    protected T t;
    public IterableWithObject(T t) {
        this.t = t;
    }
}
}

Questa è una soluzione piuttosto interessante, ma nasconde il fatto che stai usando la stessa Cursoristanza in ogni iterazione del ciclo.
— npace

8

Di seguito potrebbe essere il modo migliore:

if (cursor.moveToFirst()) {
   while (!cursor.isAfterLast()) {
         //your code to implement
         cursor.moveToNext();
    }
}
cursor.close();

Il codice sopra assicurerebbe che avrebbe attraversato l'intera iterazione e non sfuggire alla prima e all'ultima iterazione.


6
import java.util.Iterator;
import android.database.Cursor;

public class IterableCursor implements Iterable<Cursor>, Iterator<Cursor> {
    Cursor cursor;
    int toVisit;
    public IterableCursor(Cursor cursor) {
        this.cursor = cursor;
        toVisit = cursor.getCount();
    }
    public Iterator<Cursor> iterator() {
        cursor.moveToPosition(-1);
        return this;
    }
    public boolean hasNext() {
        return toVisit>0;
    }
    public Cursor next() {
    //  if (!hasNext()) {
    //      throw new NoSuchElementException();
    //  }
        cursor.moveToNext();
        toVisit--;
        return cursor;
    }
    public void remove() {
        throw new UnsupportedOperationException();
    }
}

Codice di esempio:

static void listAllPhones(Context context) {
    Cursor phones = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
    for (Cursor phone : new IterableCursor(phones)) {
        String name = phone.getString(phone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
        String phoneNumber = phone.getString(phone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
        Log.d("name=" + name + " phoneNumber=" + phoneNumber);
    }
    phones.close();
}

+1 per un'implementazione piacevole e compatta. Bugfix: iterator()dovrei anche ricalcolare l' toVisit = cursor.getCount();utilizzo class IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...che riceve una classe in extends CursorWrapper implements MyInterfacecui MyInterface definisce getter per le proprietà del database. In questo modo ho un cursore basato su Iterator <MyInterface>
— k3b

4

La soluzione Do / While è più elegante, ma se usi solo la soluzione While pubblicata sopra, senza moveToPosition (-1) perderai il primo elemento (almeno nella query di contatto).

Suggerisco:

if (cursor.getCount() > 0) {
    cursor.moveToPosition(-1);
    while (cursor.moveToNext()) {
          <do stuff>
    }
}

2
if (cursor.getCount() == 0)
  return;

cursor.moveToFirst();

while (!cursor.isAfterLast())
{
  // do something
  cursor.moveToNext();
}

cursor.close();

2

Il cursore è l'interfaccia che rappresenta una 2-dimensionaltabella di qualsiasi database.

Quando si tenta di recuperare alcuni dati utilizzando l' SELECTistruzione, il database creerà prima un oggetto CURSORE e restituirà il riferimento all'utente.

Il puntatore di questo riferimento restituito punta alla posizione 0 che altrimenti viene chiamata come prima della prima posizione del cursore, quindi quando si desidera recuperare i dati dal cursore, è necessario 1 ° passaggio al 1 ° record, quindi dobbiamo usare moveToFirst

Quando si richiama il moveToFirst()metodo sul cursore, porta il puntatore del cursore sulla prima posizione. Ora puoi accedere ai dati presenti nel 1 ° record

Il modo migliore di guardare:

Cursore cursore

for (cursor.moveToFirst(); 
     !cursor.isAfterLast();  
     cursor.moveToNext()) {
                  .........
     }

0

Inizialmente il cursore non si trova nella prima riga che mostra usando moveToNext()puoi iterare il cursore quando il record non esiste, quindi return false, a meno che return true,

while (cursor.moveToNext()) {
    ...
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.