Создание провайдера контента. Часть 2

Последнее обновление: 18.10.2021

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

В итоге получится следующий проект:

Создание Content Provider в Android и Java

Класс провайдера контента должен наследоваться от абстрактного класса 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

Но чтобы провайдер контента заработал, необходимо внести изменения в файл 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.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850