Продолжим рабоу с проектом из прошлой темы и добавим в него класс 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.