lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

Основные сведения о поставщике контента

Поставщик контента управляет доступом к центральному репозиторию данных. Поставщик является компонентом приложения Android, который зачастую имеет собственный пользовательский интерфейс для работы с данными. Однако поставщики контента предназначены в первую очередь для использования другими приложениями, которые получают доступ к поставщику посредством клиентского объекта поставщика. Вместе поставщики и клиенты поставщиков обеспечивают согласованный, стандартный интерфейс к данным, который также обрабатывает взаимодействие между процессами и обеспечивает защищенный доступ к данным.

В этой статье рассматриваются основные сведения, касающиеся следующих тем:

  • принцип работы поставщика контента;
  • API, используемый для получения данных от поставщика контента;
  • API, используемый для вставки данных в поставщик контента и их обновления или удаления в нем;
  • другие функции API, которые упрощают работу с поставщиками.

Обзор

Поставщик контента предоставляет данные внешним приложениям в виде одной или нескольких таблиц, аналогичных таблицам в реляционной базе данных. Строка представляет собой экземпляр некоторого типа собираемых поставщиком данных, а каждый столбец в этой строке — это отдельный элемент данных, собранных для экземпляра.

Примером встроенного поставщика в платформе Android может служить пользовательский словарь, в котором хранятся данные о написании нестандартных слов, добавленных пользователем. В таблице 1 показано, как данные могут выглядеть в этой таблице поставщика.

Таблица 1. Пример таблицы пользовательского словаря.

word app id frequency locale _ID
mapreduce user1 100 en_US 1
precompiler user14 200 fr_FR 2
applet user2 225 fr_CA 3
const user1 255 pt_BR 4
int user5 100 en_UK 5

В каждой строке таблицы 1 представлен экземпляр слова, которое отсутствует в стандартном словаре. В каждом ее столбце содержатся некоторые данные для слова, например, данные о языке, на котором это слово было впервые использовано. Заголовки столбцов представляют собой имена столбцов, которые хранятся в поставщике. Чтобы узнать язык строки, необходимо обратиться к столбцу locale. В этом поставщике столбец _ID выступает в роли «основного ключа», который поставщик автоматически сохраняет.

Примечание. В поставщике необязательно должен быть основной ключ, а также ему необязательно использовать_ID в качестве имени столбца основного ключа, если таковой имеется. Однако, если необходимо привязать данные из поставщика к классу ListView, один из столбцов должен именоваться _ID. Дополнительные сведения об этом требовании представлены в разделе Отображение результатов запроса.

Доступ к поставщику

Для доступа приложения к данным из поставщика контента используется клиентский объект ContentResolver. В этом объекте имеются методы, которые вызывают идентичные методы в объекте поставщика, который представляет собой экземпляр одного из конкретных подклассов класса ContentProvider. В этих методах ContentResolver представлены основные функции CRUD (аббревиатура create, retrieve, update, delete [создание, получение, обновление и удаление]) постоянного хранилища.

Объект ContentResolver в процессе клиентского приложения и объект ContentProvider в приложении, которое владеет поставщиком, автоматически обрабатывают взаимодействие между процессами. Объект ContentProvider также выступает в роли уровня абстракции между репозиторием данных и внешним представлением данных в виде таблиц.

Примечание. Для доступа к поставщику ваше приложение обычно должно запросить определенные разрешения в своем файле манифеста. Дополнительные сведения об этом представлены в разделе Разрешения поставщика контента.

Например, чтобы получить из поставщика пользовательского словаря список слов и языков, на которых они представлены, вызовите метод ContentResolver.query(). В свою очередь, метод query() вызывает метод ContentProvider.query(), определенный поставщиком пользовательского словаря. В примере кода ниже показан вызов метода ContentResolver.query().

// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
    mProjection,                        // The columns to return for each row
    mSelectionClause                    // Selection criteria
    mSelectionArgs,                     // Selection criteria
    mSortOrder);                        // The sort order for the returned rows

