Препарирование ContentProvider
Теги: Java, Android, ContentProvider
What?
ContentProvider – это класс, предоставляющий унифицированный интерфейс для доступа к данным приложения. Этот класс позволяет вам использовать единый источник данных в вашем приложении.
Sources:
- https://www.youtube.com/watch?v=3HhBKfjuvf4 - ContentProvider & Loader (обзор)
- https://www.youtube.com/watch?v=QEqGgmMkRDk - ContentProvider & SQLite (опыт использования)
- https://www.youtube.com/watch?v=zeDzbzLmpLs - ContentProvider (опыт использования)
- http://www.nerdgrl.org/ru/programming/sqlite-contentprovider-1/ - цикл статей ContentProvider & SQLite
- https://ru.stackoverflow.com/questions/653346/%D0%A7%D1%82%D0%BE-%D1%82%D0%B0%D0%BA%D0%BE%D0%B5-contentprovider - Ответ на русском StackOverflow “Что такое ContentProvider?”
- https://www.tutorialspoint.com/android/android_content_providers.htm - Tutorial ContentProvider
- https://www.youtube.com/watch?v=xHXn3Kg2IQE - классика (паттерны A/B/C)
- https://stackoverflow.com/questions/39825125/android-recyclerview-cursorloader-contentprovider-load-more - Tutorial ContentProvider & SQLite
Задуман, как способ предоставлять данные вашего приложения для сторонних приложений, + для реализации поиска среди данных вашего приложения с использованием search suggestions (подсказки при вводе слов для поиска).
На практике часто используется для создания единого источника данных внутри приложения (В противовес заявлению в оф.докумментации: “You don’t need to develop your own provider if you don’t intend to share your data with other applications.”). При этом получаем связки: ContentProvider & SQLite, ContentProvider & CursorLoader, ContentProvider & Loader…
В пользу только лишь внутреннего использования (польза для внешнего очевидна) ContentProvider’a говорит следующее:
-
Как уже было сказано, ContentProvider предоставляет унифицированный интерфейс для доступа к данным. Это говорит о том, что не нужно беспокоится о реализации хранения данных. Она может меняться, но данные остаются доступны.
-
Не нужно управлять жизненным циклом объекта для доступа к данным (к примеру, экземпляра SQLiteDatabase). Ведь в случае прямого использования таких объектов возникает немало вопросов: где хранить этот объект? Когда закрывать базу данных? Когда уничтожать этот объект? ContentProvider позволяет получать доступ к данным из любого места, где доступен контекст приложения (экземпляр Context).
-
ContentProvider полностью соответствует концепциям REST (о которых мы говорили в этих заметках)
Немного о последнем пункте. Итак, соответствие REST:
- Для каждой сущности ContentProvider’a есть свой URI.
- Регистрация в манифесте ContentProvider’a выглядит следующим образом (пока без пояснения и др. возможных атрибутов):
- рекомендуется использовать в качестве authority имя пакета с префиксом поясняющим суть провайдера, например для нашего списка контактов можно было бы применить: io.github.ziginsider.peopleprovider
- URI образуется по схеме:
- У ContentProvider’a приложения всегда определен базовый URI. Он складывается из префикса “content://” и authorities. Таким образом, для нашего примера базовый URI = “content://io.github.ziginsider.peopleprovider”
- Далее, допустим мы хотим создать группу, в которой будет храниться информация о писателях (в рамках базы данных это будет таблица). Тогда URI для этой группы должно выглядеть следующим образом: “content://io.github.ziginsider.peopleprovider/writers”. Если мы обратимся к данным в ContentProvider по этому URI, то получим все экземпляры, сохраненные в этой группе.
- И наконец, если нам нужно URI для отдельного объекта, то оно будет выглядеть следующим образом: “content://io.github.ziginsider.peopleprovider/writers/4”. Где 4 – это номер добавленного экземпляра.
Создание своего СontentProvider’a
На практике нам необходимо наследоваться от класса ContentProvider и реализовать следующие методы:
- инициализирует ContentProvider. Провайдер будет создан как только вы обратитесь к нему с помощью ContentResolver’a. Возвращает true, если ContentProvider создан успешно. В общем случае, не рекомендуется делать в onCreate() ContentProvider’a длительных операций т.к. это может повесить onCreate() Application (??)
- Возвращает объект Cursor по полученному URI. URI парсится, согласно заданным нами правилам. В случае использования в качестве источника данных БД SQLite извлекает данные из БД, и возвращает их в виде Cursor. (соответствует HTTP методу GET).
- добавляет новые данные, возвращает URI новой записи. (соответствует HTTP методу POST)
- добавляет массив элементов (не является обязательным для реализации в отличии от всех остальных методов)
- обновляет строки в хранилище данных согласно заданным условиям. (в терминологии HTTP методов – PUT или PATCH)
- удаляет данные. ( единственный метод ContentProvider, HTTP аналог которого имеет такое же название)
- возвращает MIME-тип для заданной content URI. Обычно этот метод используют для сопоставления имени таблицы и URI, чтобы потом выполнить запрос к базе данных.
Следует помнить, что все перечисленные методы кроме onCreate() могут выполняться одновременно в нескольких потоках, и поэтому должны быть потоко-безопасными (thread-safe).
Допустим, мы создали свой ContentProvider, добавили его в манифесте и теперь можем обращаться к нему в качестве интерфейса для работы с данными. Но здесь есть небольшая тонкость – мы создавали объект ContentProvider, но обращаться к данным нужно через объект ContentResolver, который можно получить через метод getContentResolver в классе Context:
Манифест
Регистрация ContentProvider’a в манифесте под тегом <provider>. Подробная иформация здесь.
синтаксис:
Атрибут android:exporter задает возможность использования нашего ContentProvider’a сторонними приложениями. До версии Android 16 по умолчанию был true, в версиях >= 16 по умолчанию false. Поэтому необходимо обращать внимание на состояние этого атрибута.
Атрибут android:authorities задает ключ по которому мы будем обращаться к нашему ContentProvider’у. Стоит обратить внимание на его уникальность. Установка приложений с одним и тем же значением authorities на одно устройство выдаст ошибку.
Атрибуты android:permission, android:readPermission, android:writePermission - задают ограничения на использование нашего ContentProvider’a сторонними приложениями (на доступ к ContentProvider’у внутри нашего приложения эти атрибуты не влияют). Атрибут android:readPermission и android:writePermission имеют приоритет над атрибутом android:permission, и, соответственно, отвечают за доступ к функции query() и фунциям insert(), bulkInsert(), update(), delete() нашего ContentProvider’a для стороннего приложения.
Например, в манифесте записано следующее:
Тогда в манифесте стороннего приложения, которое должно иметь доступ на чтение данных из ContentProvider’a должно быть указано разрешение:
Формирование URI
Соответсвие ContentProvider’a концепции REST выражается в том, что доступ к данным осуществляется с помощью URI. Соответственно, URI необходимо как-то формировать для запроса к необходимым данным.
URI - (Uniform Resource Identifier) унифицированный (единообразный) идентификатор ресурса. По-сути, символьная строка, позволяющая идентифицировать какой-либо ресурс.
Мы уже говорили, что URI ContentProvider’a формируется по следующей схеме:
- <prefix>:// - “content://”
- <authority> - уникальный идентификатор нашего ContentProvider’a. Вместе с <prefix> составляет базовый URI - часть которая будет присутствовать во всех запросах к нашему ContentProvider’у. Еще раз скажем, что его нужно делать уникальным. Обычно составляется по приципу “имя_пакета + имя_приложения + имя_провайдера”.
UriMatcher
UriMatcher - класс Android SDK, который хранит соответствие между URI и неким заданным значением integer. Это значение можно использовать в операторе switch, чтобы описать поведение для каждого Content URI. Весьма нужная штука для работы c ContentProvider’ом.
Два метода, которые необходимо знать void AddUri(String authority, String path, int code) и int match(Uri uri). Первый ставит в соответствие path (т.е., по-сути, Uri) и code (надеюсь, что такое authority объяснять не надо), второй по uri возвращает code. Данный класс используется для разбора Uri в REST-методах ContentProvider’a (getType(), query(), etc.) для идентификации действия по URI-запросу. Ниже мы полностью рассмотрим его применение на своем месте, пока - основновные моменты использования:
Объявление:
Задаем соответствие:
Проверяем на соответствие в операторе Switch: