Освой программирование играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Изучим объект Cursor. Не путайте его с курсором мыши, который бегает у вас на экране.
Класс Cursor содержит немало возможностей для навигации (но не ограничивается только ими):
Также Android предоставляет следующие методы:
И другие методы, о которых можно узнать в документации или из примеров.
Курсор обязательно следует закрывать методом close() для освобождения памяти.
Чтобы было проще понять, что такое курсоры, представляйте их в виде таблицы. Пусть у нас есть таблица из столбцов: _id (идентификатор) и catname (имя котов). Допустим, мы ввели в базу имена четырех котов и таблица базы данных выглядит таким образом:
_id | catname |
---|---|
1 | Мурзик |
2 | Васька |
3 | Барсик |
4 | Рыжик |
Как было сказано выше, при работе с курсорами необходимо вызвать метод moveToFirst() (перейти к первой строке), после чего таблица будет выглядеть следующим образом:
_id | catname |
---|---|
1 | Мурзик |
2 | Васька |
3 | Барсик |
4 | Рыжик |
Как видите, после вызова метода первая строчка таблицы подсвечена. Именно данные этой строки и содержит сейчас курсор. Можно проверить следующим образом. Добавим новую кнопку в проект и напишем код:
public void onCursorClick(View v) {
String query = "SELECT " + CatsDataBase._ID + ", "
+ CatsDataBase.CATNAME + " FROM " + CatsDataBase.TABLE_NAME;
Cursor catCursor = sqdb.rawQuery(query, null);
catCursor.moveToFirst(); // переходим на первую строку
// извлекаем данные из курсора
int item_id = catCursor
.getInt(catCursor.getColumnIndex(CatsDataBase._ID));
String item_content = catCursor.getString(catCursor
.getColumnIndex(CatsDataBase.CATNAME));
catCursor.close();
txtData.setText("id: " + item_id + " Имя кота: " + item_content);
}
На первой строке содержатся данные 1, Мурзик. Мы не знаем, как хранятся данные в курсоре, но нам это и не нужно. С помощью метода getColumnIndex() с указанием имени колонки мы можем извлечь данные, которые хранятся в них.
Теперь вызовем метод moveToNext() (перейти к следующей строке). Таблица будет выглядеть уже так:
_id | catname |
---|---|
1 | Мурзик |
2 | Васька |
3 | Барсик |
4 | Рыжик |
Код для проверки:
public void onCursorClick(View v) {
String query = "SELECT " + CatsDataBase._ID + ", "
+ CatsDataBase.CATNAME + " FROM " + CatsDataBase.TABLE_NAME;
Cursor catCursor = sqdb.rawQuery(query, null);
catCursor.moveToFirst();
catCursor.moveToNext(); // переходим к следующей записи
int item_id = catCursor
.getInt(catCursor.getColumnIndex(CatsDataBase._ID));
String item_content = catCursor.getString(catCursor
.getColumnIndex(CatsDataBase.CATNAME));
catCursor.close();
txtData.setText("id: " + item_id + " Имя кота: " + item_content);
}
Если вызвать метод moveToNext() ещё раз, то переместимся на третью позицию. А теперь представьте ситуацию, что у нас в базе более ста котов, и чтобы узнать имя 85-го кота, нам придётся 85 раз вызывать метод. Не удобно. К счастью, есть метод moveToPosition() (перейти в позицию), в котором сразу можно указать нужную строку (отсчет идет от 0):
...
catCursor.moveToFirst();
catCursor.moveToPosition(2); // прыгаем на третью запись
...
А таблица выглядит уже так:
_id | catname |
---|---|
1 | Мурзик |
2 | Васька |
3 | Барсик |
4 | Рыжик |
Надеюсь, вы поняли общий принцип работы с курсором. Теперь вы можете понять, как выглядит курсор после вызова метода moveToLast() (перейти на последнюю запись).
...
catCursor.moveToFirst();
catCursor.moveToLast();
...
Если нам надо получить имена всех котов из таблицы базы данных, то нужно последовательно вызывать методы moveToNext(). Это проще сделать через цикл. Условием для остановки цикла является проверка возвращаемого значения метода. Если вернётся значение false, значит мы дошли до конца таблицы. В данном случае не нужно вызывать метод moveToFirst(), чтобы не пропустить первую запись:
public void onCursorClick(View v) {
String query = "SELECT " + CatsDataBase._ID + ", "
+ CatsDataBase.CATNAME + " FROM " + CatsDataBase.TABLE_NAME;
Cursor catCursor = sqdb.rawQuery(query, null);
//catCursor.moveToFirst();
String catName;
while (catCursor.moveToNext()) {
catName = catCursor.getString(catCursor
.getColumnIndex(CatsDataBase.CATNAME));
txtData.append(catName + " ");
}
catCursor.close();
}
Цикл можно переписать по другому. Метод isAfterLast() возвращает true, когда курсор с последней записи пытается переместиться в никуда. А пока курсор возвращает false, можно двигать его на следующую позицию. Пример будет выглядеть так:
public void onCursorClick(View v) {
String query = "SELECT " + CatsDataBase._ID + ", "
+ CatsDataBase.CATNAME + " FROM " + CatsDataBase.TABLE_NAME;
Cursor catCursor = sqdb.rawQuery(query, null);
catCursor.moveToFirst();
String catName;
while (catCursor.isAfterLast() == false) {
catName = catCursor.getString(catCursor
.getColumnIndex(CatsDataBase.CATNAME));
txtData.append(catName + " ");
catCursor.moveToNext();
}
catCursor.close();
}
В примерах мы извлекали строковое значение записи через метод getString():
// первый столбец
String name = cursor.getString(0)
По аналогии можно получить числовое значение, например, номер ресурса изображения.
// третий столбец с типом int
int imageResource = cursor.getInt(2);
Думаю, приведённых примеров достаточно, чтобы понять с чем едят курсоры. Они совсем не страшные.
Начиная с Android 3.0, многие методы для работы с курсором считаются устаревшими.
При использовании устаревших методов вы можете получить исключение типа:
java.lang.IllegalStateException: trying to requery an already closed cursor
Кроме того, студия будет подчёркивать устаревшие методы, от которых желательно избавляться в новых проектах.
Наиболее распространён метод managedQuery(), в сети постоянно натыкаюсь на примеры с использованием данного метода.
Обычно, код выглядит следующим образом:
cursor = context.managedQuery(android.provider.Browser.BOOKMARKS_URI, projection, null, null, null);
Данный код следует переработать следующим образом:
CursorLoader cursorLoader = new CursorLoader(context, android.provider.Browser.BOOKMARKS_URI, projection, null, null, null);
cursor = cursorLoader.loadInBackground();
Метод reQuery() следует заменить на вызов LoaderManager.
Класс CursorLoader и связанный с ним LoaderManager гарантируют, что запросы будут выполняться асинхронно.
Мне пока не приходилось использовать данный приём в своей практике, поэтому просто скопирую из других источников:
Иногда попадаются примеры с использованием класса MatrixCursor. Сам пока не изучал, оставлю вам в качестве домашнего задания. Небольшой пример на память:
Cursor cursor = getContentResolver().query( Data.CONTENT_URI,
new String[] { Data._ID, Data.RAW_CONTACT_ID }, null, null, null);
MatrixCursor result = new MatrixCursor(new String[] { Data._ID, Data.RAW_CONTACT_ID });
Set seen = new HashSet();
while (cursor.moveToNext())
{
long raw = cursor.getLong(1);
if (!seen.contains(raw))
{
seen.add(raw);
result.addRow(new Object[] {cursor.getLong(0), raw});
}
}
CursorLoader для работы с Галереей
Журнал о входящих, исходящих и пропущенных звонках