В таблице 2 указано соответствие аргументов для метода query(Uri,projection,selection,selectionArgs,sortOrder) SQL-инструкции SELECT.

Таблица 2. Сравнение метода query() и SQL-запроса.

Аргумент метода query() Параметр/ключевое слово SELECT Примечания
Uri FROM table_name Uri соответствует таблице table_name в поставщике.
projection col,col,col,... projection представляет собой массив столбцов, которые следует включить в каждую полученную строку.
selection WHERE col = value selection задает критерии для выбора строк.
selectionArgs (Точный эквивалент отсутствует. В предложении выбора заполнители ? заменяются аргументами выбора).
sortOrder ORDER BY col,col,... sortOrder задает порядок отображения строк в возвращаемом объекте Cursor.

URI контента

URI контента представляет собой URI, который определяет данные в поставщике. URI контента могут включать символическое имя всего поставщика (его центр) и имя, которое указывает на таблицу (путь). При вызове клиентского метода для доступа к таблице в поставщике URI контента этой таблицы выступает в роли одного из аргументов этого метода.

Константа CONTENT_URI в предыдущих строках кода содержит URI контента таблицы words в пользовательском словаре. ОбъектContentResolver анализирует центр URI и использует его для «разрешения» поставщика путем сравнения центра с системной таблицей известных поставщиков. ContentResolver может отправить аргументы запроса в соответствующий поставщик.

ContentProvider использует часть URI контента, в которой указан путь, для выбора таблицы для доступа. В поставщике обычно имеется путь для каждой предоставляемой им таблицы.

В предыдущих строках кода полный URI для таблицы words выглядит следующим образом:

content://user_dictionary/words

Строка user_dictionary ֪– это центр поставщика, а строка words — это путь к таблице. Строка content:// (схема) присутствует всегда; она определяет, что это URI контента.

Многие поставщики предоставляют доступ к одной строке в таблице путем добавления идентификатора в конец URI. Например, чтобы извлечь из пользовательского словаря строку, в столбце _ID которой указано 4, можно воспользоваться следующим URI контента:

Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

Идентификаторы часто используются в случае, когда вы извлекли набор строк и хотите обновить или удалить одну из них.

Примечание. В классах Uri и Uri.Builder имеются методы для удобного создания правильно оформленных объектов URI из строк. ContentUris содержит методы для удобного добавления идентификаторов к URI. В примере кода выше для добавления идентификатора к URI контента UserDictionary используется метод withAppendedId().

Получение данных от поставщика

В это разделе рассматривается порядок получения данных от поставщика на примере поставщика пользовательского словаря.

Для полной ясности в примерах кода, приведенных в этом разделе, методы ContentResolver.query() вызываются в потоке пользовательского интерфейса. В реальном коде запросы следует выполнять асинхронно в отдельном потоке. Одним из способов реализовать это является использование класса CursorLoader, который более подробно описан в статье Загрузчики. Кроме того, в этой статье представлены лишь фрагменты кода; они не представляют собой готовое приложение.

Чтобы получить данные из поставщика, выполните указанные ниже основные действия.

  1. Запросите у поставщика разрешение на чтение.
  2. Определите код, который отвечает за отправку запроса поставщику.

Запрос разрешения на чтение

Чтобы ваше приложение могло получать данные от поставщика, приложению требуется получить от поставщика разрешение на чтение. Это разрешение невозможно получить во время выполнения; вместо этого вам необходимо указать, что вам требуется такое разрешение, в манифесте приложения. Для этого воспользуйтесь элементом <uses-permission> и укажите точное название разрешения, определенное поставщиком. Указав этот элемент в манифесте, вы тем самым запрашиваете необходимое разрешение для вашего приложения. Когда пользователи устанавливают ваше приложение, они косвенно получают разрешение по этому запросу.

Чтобы узнать точное название разрешения на чтение в используемом поставщике, а также названия других используемых в нем разрешений на чтение, обратитесь к документации поставщика.

Дополнительные сведения о роли разрешений в получении доступа к поставщику представлены в разделе Разрешения поставщика контента.

