Освой программирование играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Cursor

Изучим объект Cursor. Не путайте его с курсором мыши, который бегает у вас на экране.

Cursor

Работа с курсором

  • Курсор - это набор строк в табличном виде
  • Для доступа курсора вы должны использовать метод moveToFirst(), так как курсор размещается перед первой строкой
  • Вы должны знать названия столбцов
  • Вы должны знать типы столбцов
  • Все методы доступа к массивам основываются на номере столбца, поэтому сначала нужно преобразовать название столбца в номер столбца
  • Курсор является случайным (random cursor) - вы можете переходить вперед, назад и со строки на строку
  • Поскольку курсор является случайным, у него можно запрашивать количество строк (row count)

Класс Cursor содержит немало возможностей для навигации (но не ограничивается только ими):

  • moveToFirst() — перемещает курсор на первую строку в результате запроса;
  • moveToNext() — перемещает курсор на следующую строку;
  • moveToLast() - перемещает курсор на последнюю строку;
  • moveToPrevious() — перемещает курсор на предыдущую строку;
  • getCount() — возвращает количество строк в результирующем наборе данных;
  • getColumnIndexOrThrow() — возвращает индекс для столбца с указанным именем (выбрасывает исключение, если столбец с таким именем не существует);
  • getColumnName() — возвращает имя столбца с указанным индексом;
  • getColumnNames() — возвращает массив строк, содержащий имена всех столбцов в объекте Cursor;
  • moveToPosition() — перемещает курсор на указанную строку;
  • getPosition() — возвращает текущую позицию курсора

Также Android предоставляет следующие методы:

  • isBeforeFirst()
  • isAfterLast() - полезный метод, сигнализирующий о достижении конца запроса. Используется в циклах
  • isClosed()

И другие методы, о которых можно узнать в документации или из примеров.

Курсор обязательно следует закрывать методом close() для освобождения памяти.

Наглядно о курсорах

Чтобы было проще понять, что такое курсоры, представляйте их в виде таблицы. Пусть у нас есть таблица из столбцов: _id (идентификатор) и catname (имя котов). Допустим, мы ввели в базу имена четырех котов и таблица базы данных выглядит таким образом:

_idcatname
1Мурзик
2Васька
3Барсик
4Рыжик

Как было сказано выше, при работе с курсорами необходимо вызвать метод moveToFirst() (перейти к первой строке), после чего таблица будет выглядеть следующим образом:

_idcatname
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() (перейти к следующей строке). Таблица будет выглядеть уже так:

_idcatname
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); // прыгаем на третью запись
...

А таблица выглядит уже так:

_idcatname
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);

Думаю, приведённых примеров достаточно, чтобы понять с чем едят курсоры. Они совсем не страшные.

Устаревшие методы (deprecated)

Начиная с Android 3.0, многие методы для работы с курсором считаются устаревшими.

  • startManagingCursor()
  • stopManagingCursor()
  • managedQuery()
  • reQuery()

При использовании устаревших методов вы можете получить исключение типа:

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 гарантируют, что запросы будут выполняться асинхронно.

Мне пока не приходилось использовать данный приём в своей практике, поэтому просто скопирую из других источников:

  • реализуйте интерфейс в вашем классе как LoaderManager.LoaderCallbacks
  • в методе onCreate() инициализируйте loader как First implement the interface in your class as getLoaderManager().initLoader(0, null, this);
  • вместо reQuery используйте getLoaderManager().restartLoader(0, null, this);
  • переопределите три метода onCreateLoader(), onLoadFinished(), onLoaderReset()

MatrixCursor

Иногда попадаются примеры с использованием класса 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 для работы с Галереей

MediaStore

Журнал о входящих, исходящих и пропущенных звонках

Реклама