Apa cara terbaik untuk mengembalikan kursor Android?

291

Saya sering melihat kode yang melibatkan iterasi atas hasil query database, melakukan sesuatu dengan setiap baris, dan kemudian pindah ke baris berikutnya. Contoh khas adalah sebagai berikut.

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

Bagi saya, semua ini sepertinya bertele-tele, masing-masing dengan banyak panggilan ke Cursormetode. Tentunya harus ada cara yang lebih rapi?

Graham Borland
sumber
1
Apa tujuan dari ini? Anda sendiri yang menjawabnya dalam satu menit setelah mempostingnya ...
Barak
10
Saya menjawabnya bersamaan dengan menanyakannya.
Graham Borland
1
Ah, belum pernah melihat tautan itu sebelumnya. Rasanya konyol untuk mengajukan pertanyaan yang tampaknya sudah Anda jawab.
Barak
5
@ Baharak: Saya pikir itu bagus bahwa dia memasang posting - sekarang saya tahu cara yang sedikit lebih rapi dalam melakukan sesuatu, bahwa saya tidak akan tahu sebaliknya.
George
4
Tampak jelas bagi saya bahwa Anda memposting ini untuk membantu siapa saja yang mungkin datang mencari. Alat bantu untuk Anda untuk ini, dan terima kasih atas tip yang bermanfaat!
muttley91

Jawaban:

515

Cara paling sederhana adalah ini:

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

Kursor dimulai sebelum baris hasil pertama, jadi pada iterasi pertama ini bergerak ke hasil pertama jika ada . Jika kursor kosong, atau baris terakhir sudah diproses, maka loop keluar dengan rapi.

Tentu saja, jangan lupa untuk menutup kursor setelah Anda selesai melakukannya, lebih disukai dalam finallyklausa.

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

Jika Anda menargetkan API 19+, Anda dapat menggunakan coba-dengan-sumber daya.

try (Cursor cursor = db.rawQuery(...)) {
    while (cursor.moveToNext()) {
        ...
    }
}
Graham Borland
sumber
19
jadi jika Anda ingin melakukan iterasi ini dengan kursor di posisi yang berlawanan sebelumnya, Anda akan menggunakan cursor.moveToPosition (-1) sebelum loop while?
Sam
43
jangan lupa untuk menutupnya!
simon
13
Kueri basis data SQLite tidak akan pernah mengembalikan nol. Ini akan mengembalikan kursor kosong jika tidak ada hasil yang ditemukan. Kueri ContentProvider terkadang dapat mengembalikan nol.
Graham Borland
8
Hanya untuk menambahkan beberapa sen ... Jangan periksa apakah kursor memiliki data dengan memanggil moveToFirst () sebelum Anda beralih ke kursor - Anda akan kehilangan entri pertama
AAverin
47
Jika menggunakan a CursorLoader, pastikan Anda menelepon cursor.moveToPosition(-1)sebelum iterasi, karena loader menggunakan kembali kursor ketika orientasi layar berubah. Menghabiskan satu jam melacak masalah ini!
Vicky Chijwani
111

Cara berpenampilan terbaik yang saya temukan untuk melalui kursor adalah sebagai berikut:

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

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

Jangan lupa untuk menutup kursor sesudahnya

EDIT: Solusi yang diberikan sangat bagus jika Anda perlu mengulangi kursor yang bukan tanggung jawab Anda. Contoh yang baik adalah, jika Anda mengambil kursor sebagai argumen dalam suatu metode, dan Anda perlu memindai kursor untuk nilai yang diberikan, tanpa harus khawatir tentang posisi kursor saat ini.

Alex Styl
sumber
8
Mengapa memanggil tiga metode berbeda, ketika Anda bisa melakukannya hanya dengan satu? Menurut Anda mengapa itu lebih baik?
Graham Borland
11
Ini adalah cara teraman untuk pergi jika Anda memuat ulang kursor yang sudah ada sebelumnya dan ingin memastikan bahwa iterasi Anda dimulai dari awal.
Michael Eilers Smith
9
Saya setuju itu lebih jelas daripada alternatif yang lebih sederhana. Saya biasanya mendukung kejelasan untuk singkatnya. Variasi serupa dengan while loop - android.codota.com/scenarios/51891850da0a87eb5be3cc22/…
drorw
Setelah bermain lebih banyak dengan kursor, saya telah memperbarui jawaban saya. Kode yang diberikan pasti bukan cara yang paling efisien untuk beralih kursor, tetapi memang memiliki penggunaannya. (lihat hasil edit)
Alex Styl
@ AlexStyl Ya, itu benar-benar! Ini menyelamatkan kewarasan saya!
Alessandro
45