Поставщик пользовательского словаря задает разрешение android.permission.READ_USER_DICTIONARY в своем файле манифеста, поэтому приложению, которому требуется выполнить чтение данных из поставщика, необходимо запросить именно это разрешение.

Создание запроса

Следующим этапом получения данных от поставщика является создание запроса. В следующем фрагменте кода задаются некоторые переменные для доступа к поставщику пользовательского словаря:


// A "projection" defines the columns that will be returned for each row
String[] mProjection =
{
    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
    UserDictionary.Words.WORD,   // Contract class constant for the word column name
    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
};

// Defines a string to contain the selection clause
String mSelectionClause = null;

// Initializes an array to contain selection arguments
String[] mSelectionArgs = {""};

В следующем фрагменте кода демонстрируется порядок использования метода ContentResolver.query() (в качестве примера выступает поставщик пользовательского словаря): Клиентский запрос поставщика аналогичен SQL-запросу. В нем содержится набор столбцов, которые возвращаются, набор критериев выборки и порядок сортировки.

Набор столбцов, которые должен возвратить запрос, называется проекцией (переменная mProjection).

Выражение, которое задает строки для получения, состоит из предложения выбора и аргументов выбора. Предложение выбора представляет собой сочетание логических выражений, имен столбцов и значений (переменная mSelectionClause). Если вместо значения указать подставляемый параметр ?, метод запроса извлекает значение из массива аргументов выбора (переменная mSelectionArgs).

В следующем фрагменте кода, если пользователь не указал слово, то для предложения выбора задается значение null, а запрос возвращает все слова, имеющиеся в поставщике. Если пользователь указал слово, то для предложения выбора задается значение UserDictionary.Words.WORD + " = ?", а для первого элемента в массиве аргументов выбора задается введенное пользователем слово.

/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] mSelectionArgs = {""};

// Gets a word from the UI
mSearchString = mSearchWord.getText().toString();

// Remember to insert code here to check for invalid or malicious input.

// If the word is the empty string, gets everything
if (TextUtils.isEmpty(mSearchString)) {
    // Setting the selection clause to null will return all words
    mSelectionClause = null;
    mSelectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered.
    mSelectionClause = UserDictionary.Words.WORD + " = ?";

    // Moves the user's input string to the selection arguments.
    mSelectionArgs[0] = mSearchString;

}

// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    mProjection,                       // The columns to return for each row
    mSelectionClause                   // Either null, or the word the user entered
    mSelectionArgs,                    // Either empty, or the string the user entered
    mSortOrder);                       // The sort order for the returned rows

// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
    /*
     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
     * call android.util.Log.e() to log this error.
     *
     */
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {

    /*
     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
     * an error. You may want to offer the user the option to insert a new row, or re-type the
     * search term.
     */

} else {
    // Insert code here to do something with the results

}

Этот запрос аналогичен следующей инструкции SQL:

SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;

В этой инструкции SQL вместо констант класса-контракта используются фактические имена столбцов.

Защита от ввода вредоносного кода

Если данные, которыми управляет поставщик контента, находятся в базе данных SQL, то включение в необработанные инструкции SQL внешних ненадежных данных может привести к атаке путем внедрения кода SQL.

Рассмотрим следующее предложение выбора:

// Constructs a selection clause by concatenating the user's input to the column name
String mSelectionClause =  "var = " + mUserInput;

Если вы используете это предложение, вы разрешаете пользователю связать вашу инструкцию SQL с вредоносным кодом SQL. Например, пользователь может ввести nothing; DROP TABLE *; для mUserInput, что приведет к созданию следующего предложения выбора: var = nothing; DROP TABLE *;. Поскольку предложение выбора выполняется в потоке как инструкция SQL, это может привести к тому, что поставщик удалит все таблицы в соответствующей базе данных SQLite (если только в поставщик не настроен на отслеживание попыток внедрить вредоносный код SQL).

