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.