Продолжим рабоу с проектом из прошлой темы и добавим в него класс AppProvider, который собственно и будет представлять провайдер контента:
package com.example.friendsproviderapp;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class AppProvider extends ContentProvider {
private AppDatabase mOpenHelper;
private static final UriMatcher sUriMatcher = buildUriMatcher();
public static final int FRIENDS = 100;
public static final int FRIENDS_ID = 101;
private static UriMatcher buildUriMatcher(){
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
// content://com.example.friendsprovider/FRIENDS
matcher.addURI(FriendsContract.CONTENT_AUTHORITY, FriendsContract.TABLE_NAME, FRIENDS);
// content://com.example.friendsprovider/FRIENDS/8
matcher.addURI(FriendsContract.CONTENT_AUTHORITY, FriendsContract.TABLE_NAME + "/#", FRIENDS_ID);
return matcher;
}
@Override
public boolean onCreate() {
mOpenHelper = AppDatabase.getInstance(getContext());
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
final int match = sUriMatcher.match(uri);
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
switch(match){
case FRIENDS:
queryBuilder.setTables(FriendsContract.TABLE_NAME);
break;
case FRIENDS_ID:
queryBuilder.setTables(FriendsContract.TABLE_NAME);
long taskId = FriendsContract.getFriendId(uri);
queryBuilder.appendWhere(FriendsContract.Columns._ID + " = " + taskId);
break;
default:
throw new IllegalArgumentException("Unknown URI: "+ uri);
}
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
return queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
final int match = sUriMatcher.match(uri);
switch(match){
case FRIENDS:
return FriendsContract.CONTENT_TYPE;
case FRIENDS_ID:
return FriendsContract.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI: "+ uri);
}
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
final int match = sUriMatcher.match(uri);
final SQLiteDatabase db;
Uri returnUri;
long recordId;
if (match == FRIENDS) {
db = mOpenHelper.getWritableDatabase();
recordId = db.insert(FriendsContract.TABLE_NAME, null, values);
if (recordId > 0) {
returnUri = FriendsContract.buildFriendUri(recordId);
} else {
throw new SQLException("Failed to insert: " + uri.toString());
}
} else {
throw new IllegalArgumentException("Unknown URI: " + uri);
}
return returnUri;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
final int match = sUriMatcher.match(uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selectionCriteria = selection;
if(match != FRIENDS && match != FRIENDS_ID)
throw new IllegalArgumentException("Unknown URI: "+ uri);
if(match==FRIENDS_ID) {
long taskId = FriendsContract.getFriendId(uri);
selectionCriteria = FriendsContract.Columns._ID + " = " + taskId;
if ((selection != null) && (selection.length() > 0)) {
selectionCriteria += " AND (" + selection + ")";
}
}
return db.delete(FriendsContract.TABLE_NAME, selectionCriteria, selectionArgs);
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
final int match = sUriMatcher.match(uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selectionCriteria = selection;
if(match != FRIENDS && match != FRIENDS_ID)
throw new IllegalArgumentException("Unknown URI: "+ uri);
if(match==FRIENDS_ID) {
long taskId = FriendsContract.getFriendId(uri);
selectionCriteria = FriendsContract.Columns._ID + " = " + taskId;
if ((selection != null) && (selection.length() > 0)) {
selectionCriteria += " AND (" + selection + ")";
}
}
return db.update(FriendsContract.TABLE_NAME, values, selectionCriteria, selectionArgs);
}
}
В итоге получится следующий проект:
Класс провайдера контента должен наследоваться от абстрактного класса ContentProvider, который определяет ряд методов для работы с данными, в частности, методы oncreate, query, insert, update, delete, getType.
Для построения путей uri для запросов к источнику данных определен объект sUriMatcher, который представляет тип UriMatcher.
Для его создания применяется метод buildUriMatcher:
private static UriMatcher buildUriMatcher(){
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
// content://com.example.friendsprovider/FRIENDS
matcher.addURI(FriendsContract.CONTENT_AUTHORITY, FriendsContract.TABLE_NAME, FRIENDS);
// content://com.example.friendsprovider/FRIENDS/8
matcher.addURI(FriendsContract.CONTENT_AUTHORITY, FriendsContract.TABLE_NAME + "/#", FRIENDS_ID);
return matcher;
}
С помощью метода addURI в объект UriMatcher добавляется определенный путь uri, используемый для отправки запроса. В качестве первого параметра addUri принимает название провайдера, который описывается константой CONTENT_AUTHORITY. Второй параметр - путь к данным в рамках
источника данных - в данном случае это таблица friends. Третий параметр - числовой код, который позволяет разграничить характер операции.
В данном случае у нас возможны два типа запросов - для обращения ко всей таблице, либо для обращения к отдельному объекту, вне зависимости идет ли речь о добавлении, получении, обновлении или удалении данных.
Поэтому добавлюятся два uri. И для каждого используется один из двух числовых кодов - FRIENDS или FRIENDS_ID. Это могут быть абсолютно любые числовые коды.
Но они позволят затем узнать, идет запрос ко всей таблице в целом или к какому-то одному определенному объекту.
Метод oncreate() выполняет начальную инициализацию провайдера при его создании. В данном случае просто устанавливается используемая база данных:
public boolean onCreate() {
mOpenHelper = AppDatabase.getInstance(getContext());
return true;
}
Для получения данных в провайдере определен метод query().
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
final int match = sUriMatcher.match(uri);
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
switch(match){
case FRIENDS:
queryBuilder.setTables(FriendsContract.TABLE_NAME);
break;
case FRIENDS_ID:
queryBuilder.setTables(FriendsContract.TABLE_NAME);
long taskId = FriendsContract.getFriendId(uri);
queryBuilder.appendWhere(FriendsContract.Columns._ID + " = " + taskId);
break;
default:
throw new IllegalArgumentException("Unknown URI: "+ uri);
}
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
return queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
}
Данный метод должен принимать пять параметров:
uri: путь запроса
projection: набор столбцов, данные для которых надо получить
selection: выражение для выборки типа "WHERE Name = ? ...."
selectionArgs: набор значений для параметров из selection (вставляются вместо знаков вопроса)
sortOrder: критерий сортировки, в качестве которого выступает имя столбца
С помощью объекта SQLiteQueryBuilder создаем запрос sql, который будет выполняться. Для этого вначале получаем числовой код операции с помощью выражения sUriMatcher.match(uri). То есть здесь мы узнаем,
обращен запрос ко всей таблице (код FRIENDS) или к одному объекту (код FRIENDS_ID). Если запрос обращен ко всей таблице, то вызываем метод queryBuilder.setTables(FriendsContract.TABLE_NAME).
Если запрос идет к одному объекту, то в этом случае получаем собственно идентификатор объекта и с помощью метода appendWhere() добавляем условие для выборки
по данному идентификатору.
В конце собственно выполняем запрос с помощью метода queryBuilder.query() и возвращаем объект Cursor.
Далее мы рассмотрим использование этого метода и возвращаемого им курсора.
Для добавления данных применяется метод insert():
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
final int match = sUriMatcher.match(uri);
final SQLiteDatabase db;
Uri returnUri;
long recordId;
if (match == FRIENDS) {
db = mOpenHelper.getWritableDatabase();
recordId = db.insert(FriendsContract.TABLE_NAME, null, values);
if (recordId > 0) {
returnUri = FriendsContract.buildFriendUri(recordId);
} else {
throw new SQLException("Failed to insert: " + uri.toString());
}
} else {
throw new IllegalArgumentException("Unknown URI: " + uri);
}
return returnUri;
}
Метод принимает два параметра:
uri: путь запроса
values: объект ContentValues, через который передаются добавляемые данные
Для выполнения добавления выполняется метод db.insert, который возвращает идентификатор добавленного объекта:
recordId = db.insert(TasksContract.TABLE_NAME, null, values);
С помощью этого идентификатора создается и возвращается путь Uri к созданному объекту.
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
final int match = sUriMatcher.match(uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selectionCriteria = selection;
if(match != FRIENDS && match != FRIENDS_ID)
throw new IllegalArgumentException("Unknown URI: "+ uri);
if(match==FRIENDS_ID) {
long taskId = FriendsContract.getFriendId(uri);
selectionCriteria = FriendsContract.Columns._ID + " = " + taskId;
if ((selection != null) && (selection.length() > 0)) {
selectionCriteria += " AND (" + selection + ")";
}
}
return db.delete(FriendsContract.TABLE_NAME, selectionCriteria, selectionArgs);
}
Данный метод должен принимать три параметра:
uri: путь запроса
selection: выражение для выборки типа "WHERE Name = ? ...."
selectionArgs: набор значений для параметров из selection (вставляются вместо знаков вопроса)
При удалении мы можем реализовать один из двух сценариев: либо удалить из таблицы набор данных (например, друзей, у которых имя Том), либо удалить один объект по определенному идентифкатору. В случае если идет удаление по идентификатору, то к выражению выборки удаляемых данных в selection добавляется условие удаления по id:
long taskId = FriendsContract.getFriendId(uri);
selectionCriteria = FriendsContract.Columns._ID + " = " + taskId;
if((selection != null) && (selection.length() > 0)){
selectionCriteria += " AND (" + selection + ")";
}
count = db.delete(FriendsContract.TABLE_NAME, selectionCriteria, selectionArgs);
Результатом удаления является количество удаленных строк в таблице.
Для обновления данных применяется метод update():
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
final int match = sUriMatcher.match(uri);
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selectionCriteria = selection;
if(match != FRIENDS && match != FRIENDS_ID)
throw new IllegalArgumentException("Unknown URI: "+ uri);
if(match==FRIENDS_ID) {
long taskId = FriendsContract.getFriendId(uri);
selectionCriteria = FriendsContract.Columns._ID + " = " + taskId;
if ((selection != null) && (selection.length() > 0)) {
selectionCriteria += " AND (" + selection + ")";
}
}
return db.update(FriendsContract.TABLE_NAME, values, selectionCriteria, selectionArgs);
}
Данный метод должен принимать четыре параметра:
uri: путь запроса
values: объект ContentValues, который определяет новые значения
selection: выражение для выборки типа "WHERE Name = ? ...."
selectionArgs: набор значений для параметров из selection (вставляются вместо знаков вопроса)
Метод update во многом аналогичен методу delete за тем исключением, что в метод передаются данные типа ContentValues, которые передаются в метод db.update().
Но чтобы провайдер контента заработал, необходимо внести изменения в файл AndroidManifest.xml. К примеру, по умолчанию данный файл выглядит примерно следующим образом:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.friendsproviderapp">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.FriendsProviderApp">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
И в конец элемента <application> добавим определение провайдера:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.friendsproviderapp">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.FriendsProviderApp">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:authorities="com.example.friendsprovider"
android:name="com.example.friendsproviderapp.AppProvider"
android:exported="false"/>
</application>
</manifest>
В элементе provider атрибут android:authorities указывает на название провайдера - в данном случае это название, которое определено в прошлой теме в константе
CONTENT_AUTHORITY в классе FriendsContract, то есть com.example.friendsprovider. А атрибут android:name указывает на полное название класса провайдера с учетом его пакета. В моем
случае пакет com.example.friendsproviderapp, а класс провайдера - AppProvider, поэтому в итоге получается com.example.friendsproviderapp.AppProvider.