Чтобы избежать этого, воспользуйтесь предложением выбора, в котором ? выступает в качестве подставляемого параметра, а также отдельным массивом аргументов выбора. После этого ввод пользователя будет связан напрямую с запросом и не будет интерпретироваться как часть инструкции SQL. Поскольку в этом случае введенный пользователем запрос не рассматривается как код SQL, то в него не удастся внедрить вредоносный код SQL. Вместо объединения, которое следует включить в пользовательский ввод, используйте следующее предложение выбора:

// Constructs a selection clause with a replaceable parameter
String mSelectionClause =  "var = ?";

Настройте массив аргументов выбора следующим образом:

// Defines an array to contain the selection arguments
String[] selectionArgs = {""};

Укажите значение для массива аргументов выбора:

// Sets the selection argument to the user's input
selectionArgs[0] = mUserInput;

Предложение выбора, в котором ? используется в качестве подстановочного параметра, и массив аргументов выбора представляют собой предпочтительный способ указания выбора, даже если поставщик не использует базу данных SQL.

Отображение результатов запроса

Клиентский метод ContentResolver.query() всегда возвращает объект Cursor, содержащий столбцы, указанные в проекции запроса для строк, которые соответствуют критериям выборки в запросе. Объект Cursor предоставляет прямой доступ на чтение содержащихся в нем строк и столбцов. С помощью методов Cursor можно выполнить итерацию по строкам в результатах, определить тип данных для каждого столбца, получить данные из столбца, а также проверить другие свойства результатов. Некоторые реализации объекта Cursor автоматически обновляют объект при изменении данных в поставщике или запускают выполнение методов в объекте-наблюдателе при изменении объектаCursor, либо выполняют и то, и другое.

Примечание. Поставщик может ограничить доступ к столбцам на основе характера объекта, выполняющего запрос. Например, поставщик контактов ограничивает доступ адаптеров синхронизации к некоторым столбцам, поэтому он не возвращает их в операцию или службу.

Если строки, соответствующие критериям выборки, отсутствуют, поставщик возвращает объектCursor, в котором для метода Cursor.getCount() указано значение «0» (пустой объект cursor).

При возникновении внутренней ошибки результаты запроса зависят от определенного поставщика. Поставщик может возвратитьnull или выдать Exception.

Поскольку Cursor представляет собой «список» строк, то наилучшим способом отобразить содержимое объекта Cursor будет связать его с ListView посредством SimpleCursorAdapter.

Следующий фрагмент кода является продолжением предыдущего фрагмента. Он создает объект SimpleCursorAdapter, содержащий объектCursor, который был получен в запросе, а затем определяет этот объект в качестве адаптера для ListView:

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
};

// Defines a list of View IDs that will receive the Cursor columns for each row
int[] mWordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
    getApplicationContext(),               // The application's Context object
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
    mCursor,                               // The result from the query
    mWordListColumns,                      // A string array of column names in the cursor
    mWordListItems,                        // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);

Примечание. Чтобы вернуть ListView с объектом Cursor, объект cursor должен содержать столбец с именем _ID. Поэтому показанный ранее запрос извлекает столбец_ID для таблицы words, даже если ListView не отображает ее. Данное ограничение также объясняет, почему в каждой таблице поставщика имеется столбец _ID.

Получение данных из результатов запроса

Вместо того, чтобы просто отобразить результаты запроса, вы можете использовать их для выполнения других задач. Например, можно получить написание слов из пользовательского словаря, а затем выполнить их поиск в других поставщиках. Для этого выполните итерацию по строкам в объекте Cursor:


// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);

/*
 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
 * an internal error occurs. Other providers may throw an Exception instead of returning null.
 */

if (mCursor != null) {
    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
     * exception.
     */
    while (mCursor.moveToNext()) {

        // Gets the value from the column.
        newWord = mCursor.getString(index);

        // Insert code here to process the retrieved word.

        ...

        // end of while loop
    }
} else {

    // Insert code here to report an error if the cursor is null or the provider threw an exception.
}

