В моей последней статье я создал Android- приложение Todo , которое является более продвинутой версией классического приложения Hello World для разработки мобильных приложений. В этой статье я представил создание представлений для Android, присвоение методов (функций) таким событиям, как нажатие кнопок, и использование встроенной базы данных SQLite для хранения, извлечения и удаления данных (задач).
В этом уроке мне нужно было написать SQL-запросы для достижения этой цели, но есть более простой способ, контент-провайдеры .
Предоставление контента для Android
Поставщики контента — это более простой способ управления данными, хранящимися во встроенной базе данных SQLite. Это стандартный интерфейс, который связывает данные в одном процессе с кодом, выполняющимся в другом процессе. Может показаться, что их трудно понять или реализовать, но это не так. В этом уроке я покажу вам, как создать свой собственный контент-провайдер.
Вам не нужно разрабатывать собственного провайдера, если вы не собираетесь делиться своими данными с другими приложениями. Тем не менее, вам нужен собственный провайдер для предоставления пользовательских поисковых запросов в вашем собственном приложении. Вам также нужен собственный поставщик, если вы хотите копировать и вставлять сложные данные или файлы из вашего приложения в другие приложения.
Чтобы начать следовать руководству, загрузите текущее хранилище проекта с GitHub и импортируйте его в свою среду IDE.
Перед написанием класса провайдера контента мы должны добавить некоторый код в класс TaskContract
. Добавьте код под объявлениями существующих переменных в классе:
public static final String AUTHORITY = "com.example.TodoList.mytasks"; public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+ TABLE); public static final int TASKS_LIST = 1; public static final int TASKS_ITEM = 2; public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/example.tasksDB/"+TABLE; public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/example/tasksDB" + TABLE;
Код — это не что иное, как список static
констант ( final
переменных), содержащих необходимую информацию. Во-первых, я выбрал полномочия для поставщика контента. Обычно это было бы .provider
После content://com.example.TodoList.mytasks/tasks
URI контента устанавливается значение content://com.example.TodoList.mytasks/tasks
( tasks
— TABLE
). Этот URI контента всегда должен начинаться с content://
и используется для доступа к данным в таблице. Если вы будете использовать более одной таблицы, вы можете использовать ту же структуру, что и выше, хотя вам придется изменить имя таблицы.
CONTENT_TYPE
и CONTENT_TYPE_ITEM
используются для определения, указывает ли запрошенный URI на каталог ( т. Е. На таблицу) или на элемент ( т. Е. На запись в таблице).
Нам также нужно добавить поставщика контента в файл AndroidManifest.xml, добавив следующий код:
<provider android:authorities="com.example.TodoList.mytasks" android:name=".db.TaskProvider" />
Прямо перед закрытием тега application
.
Теперь, когда контракт готов, мы можем создать контент-провайдера.
Создание провайдера
Поставщик контента — это простой класс Java, который расширяет класс ContentProvider
и реализует его методы.
Наш контент-провайдер будет называться TaskProvider
и будет помещен в пакет db
. Для этого создайте новый файл класса в папке db
именем TaskProvider.java
и добавьте следующий код:
package com.example.TodoList.db; import android.content.ContentProvider; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; public class TaskProvider extends ContentProvider{ private SQLiteDatabase db; private TaskDBHelper taskDBHelper; public static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { uriMatcher.addURI(TaskContract.AUTHORITY,TaskContract.TABLE,TaskContract.TASKS_LIST); uriMatcher.addURI(TaskContract.AUTHORITY,TaskContract.TABLE+"/#",TaskContract.TASKS_ITEM); } @Override public boolean onCreate() { return false; } @Override public Cursor query(Uri uri, String[] strings, String s, String[] strings2, String s2) { return null; } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues contentValues) { return null; } @Override public int delete(Uri uri, String s, String[] strings) { return 0; } @Override public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { return 0; } }
В этот момент этот класс мало что делает. Это поставщик контента, но он не предоставляет никакого контента, так как методы еще не реализованы. Все, что он делает — это UriMatcher
экземпляр UriMatcher
который затем будет использоваться для проверки, является ли доступный URI действительным или нет. В статическом блоке к UriMatcher
добавляются два URI: URI, соответствующий таблице, и URI, соответствующий записи.
Давайте реализуем метод onCreate()
. Система Android вызывает этот метод сразу после создания провайдера. Это означает, что это должно быть максимально простым, поскольку мы не хотим, чтобы система создавала слишком много вычислений при создании провайдера. Поместите этот код в метод onCreate()
в классе, который мы только что создали:
@Override public boolean onCreate() { boolean ret = true; taskDBHelper = new TaskDBHelper(getContext()); db = taskDBHelper.getWritableDatabase(); if (db == null) { ret = false; } if (db.isReadOnly()) { db.close(); db = null; ret = false; } return ret; }
В этом методе класс TaskDBHelper
используется для создания вспомогательного объекта, который создает базу данных, если она еще не существует. Этот метод возвращает false, если поставщик не будет загружен, поскольку база данных недоступна, в противном случае будет возвращено значение true. Этого простого кода более чем достаточно для создания нашего провайдера контента.
Теперь давайте добавим функцию query()
, которая используется для извлечения данных, хранящихся в базе данных, и возврата экземпляра Cursor
:
@Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(TaskContract.TABLE); switch (uriMatcher.match(uri)) { case TaskContract.TASKS_LIST: break; case TaskContract.TASKS_ITEM: qb.appendWhere(TaskContract.Columns._ID + " = "+ uri.getLastPathSegment()); break; default: throw new IllegalArgumentException("Invalid URI: " + uri); } Cursor cursor = qb.query(db,projection,selection,selectionArgs,null,null,null); return cursor; }
Метод query()
должен возвращать экземпляр Cursor
или генерировать исключение, если есть какие-либо проблемы с запросом.
В приведенном выше SQLiteQueryBuilder
для создания запроса используется экземпляр SQLiteQueryBuilder
. Если запрашиваемый объект (по URI) является записью (элементом, а не списком / таблицей), SQLiteQueryBuilder
использует метод appendWhere()
для добавления в запрос предложения WHERE
. В конце создается экземпляр Cursor
путем выполнения запроса, созданного построителем запросов, который затем возвращается методом. Если запрошенный URI недействителен, создается IllegalArgumentException
.
После добавления метода query()
мы собираемся реализовать метод insert()
, который добавляет некоторые значения, хранящиеся в экземпляре ContentValues
в качестве новой записи в таблице:
@Override public Uri insert(Uri uri, ContentValues contentValues) { if (uriMatcher.match(uri) != TaskContract.TASKS_LIST) { throw new IllegalArgumentException("Invalid URI: "+uri); } long id = db.insert(TaskContract.TABLE,null,contentValues); if (id>0) { return ContentUris.withAppendedId(uri,id); } throw new SQLException("Error inserting into table: "+TaskContract.TABLE); }
Этот метод получает два аргумента: URI, в который будут вставляться записи, и значения, которые будут составлять запись. Если URI не совпадает с URI таблицы, IllegalArgumentException
. Если URI правильный, то метод insert()
экземпляра SQLiteDatabase
используется для вставки данных в соответствующую таблицу. Этот метод insert()
возвращает URI новой записи. Это делается, если id
больше 0 (что означает, что запись добавляется в таблицу), и новый URI создается с помощью withAppendedId()
. Если id
не больше 0, это означает, что запись не сохраняется, поэтому выбрасывается исключение SQLException
.
Теперь давайте перейдем к другому методу, методу update()
. Этот метод используется для изменения ( то есть обновления) существующей записи данной таблицы в базе данных SQLite:
@Override public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { int updated = 0; switch (uriMatcher.match(uri)) { case TaskContract.TASKS_LIST: db.update(TaskContract.TABLE,contentValues,s,strings); break; case TaskContract.TASKS_ITEM: String where = TaskContract.Columns._ID + " = " + uri.getLastPathSegment(); if (!s.isEmpty()) { where += " AND "+s; } updated = db.update(TaskContract.TABLE,contentValues,where,strings); break; default: throw new IllegalArgumentException("Invalid URI: "+uri); } return updated; }
Этот метод принимает четыре параметра:
-
Uri uri
: URI для запроса. Это может быть URI отдельной записи или URI таблицы. -
ContentValues contentValues
: набор пар ключ-значение с именем столбца в качестве ключа и значениями для обновления в качестве значений. -
String s
: выбор, соответствующий строкам, которые будут обновлены. -
String[] strings
: аргументы вышеуказанных строк.
Используя этот метод, мы сначала проверяем, соответствует ли URI таблице или записи. Если он соответствует таблице, мы вызываем метод update()
для экземпляра SQLiteDatabase
, передавая те же параметры, что и наш метод (за исключением URI, который изменяется на имя таблицы).
Если URI соответствует записи, мы должны изменить третий параметр, чтобы он соответствовал идентификатору, необходимому для идентификатора записи в базе данных, и добавить любой выбор, который потребовался пользователю.
Наконец, запись update()
вызывается для экземпляра SQLiteDatabase
, передавая имя таблицы, значения содержимого, наше предложение where и последний аргумент.
Этот метод возвращает число обновленных строк или IllegalArgumentException
если переданный URI IllegalArgumentException
.
Следующий метод — delete()
. Вы заметите, что этот метод ничем не отличается от метода update()
реализованного выше:
@Override public int delete(Uri uri, String selection, String[] selectionArgs) { int deleted = 0; switch (uriMatcher.match(uri)) { case TaskContract.TASKS_LIST: db.delete(TaskContract.TABLE,selection,selectionArgs); break; case TaskContract.TASKS_ITEM: String where = TaskContract.Columns._ID + " = " + uri.getLastPathSegment(); if (!selection.isEmpty()) { where += " AND "+selection; } deleted = db.delete(TaskContract.TABLE,where,selectionArgs); break; default: throw new IllegalArgumentException("Invalid URI: "+uri); } return deleted; }
Как и метод update()
метод delete()
проверяет, соответствует ли переданный URI таблице и, если это так, удаляет таблицу. Если он соответствует записи, он удаляет эту запись из таблицы. В последнем случае мы обязательно добавим предложение для идентификатора в параметр selection
.
Как и выше, метод delete()
возвращает количество удаленных строк или IllegalArgumentException
исключение IllegalArgumentException
если URI недопустим.
Чтобы завершить наш класс TaskProvider
, мы должны реализовать последний метод, метод getType()
. Этот метод сообщает, соответствует ли переданный URI таблице, записи или является недействительным. Этот метод просто:
@Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { case TaskContract.TASKS_LIST: return TaskContract.CONTENT_TYPE; case TaskContract.TASKS_ITEM: return TaskContract.CONTENT_ITEM_TYPE; default: throw new IllegalArgumentException("Invalid URI: "+uri); } }
Используйте свой собственный провайдер
Чтобы использовать наш новый контент-провайдер, нам нужно открыть MainActivity.java
и изменить часть его кода.
Перейдите к updateUI()
и мы изменим метод запроса данных из базы данных. Удалить этот кусок кода:
helper = new TaskDBHelper(MainActivity.this); SQLiteDatabase sqlDB = helper.getReadableDatabase(); Cursor cursor = sqlDB.query(TaskContract.TABLE, new String[]{TaskContract.Columns._ID, TaskContract.Columns.TASK}, null, null, null, null, null);
И заменить его на:
Uri uri = TaskContract.CONTENT_URI; Cursor cursor = this.getContentResolver().query(uri,null,null,null,null);
Для удаления задач удалите следующий код из onDoneButtonClick()
:
String sql = String.format("DELETE FROM %s WHERE %s = '%s'", TaskContract.TABLE, TaskContract.Columns.TASK, task); helper = new TaskDBHelper(MainActivity.this); SQLiteDatabase sqlDB = helper.getWritableDatabase(); sqlDB.execSQL(sql);
И заменить это на это:
Uri uri = TaskContract.CONTENT_URI; this.getContentResolver().delete(uri, TaskContract.Columns.TASK + "=?", new String[]{task});
Теперь вы можете удалять задачи, используя созданный контент-провайдер, без каких-либо SQL-запросов.
Наконец, нам нужно вставить новые задачи, используя метод insert()
который мы создали. Найдите метод onOptionsItemSelected()
и замените следующий код:
helper = new TaskDBHelper(MainActivity.this); SQLiteDatabase db = helper.getWritableDatabase(); ContentValues values = new ContentValues(); values.clear(); values.put(TaskContract.Columns.TASK,task); db.insertWithOnConflict(TaskContract.TABLE,null,values,SQLiteDatabase.CONFLICT_IGNORE);
С:
helper = new TaskDBHelper(MainActivity.this); SQLiteDatabase db = helper.getWritableDatabase(); ContentValues values = new ContentValues(); values.clear(); values.put(TaskContract.Columns.TASK,task); Uri uri = TaskContract.CONTENT_URI; getApplicationContext().getContentResolver().insert(uri,values);
Теперь значения будут добавлены в базу данных с помощью метода insert()
который мы создали ранее.
Вывод
Как видите, контент-провайдеры несложные, особенно после того, как они попробовали их в нескольких проектах.
Я надеюсь, что это руководство дало вам хорошее представление о поставщиках контента Android, их реализации и использовании. Вы можете найти исходный код этого руководства на Github .