Saya hanya ingin menunjukkan alternatif ketiga yang juga berfungsi jika kursor tidak pada posisi awal:

if (cursor.moveToFirst()) {
    do {
        // do what you need with the cursor here
    } while (cursor.moveToNext());
}
Jörg Eisfeld
sumber
1
Ada cek berlebihan. Anda dapat mengganti if + do-while, dengan simple sementara sebagaimana diberikan dalam solusi yang diterima, yang juga lebih sederhana / lebih mudah dibaca.
mtk
6
@ mtk tidak, itu tidak berlebihan, itu intinya - jika kursor digunakan kembali, itu mungkin ada pada suatu posisi, maka kebutuhan untuk secara eksplisit memanggil moveToFirst
Mike Repass
Ini hanya berguna jika Anda memiliki pernyataan lain dengan pencatatan; kalau tidak , jawaban Graham Borland lebih ringkas.
rds
5
Seperti komentar Vicky Chijwani tunjukkan, dalam penggunaan dunia nyata jawaban Graham Borland tidak aman dan membutuhkan moveToPosition(-1). Jadi kedua jawaban sama-sama ringkas karena keduanya memiliki dua panggilan kursor. Saya pikir jawaban ini hanya mengesampingkan jawaban Borland karena tidak memerlukan angka -1ajaib.
Benjamin
Saya benar-benar berpikir ini berguna jika Anda ingin tahu bahwa kursor tidak memiliki nilai karena mudah dan masuk akal untuk menambahkan yang lain ke pernyataan if di sini.
chacham15
9

Bagaimana dengan menggunakan foreach loop:

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

Namun versi CursorUtils saya harus kurang jelek, tetapi secara otomatis menutup kursor:

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;
    }
}
}
aleksander.w1992
sumber
Ini adalah solusi yang cukup keren, tetapi menyembunyikan fakta bahwa Anda menggunakan Cursorcontoh yang sama di setiap iterasi dari loop.
npace
8

Di bawah ini bisa menjadi cara yang lebih baik:

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

Kode di atas akan memastikan bahwa ia akan melewati seluruh iterasi dan tidak akan lolos dari iterasi pertama dan terakhir.

Pankaj
sumber
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();
    }
}

Kode contoh:

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();
}
18446744073709551615
sumber
+1 untuk implementasi yang bagus dan ringkas. Perbaikan Bug : iterator()juga harus menghitung ulang toVisit = cursor.getCount();saya menggunakan class IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...yang menerima kelas yang extends CursorWrapper implements MyInterfacemana MyInterface mendefinisikan getter untuk properti database. Dengan cara ini saya memiliki Iterator berbasis kursor <MyInterface>
k3b
4

Solusi Do / While lebih elegan, tetapi jika Anda hanya menggunakan solusi While yang diposting di atas, tanpa moveToPosition (-1) Anda akan kehilangan elemen pertama (setidaknya pada permintaan Kontak).

Saya menyarankan:

if (cursor.getCount() > 0) {
    cursor.moveToPosition(-1);
    while (cursor.moveToNext()) {
          <do stuff>
    }
}
Lars
sumber
2
if (cursor.getCount() == 0)
  return;

cursor.moveToFirst();

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

cursor.close();
Changhoon
sumber
2

The kursor adalah Antarmuka yang mewakili 2-dimensionaltabel database apapun.

Ketika Anda mencoba untuk mengambil beberapa data menggunakan SELECTpernyataan, maka database akan membuat objek CURSOR pertama dan mengembalikan referensi kepada Anda.

Pointer dari referensi yang dikembalikan ini menunjuk ke lokasi ke - 0 yang sebelumnya disebut sebagai sebelum lokasi pertama dari Kursor, jadi ketika Anda ingin mengambil data dari kursor, Anda harus memindahkan ke-1 ke catatan ke-1 sehingga kita harus menggunakan moveToFirst

Saat Anda memanggil moveToFirst()metode pada kursor, dibutuhkan kursor ke lokasi pertama. Sekarang Anda dapat mengakses data yang ada di catatan 1

Cara terbaik untuk melihat:

Kursor kursor

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

Awalnya kursor tidak berada pada tampilan baris pertama menggunakan moveToNext()Anda dapat mengulangi kursor ketika catatan tidak ada maka return false, kecuali return true,

while (cursor.moveToNext()) {
    ...
}
kundan kamal
sumber