Реализации объекта Cursor содержат несколько методов get для получения из объекта различных типов данных. Например, в следующем фрагменте кода используется метод getString(). В них также имеется метод getType(), который возвращает значение, указывающее на тип данных в столбце.

Разрешения поставщика контента

Приложение поставщика может задавать разрешения, которые требуются другим приложениям для доступа к данным в поставщике. Такие разрешения гарантируют, что пользователь знает, к каким данным приложение будет пытаться получить доступ. На основе требований поставщика другие приложения запрашивают разрешения, которые требуются им для доступа к поставщику. Конечные пользователи видят запрошенные разрешения при установке приложения.

Если приложение поставщика не задает никаких разрешений, другие приложения не получают доступ к данным поставщика. Однако компонентам приложения поставщика всегда предоставлен полный доступ на чтение и запись, независимо от заданных разрешений.

Как уже было отмечено ранее, для получения данных из поставщика пользовательского словаря требуется разрешение android.permission.READ_USER_DICTIONARY. В поставщике предусмотрено отдельное разрешениеandroid.permission.WRITE_USER_DICTIONARY для вставки, обновления или удаления данных.

Чтобы получить разрешения, необходимые для доступа к поставщику, приложение запрашивает их с помощью элемента <uses-permission> в файле манифеста. При установке менеджером пакетов Android приложения пользователю необходимо утвердить все разрешения, запрашиваемые приложением. В случае утверждения всех разрешений менеджер пакетов продолжает установку; если же пользователь отклоняет их, менеджер пакетов отменяет установку.

Для запроса доступа на чтение данных в поставщике пользовательского словаря используется следующий элемент <uses-permission>:

    <uses-permission android:name="android.permission.READ_USER_DICTIONARY">

Дополнительные сведения о влиянии разрешений на доступ к поставщику представлены в статье Безопасность и разрешения.

Вставка, обновление и удаление данных

Подобно тому, как вы получаете данные от поставщика, вы также можете можете использовать возможности взаимодействия между клиентом поставщика и объектом ContentProvider поставщика для изменения данных. Можно вызвать метод объекта ContentResolver, указав аргументы, которые были переданы в соответствующий метод объекта ContentProvider. Поставщик и клиент поставщика автоматически обрабатывают взаимодействие между процессами и обеспечивают безопасность.

Вставка данных

Для вставки данных в поставщик вызовите метод ContentResolver.insert(). Этот метод вставляет новую строку в поставщик и возвращает URI контента для этой строки. В следующем фрагменте кода демонстрируется порядок вставки нового слова в поставщик пользовательского словаря:

// Defines a new Uri object that receives the result of the insertion
Uri mNewUri;

...

// Defines an object to contain the new values to insert
ContentValues mNewValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value"
 */
mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
mNewValues.put(UserDictionary.Words.WORD, "insert");
mNewValues.put(UserDictionary.Words.FREQUENCY, "100");

mNewUri = getContentResolver().insert(
    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
    mNewValues                          // the values to insert
);

Данные для новой строки поступают в один объект ContentValues, который аналогичен объекту cursor с одной строкой. Столбцы в этом объекте необязательно должны содержать данные такого же типа, и если вы вообще не собираетесь указывать значение, вы можете задать для столбца значение null с помощью метода ContentValues.putNull().

Код в представленном фрагменте не добавляет столбец _ID, поскольку этот столбец сохраняется автоматически. Поставщик присваивает уникальное значение _ID каждой добавляемой строке. Обычно поставщики используют это значение в качестве основного ключа таблицы.

URI контента, возвращенный в элементе newUri, служит для идентификации новой добавленной строки в следующем формате:

content://user_dictionary/words/<id_value>

<id_value> — это содержимое столбца _ID для новой строки. Большинство поставщиков автоматически определяют эту форму URI контента, а затем выполняют запрошенную операцию с требуемой строкой.

Чтобы получить значение _ID из возвращенного объекта Uri, вызовите метод ContentUris.parseId().

Обновление данных

Чтобы обновить строку, используйте объект ContentValues с обновленными значениями (точно так же, как вы это делаете при вставке) и критериями выборки (так же, как и с запросом). Используемый вами клиентский метод называется ContentResolver.update(). Вам не нужно добавлять значения в объект ContentValues для обновляемых столбцов. Чтобы очистить содержимое столбца, задайте значение null.

Следующий фрагмент кода служит для изменения языка во всех строках, где в качестве языка указано en, на null. Возвращаемое значение представляет собой количество строк, которые были обновлены:

// Defines an object to contain the updated values
ContentValues mUpdateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
String[] mSelectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int mRowsUpdated = 0;

...

/*
 * Sets the updated value and updates the selected words.
 */
mUpdateValues.putNull(UserDictionary.Words.LOCALE);

mRowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mUpdateValues                       // the columns to update
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

Также следует проверить пользовательский ввод при вызове метода ContentResolver.update(). Дополнительные сведения об этом представлены в разделе Защита от ввода вредоносного кода.

Удаление данных

Удаление данных аналогично получению данных строки: необходимо указать критерии выборки для строк, которые требуется удалить, после чего клиентский метод возвратит количество удаленных строк. Ниже представлен фрагмент кода для удаления строк с идентификатором appid user. Метод возвращает количество удаленных строк.


// Defines selection criteria for the rows you want to delete
String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] mSelectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int mRowsDeleted = 0;

...

// Deletes the words that match the selection criteria
mRowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

Также следует проверить пользовательский ввод при вызове метода ContentResolver.delete(). Дополнительные сведения об этом представлены в разделе Защита от ввода вредоносного кода.

Типы поставщиков данных

Поставщики контента могут предоставлять различные тип данных. Поставщик пользовательского словаря предоставляет только текст, но также может предоставлять следующие форматы:

  • целое число;
  • длинное целое число (long);
  • число с плавающей запятой;
  • длинное число с плавающей запятой (double).

Другим типом данных, предлагаемых поставщиком, является большой двоичный объект (BLOB), реализованный как 64-разрядный массив. Чтобы просмотреть доступные типы данных, обратитесь к методам get класса Cursor.

Тип данных для каждого столбца в поставщике обычно указывается в документации к поставщику. Типы данных для поставщика пользовательского словаря указаны в справочной документации для класса-контракта UserDictionary.Words (дополнительные сведения о классах-контрактах представлены в разделе Классы-контракты). Также определить тип данных можно путем вызова метода Cursor.getType().

Поставщики также хранят информацию о типе данных MIME для каждого определяемого ими URI контента. Эту информацию можно использовать для определения того, может ли ваше приложение обрабатывать предлагаемые поставщиком данные, а также для выбора типа обработки на основе типа MIME. Информация о типе MIME обычно требуется при работе с поставщиком, который содержит сложные структуры данных или файлы. Например, в таблицеContactsContract.Data в поставщике контактов используются типы MIME для отметки типа данных контакта, которые хранятся в каждой строке. Чтобы получить тип MIME, соответствующий URI контента, вызовите метод ContentResolver.getType().

Синтаксис стандартных и настраиваемых типов MIME описан в справке по типам MIME.

Альтернативные формы доступа к поставщику

При разработке приложения следует учитывать три альтернативных формы доступа к поставщику:

  • Пакетный доступ: можно создать пакет вызовов доступа с использованием методов в классе ContentProviderOperation, а затем применить их с помощью метода ContentResolver.applyBatch().
  • Асинхронные запросы: Запросы следует выполнять в отдельном потоке. Одним из способов реализовать это является использование объекта CursorLoader. Примеры этого представлены в статье Загрузчики.
  • Доступ к данным с помощью намерений: Несмотря на то, что намерение невозможно отправить напрямую в поставщик, вы можете отправить запрос в приложение поставщика, в котором обычно имеется больше возможностей для изменения данных поставщика.

Пакетный доступ и изменение с помощью намерений описаны в следующих разделах.

Пакетный доступ

Пакетный доступ к поставщику полезно использовать в случаях, когда необходимо вставить большое количество строк, или для вставки строк в несколько таблиц в рамках одного вызова метода, а также в общих случаях для выполнения ряда операций на границах процессов в виде транзакции (атомарной операции).

Для доступа к поставщику в «пакетном режиме» необходимо создать массив объектов ContentProviderOperation, а затем отправить их в поставщик контента с помощью метода ContentResolver.applyBatch(). В этот метод необходимо передать центр поставщика контента, а не определенный URI контента. Это позволит каждому объекту ContentProviderOperation в массиве взаимодействовать с разными таблицами. Метод ContentResolver.applyBatch() возвращает массив результатов.

В описании класса-контракта ContactsContract.RawContacts также представлен фрагмент кода, в котором демонстрируется вставка в пакетном режиме. В исходном файле ContactAdder.java примера приложения Диспетчер контактов имеется пример пакетного доступа.

Доступ к данным с помощью намерений

Намерения позволяют в обход получать доступ к поставщику контента. Вы можете разрешить пользователям доступ к данным в поставщике даже в том случае, если у приложения отсутствуют разрешения на доступ, либо путем получения результирующего намерения от приложения, у которого имеются необходимые разрешения, либо путем активации приложения, у которого имеются разрешения и которое разрешает пользователю работать с ним.

Получение доступа с временными разрешениями

Вы можете получить доступ к данным в поставщике контента даже тогда, когда у вас нет необходимых разрешений на доступ , путем отправки намерения в приложение, у которого есть такие разрешения, и получения результирующего намерения, которое содержит разрешения URI. Эти разрешения для определенного URI контента действуют до тех пор, пока не будет завершена операция, получившая их. Приложение, у которой имеются бессрочные разрешения, предоставляет временные разрешения путем задания соответствующего флага в результирующем намерении:

Примечание. Эти флаги не предоставляют общий доступ на чтение или запись поставщику, центр которого указан в URI контента. Доступ предоставляется только самому URI.

Поставщик определяет разрешения URI для URI контента в своем манифесте с помощью атрибута android:grantUriPermission элемента <provider>, а также с помощью дочернего элемента <grant-uri-permission> элемента <provider>. Дополнительные сведения о механизме разрешений URI представлены в статье Безопасность и разрешения в разделе Разрешения URI.

Например, можно получить данные о контакте из поставщика контактов, даже если у вас нет разрешения READ_CONTACTS. Возможно, это потребуется реализовать в приложении, которое отправляет электронные поздравления контакту в день его рождения. Вместо запроса READ_CONTACTS, когда вы получаете доступ ко всем контактам пользователя и всей информации о них, можно предоставить пользователю возможность указать, какие контакты используются вашим приложением. Для этого воспользуйтесь указанным ниже процессом.

  1. Ваше приложение отправляет намерение, содержащее действие ACTION_PICK и тип MIME CONTENT_ITEM_TYPE контактов, используя для этого метод startActivityForResult().
  2. Поскольку это намерение соответствует условиям отбора намерений для операции выбора приложения «Контакты», эта операция переходит на передний план.
  3. В операции выбора пользователь выбирает контакт для обновления. Когда это происходит, операция выбора вызывает метод setResult(resultcode, intent) для создания намерения, которое будет передано обратно в ваше приложение. Намерение содержит URI контента выбранного пользователем контакта, а также флаги FLAG_GRANT_READ_URI_PERMISSION дополнительных данных. Эти флаги предоставляют вашему приложению разрешение URI на чтение данных контакта, на который указывает URI контента. Затем операция выбора вызывает методfinish(), чтобы вернуть управление вашему приложению.
  4. Ваша операция возвращается на передний план, а система вызывает метод onActivityResult() вашей операции. Этот метод получает результирующее намерение, созданное операцией выбора в приложении «Контакты».
  5. С помощью URI контента из результирующего намерения можно выполнить чтение данных контакта из поставщика контактов, даже если вы не запрашивали у поставщика постоянный доступ на чтение в своем манифесте. Можно получить информацию о дне рождения контакта или сведения о его адресе эл. почты, а затем отправить контакту электронное поздравление.

Использование другого приложения

Простой способ разрешить пользователю изменять данные, на доступ к которым у вас нет доступа — это активировать приложение, у которого есть такие разрешения, а затем предоставить пользователю возможность выполнять необходимые действия в этом приложении.

Например, приложение «Календарь» принимает намерения ACTION_INSERT, с помощью которого можно активировать пользовательский интерфейс приложения для вставки. Вы можете передать в это намерение дополнительные данные, которые приложение использует для заполнения полей в пользовательском интерфейсе. Поскольку синтаксис повторяющихся событий довольно сложный, то события предпочтительно вставлять в поставщик календаря путем активации приложения «Календарь» с помощью действия ACTION_INSERT и последующего предоставления пользователю возможности самому вставить событие в этом приложении.

Классы-контракты

Класс-контракт определяет константы, которые обеспечивают для приложений возможность работать с URI контента, именами столбцов, операциями намерения и другими функциями поставщика контента. Классы-контракты не включены в поставщик; разработчику поставщика следует определить их и сделать их доступными для других разработчиков. Многие из поставщиков, включенные в платформу Android, содержат соответствующие классы-контракты в пакете android.provider.

Например, в поставщике пользовательского календаря имеется класс-контракт UserDictionary, содержащий константы URI контента и имен столбцов. URI контента для таблицы words определен в константе UserDictionary.Words.CONTENT_URI. В классе UserDictionary.Words также имеются константы имен столбцов, которые используются в фрагментах кода примера приложения, представленных в этой статье. Например, проекцию запроса можно определить следующим образом:

String[] mProjection =
{
    UserDictionary.Words._ID,
    UserDictionary.Words.WORD,
    UserDictionary.Words.LOCALE
};

Другим классом-контрактом является класс ContactsContract для поставщика контактов. В справочной документации к этому классу представлены фрагменты кода примера приложения. Один из его подклассов, ContactsContract.Intents.Insert, представляет собой класс-контракт, который содержит константы для намерений и их данных.

Справка по типам MIME

Поставщики контента могут возвращать как стандартные типы мультимедиа MIME, так и строки с настраиваемым типом MIME, либо оба этих типа.

Типы MIME имеют следующий формат:

type/subtype

Например, хорошо известный тип MIME text/html имеет тип text и подтип html. Если поставщик возвращает этот тип URI, это означает, что строка запроса, в которой используется этот URI, возвратит текста с тегами HTML.

Строки с настраиваемым типом MIME, которые также называются типами MIME поставщика, имеют более сложные значения типов и подтипов. Значение типа всегда следующее:

vnd.android.cursor.dir

для нескольких строк, или

vnd.android.cursor.item

для одной строки.

Подтип зависит от поставщика. Встроенные поставщики Android обычно содержат простой подтип. Например, когда приложение «Контакты» создает строку для номера телефона, оно задает следующий тип MIME в этой строке:

vnd.android.cursor.item/phone_v2

Обратите внимание, что значение подтипа просто phone_v2.

Разработчики поставщиков могут создавать свои собственные шаблоны подтипов на основе центра и названий таблиц поставщика. Например, рассмотрим поставщик, который содержит расписание движения поездов. Центром поставщика является com.example.trains, в котором содержатся таблицы Line1, Line2 и Line3. В ответ на следующий URI контента

content://com.example.trains/Line1

для таблицы Line1 поставщик возвращает следующий тип MIME

vnd.android.cursor.dir/vnd.example.line1

В ответ на следующий URI контента

content://com.example.trains/Line2/5

для строки 5 в таблице Line2 поставщик возвращает следующий тип MIME

vnd.android.cursor.item/vnd.example.line2

В большинстве поставщиков контента определены константы класса-контракта для используемых в них типов MIME. Например, класс-контракт ContactsContract.RawContacts поставщика контактов определяет константу CONTENT_ITEM_TYPE для типа MIME одной строки необработанного контакта.

URI контента для единичных строк описываются в разделе URI контента.