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

Создание поставщика контента

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

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

Подготовка к созданию поставщика

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

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

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

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

После этого можно приступать к созданию поставщика. Для этого выполните указанные ниже действия.

  1. Спроектируйте базовое хранилище для своих данных. Поставщик контента предоставляет данные двумя способами:
    Данные для файлов
    Данные, которые обычно поступают в файлы, такие как фотографии, аудио- или видеоданные. Файлы следует хранить в закрытом пространстве вашего приложения. В ответ на запрос файла из другого приложения ваш поставщик может предложить дескриптор для файла.
    Структурированные данные
    Данные, которые обычно поступают в базу данных, массив или аналогичную структуру. Данные следует хранить в той форме, которая совместима с таблицами из строк и столбцов. Строка представляет собой объект, например, пользователя, позицию или учетную единицу. Столбец представляет собой некоторые данные об объекте, например, имя пользователя или стоимость единицы. Обычно данные такого типа хранятся в базе данных SQLite, однако вы можете использовать постоянное хранилище любого типа. Дополнительные сведения о типах хранилищ, доступных в системе Android, представлены в разделе Проектирование хранилища данных.
  2. Определите конкретную реализацию класса ContentProvider и его необходимые методы. Этот класс выступает в роли интерфейса между вашими данными и остальной частью системы Android. Дополнительные сведения об этом классе представлены в разделе Реализация класса ContentProvider.
  3. Определите строку центра поставщика, его URI контента и имена столбцов. Если необходимо, чтобы приложение поставщика обрабатывало намерения, также необходимо определить действия намерений, дополнительные данные и флаги. Кроме того, необходимо определить разрешения, которые будут необходимы приложениям для доступа к вашим данным. Все эти значения следует определить как константы в отдельном классе-контракте; в дальнейшем этот класс можно предоставить другим разработчикам. Дополнительные сведения о URI контента представлены в разделе Проектирование URI контента. Дополнительные сведения о намерениях представлены в разделе Намерения и доступ к данным.
  4. Добавьте другие дополнительные компоненты, например, демонстрационные данные или реализация адаптера AbstractThreadedSyncAdapter, который служит для синхронизации данных между поставщиком и облаком.

Проектирование хранилища данных

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

В Android имеются некоторые технологии хранения данных:

  • В системе Android имеется API базы данных SQLite, который используется собственными поставщиками Android для хранения табличных данных. С помощью класса SQLiteOpenHelper можно создавать базы данных, а класс SQLiteDatabase представляет собой базовый класс для доступа к базам данных.

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

  • Для хранения данных файлов в Android предусмотрены различные API-интерфейсы для работы с файлами. Дополнительные сведения о хранилище файлов представлены в статье Хранилище данных. Если вы проектируете поставщик, который предлагает мультимедийные данные, такие как музыка или видео, можно создать поставщик, объединяющий табличные данные и файлы.
  • Для работы с сетевыми данными используйте классы в java.net и android.net. Вы также можете синхронизировать сетевые данные с локальным хранилищем данных (например, с базой данных), а затем предоставить такие данные в виде таблиц или файлов. Такой тип синхронизации демонстрируется в примере приложения адаптера синхронизации.

Рекомендации по проектированию данных

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

  • В табличных данных всегда должен быть столбец для «основного ключа», который поставщик хранит в виде уникального числового значения для каждой строки. Вы можете использовать это значение для связывания строки со строками в других таблицах (используя его в качестве «внешнего ключа»). Несмотря на то, что вы можете использовать любое имя для этого столбца, рекомендуется указать имя BaseColumns._ID, поскольку для связывания результатов запроса поставщика с ListView необходимо, чтобы один из получаемых столбцов назывался _ID.
  • Если вы планируете предоставлять растровые изображения или очень большие фрагменты данных для файлов, то данные следует хранить в файлах, а затем предоставлять их косвенно вместо хранения таких данных прямо в таблице. В таком случае вам необходимо сообщить пользователям вашего поставщика о том, что для доступа к данным им потребуется воспользоваться методом ContentResolver.
  • Для хранения данных разного размера или с разной структурой используйте тип BLOB. Например, столбец BLOB можно использовать для хранения буфера протокола или структуры JSON.

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

Проектирование URI контента

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

Базовые сведения о URI контента представлены в статье Основные сведения о поставщике контента.

Проектирование центра поставщика

У поставщика обычно имеется только один центр, который выступает в качестве его внутреннего имени в системе Android. Во избежание конфликтов с другими поставщиками в качестве основы центра поставщика должны выступать сведения о владении доменом в Интернете (в обратном порядке). Поскольку эта рекомендация также применяется и к названиям пакетов Android, вы можете определить центр своего поставщика в виде расширения названия пакета, в котором содержится поставщик. Например, если пакет Android называется com.example.<appname>, то центром вашего поставщика должен быть com.example.<appname>.provider.

Проектирование структуры пути

Обычно разработчики создают URI контента на основе центра поставщика, добавляя к нему путь, который указывает на отдельные таблицы. Например, если имеется две таблицы, table1 и table2, центр поставщика из предыдущего примера следует объединить для формирования следующих URI контента: com.example.<appname>.provider/table1 и com.example.<appname>.provider/table2. Пути не ограничены одним сегментом, и не на каждом уровне пути имеется таблица.

Обработка идентификаторов URI контента

Обычно поставщики предоставляют доступ к одной строке в таблице путем принятия URI контента, в конце которого указано значение идентификатора строки. Также поставщики обычно проверяют совпадение значения идентификатора по столбцу _ID в таблице и предоставляют запрашиваемый доступ к соответствующей строке.

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

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

Шаблоны URI контента

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

Для определения совпадения URI контента с шаблоном используются подстановочные символы:

  • *: соответствие строке любой длины с любыми допустимыми символами;
  • #: соответствие строке любой длины с цифрами.

В качестве примера для проектирования и написания кода для обработки URI контента рекомендуется использовать центр поставщикаcom.example.app.provider, который распознает следующие URI контента, указывающие на таблицы:

  • content://com.example.app.provider/table1: таблица table1;
  • content://com.example.app.provider/table2/dataset1: таблица dataset1;
  • content://com.example.app.provider/table2/dataset2: таблица dataset2;
  • content://com.example.app.provider/table3: таблица table3.

Поставщик также распознает следующие URI контента, если к ним добавлен идентификатор строки (например, content://com.example.app.provider/table3/1 для строки с идентификатором1 в таблице table3.

Возможно использование следующих шаблонов URI контента:

content://com.example.app.provider/*
Совпадает с любым URI контента в поставщике.
content://com.example.app.provider/table2/*:
Совпадает с URI контента в таблицах dataset1 и dataset2, однако не совпадает с URI контента в таблице table1 или table3.
content://com.example.app.provider/table3/#: Совпадает с URI контента для отдельных строк в таблице table3, такими как content://com.example.app.provider/table3/6 для строки с идентификатором 6.

Во фрагменте кода ниже показано, как работают методы в классе UriMatcher. Этот код обрабатывает URI для всей таблицы иначе, чем для URI для отдельной строки, используя шаблон URI контента content://<authority>/<path> для таблиц и шаблон content://<authority>/<path>/<id> — для отдельных строк.

Метод addURI() сопоставляет центр поставщика и его путь с целочисленным значением. Метод match() возвращает целочисленное значение для URI. Оператор switch выбирает, следует ли ему выполнить запрос всей таблицы или только отдельной записи:

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher sUriMatcher;
...
    /*
     * The calls to addURI() go here, for all of the content URI patterns that the provider
     * should recognize. For this snippet, only the calls for table 3 are shown.
     */
...
    /*
     * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
     * in the path
     */
    sUriMatcher.addURI("com.example.app.provider", "table3", 1);

    /*
     * Sets the code for a single row to 2. In this case, the "#" wildcard is
     * used. "content://com.example.app.provider/table3/3" matches, but
     * "content://com.example.app.provider/table3 doesn't.
     */
    sUriMatcher.addURI("com.example.app.provider", "table3/#", 2);
...
    // Implements ContentProvider.query()
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * Choose the table to query and a sort order based on the code returned for the incoming
         * URI. Here, too, only the statements for table 3 are shown.
         */
        switch (sUriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query
                 */
                selection = selection + "_ID = " uri.getLastPathSegment();
                break;

            default:
            ...
                // If the URI is not recognized, you should do some error handling here.
        }
        // call the code to actually do the query
    }

Другой класс, ContentUris, предоставляет удобные методы для работы с частью id URI контента. Классы Uri и Uri.Builder содержат удобные методы для синтаксического анализа существующих объектов Uri и создания новых.

Реализация класса ContentProvider

Экземпляр класса ContentProvider управляет доступом к структурированному набору данных путем обработки запросов от других приложений. В конечном счете, при всех формах доступа вызывается метод ContentResolver, который затем вызывает конкретный метод ContentProvider для получения доступа.

Необходимые методы

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

query()
Получение данных от поставщика. Использует аргументы для выбора таблицы для запроса, строк и столбцов, которые необходимо возвратить, и указания порядка сортировки результатов. Возвращает данные в виде объекта Cursor.
insert()
Вставка строки в ваш поставщик. Использует аргументы для выбора конечной таблицы и получения значений столбца, которые следует использовать. Возвращает URI контента для новой вставленной строки.
update()
Обновление существующих строк в поставщике. Использует аргументы для выбора таблицы и строк для обновления, а также для получения обновленных значений столбца. Возвращает количество обновленных строк.
delete()
Удаление строк из поставщика. Использует аргументы для выбора таблицы и строк для удаления. Возвращает количество удаленных строк.
getType()
Возвращение типа MIME, соответствующего URI контента. Дополнительные сведения об этом методе представлены в разделе Реализация типов MIME поставщика контента.
onCreate()
Инициализация поставщика. Система Android вызывает этот метод сразу после создания вашего поставщика. Обратите внимание, что поставщик не будет создан до тех пор, пока объект ContentResolver не прекратит попытки получить доступ к нему.

Подпись этих методов аналогична подписи для идентичных методов в объекте ContentResolver.

При реализации этих методов следует учитывать указанные ниже моменты.

  • Все эти методы, кроме onCreate(), можно вызвать сразу из нескольких потоков, поэтому они должны быть реализованы с сохранением потокобезопасности. Дополнительные сведения об использовании нескольких потоков представлены в статье Процессы и потоки.
  • Избегайте слишком длинных операций в методе onCreate(). Отложите выполнение задач инициализации до тех пор, пока они не потребуются. Дополнительные сведения об этом представлены в разделе Реализация метода onCreate.
  • Несмотря на то, что вы должны реализовать эти методы, ваш код необязательно должен выполнять какие-либо другие действия, кроме возврата ожидаемого типа данных. Например, может потребоваться, чтобы другие приложения не имели возможности вставлять данные в некоторые таблицы. Для этого можно игнорировать вызов метода insert() и возвратить 0.

Реализация метода query()

Метод ContentProvider.query() должен возвращать объект Cursor, а при сбое выдавать Exception. Если в качестве хранилища данных используется база данных SQLite, можно просто возвратить объект Cursor, который был возвращен одним из методов query() класса SQLiteDatabase. Если запрос не соответствует ни одной строке, следует возвратить экземпляр объектаCursor, метод getCount() которого возвращает 0. null следует возвращать только в том случае, если во время обработки запроса произошла внутренняя ошибка.

Если вы не используете базу данных SQLite в качестве хранилища данных, обратитесь к одному из конкретных подклассов объекта Cursor. Например, класс MatrixCursor реализует объект cursor, в котором каждая строка представляет собой массив класса Object. С помощью этого класса воспользуйтесь методом addRow(), чтобы добавить новую строку.

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

Реализация метода insert()

Метод insert() добавляет новую строку в соответствующую строку, используя значения в аргументе ContentValues. Если в аргументе ContentValues отсутствует имя столбца, возможно, потребуется указать для него значение по умолчанию (либо в коде поставщика, либо в схеме базы данных).

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

Реализация метода delete()

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

Реализация метода update()

Метод update() принимает тот же аргумент ContentValues, который используется методом insert(), и те же аргументы selection и selectionArgs, которые используются методами delete() и ContentProvider.query(). Благодаря этому код можно повторно использовать между данными методами.

Реализация метода onCreate()

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

Например, если вы используете базу данных SQLite, в методе ContentProvider.onCreate() можно создать новый объект SQLiteOpenHelper, а затем создать таблицы SQL при первом открытии базы данных. Чтобы упростить это, при первом вызове метода getWritableDatabase() он автоматически вызывает метод SQLiteOpenHelper.onCreate().

В двух указанных ниже фрагментах кода иллюстрируется взаимодействие между методом ContentProvider.onCreate() и методом SQLiteOpenHelper.onCreate(). В первом фрагменте кода представлена реализация метода ContentProvider.onCreate():

public class ExampleProvider extends ContentProvider

    /*
     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
     * in a following snippet.
     */
    private MainDatabaseHelper mOpenHelper;

    // Defines the database name
    private static final String DBNAME = "mydb";

    // Holds the database object
    private SQLiteDatabase db;

    public boolean onCreate() {

        /*
         * Creates a new helper object. This method always returns quickly.
         * Notice that the database itself isn't created or opened
         * until SQLiteOpenHelper.getWritableDatabase is called
         */
        mOpenHelper = new MainDatabaseHelper(
            getContext(),        // the application context
            DBNAME,              // the name of the database)
            null,                // uses the default SQLite cursor
            1                    // the version number
        );

        return true;
    }

    ...

    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which table to open, handle error-checking, and so forth

        ...

        /*
         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
         *
         */
        db = mOpenHelper.getWritableDatabase();
    }
}

В следующем фрагменте кода представлена реализация метода SQLiteOpenHelper.onCreate(), включая вспомогательный класс:

...
// A string that defines the SQL statement for creating a table
private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
    "main " +                       // Table's name
    "(" +                           // The columns in the table
    " _ID INTEGER PRIMARY KEY, " +
    " WORD TEXT"
    " FREQUENCY INTEGER " +
    " LOCALE TEXT )";
...
/**
 * Helper class that actually creates and manages the provider's underlying data repository.
 */
protected static final class MainDatabaseHelper extends SQLiteOpenHelper {

    /*
     * Instantiates an open helper for the provider's SQLite data repository
     * Do not do database creation and upgrade here.
     */
    MainDatabaseHelper(Context context) {
        super(context, DBNAME, null, 1);
    }

    /*
     * Creates the data repository. This is called when the provider attempts to open the
     * repository and SQLite reports that it doesn't exist.
     */
    public void onCreate(SQLiteDatabase db) {

        // Creates the main table
        db.execSQL(SQL_CREATE_MAIN);
    }
}

Реализация типов MIME для класса ContentProvider

В классе ContentProvider предусмотрены два метода для возврата типов MIME:

getType()
Один из необходимых методов, который требуется реализовать для каждого поставщика.
getStreamTypes()
Метод, который нужно реализовать в случае, если поставщик предоставляет файлы.

Типы MIME для таблиц

Метод getType() возвращает объект String в формате MIME, который описывает тип данных, возвращаемых аргументом URI контента. Аргумент Uri может выступать в качестве шаблона, а не в виде определенного URI; в этом случае необходимо возвратить тип данных, связанный с URI контента, который соответствует шаблону.

Для общих типов данных, таких как текст, HTML или JPEG, метод getType() должен возвращать стандартный тип MIME. Полный список стандартных типов представлен на веб-сайте IANA MIME Media Types.

Для URI контента, которые указывают на одну или несколько строк табличных данных, метод getType() должен возвращать тип MIME в формате MIME поставщика, который имеется в системе Android:

  • Часть типа: vnd
  • Часть подтипа:
    • Если шаблон URI предназначен для одной строки: android.cursor.item/
    • Если шаблон URI предназначен для нескольких строк: android.cursor.dir/
  • Часть поставщика: vnd.<name>.<type>

    Вы указываете <name> и <type>. Значение <name> должно быть уникальным глобально, а значение <type> должно быть уникальным для соответствующего шаблона URI. В качестве <name> рекомендуется использовать название вашей компании или часть названия пакета Android вашего приложения. В качестве <type> рекомендуется использовать строку, которая определяет связанную с URI таблицу.

Например, если центром поставщика является com.example.app.provider, который предоставляет таблицу table1, то тип MIME для нескольких строк в таблице table1 будет следующим:

vnd.android.cursor.dir/vnd.com.example.provider.table1

Для одной строки в таблице table1 тип MIME будет следующим:

vnd.android.cursor.item/vnd.com.example.provider.table1

Типы MIME для файлов

Если поставщик предоставляет файлы, необходимо реализовать метод getStreamTypes(). Этот метод возвращает массив String с типами MIME для файлов, возвращаемых поставщиком для заданного URI контента. Предлагаемые поставщиком типы следует сортировать с помощью аргумента фильтра типов MIME, чтобы возвращались только те типы MIME, которые необходимо обработать клиенту.

Например, рассмотрим поставщик, который предоставляет фотографии в виде файлов в форматах .jpg, .png и .gif. Если приложение вызывает метод ContentResolver.getStreamTypes() со строкой фильтра image/* (нечто вроде «изображения»), то метод ContentProvider.getStreamTypes() должен возвращать следующий массив:

{ "image/jpeg", "image/png", "image/gif"}

Если же приложению требуются только файлы .jpg, то оно вызывает метод ContentResolver.getStreamTypes() со строкой фильтра *\/jpeg; метод ContentProvider.getStreamTypes() при этом должен возвращать следующее:

{"image/jpeg"}

Если поставщик не предоставляет ни один из типов MIME, запрошенных в строке фильтра, то метод getStreamTypes() должен возвращать null.

Реализация класса-контракта

Класс-контракт представляет собой класс public final, в котором содержатся определения констант для URI, имен столбцов, типов MIME и других метаданных поставщика. Класс устанавливает контрактные отношения между поставщиком и другими приложениями путем обеспечения прямого доступа к поставщику даже в случае изменения фактических значений URI, имен столбцов и т. д.

Класс-контракт также полезен для разработчиков тем, что в нем содержатся мнемонические имена для его констант, благодаря чему снижается риск того, что разработчики воспользуются неправильными значениями для имен столбцов или URI. Поскольку это класс, он может содержать документацию Javadoc. Интегрированные среды разработки, такие как Eclipse, могут автоматически заполнять имена констант из класса-контракта и отображать Javadoc для констант.

У разработчиков нет доступа к файлу класса-контракта из вашего приложения, однако они могут статически скомпилировать класс-контракт в свое приложение из предоставленного вами файла .jar.

Примом класса-контракта может служить класс ContactsContract и его вложенные классы.

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

Разрешения и доступ ко всем компонентам системы Android подробно описаны в статье Безопасность и разрешения. Кроме того, в статье Хранилище данных представлены сведения о безопасности и разрешениях, применяемых к различным типам хранилища. Ниже приведен краткий обзор основных моментов.

  • По умолчанию файлы с данными хранятся во внутреннем хранилище устройства и доступны только вашему приложению и поставщику.
  • Создаваемые вами базы данных SQLiteDatabase также доступны только вашему приложению и поставщику.
  • Файлы с данными, которые вы сохраняете во внешнем хранилище, по умолчанию являются общедоступными, которые может считать любой пользователь. Вам не удастся использовать поставщик контента для ограничения доступа к файлам, которые хранятся во внешнем хранилище, поскольку приложения могут использовать другие вызовы API для их чтения или записи.
  • Вызовы метода для открытия или создания файлов или баз данных SQLite, находящихся во внутреннем хранилище на вашем устройстве, потенциально могут предоставить всем другим приложениям доступ как на запись, так и на чтение данных. Если вы используете внутренний файл или базу данных в качестве репозитория поставщика, выполнить чтение или запись данных в котором может любой пользователь, то разрешений, заданных в манифесте поставщика, будет явно недостаточно для защиты ваших данных. По умолчанию доступ к файлам и базам данных во внутреннем хранилище является закрытым, и вам не следует изменять параметры доступа к репозиторию вашего поставщика.

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

Реализация разрешений

Любое приложение может выполнять чтение данных в поставщике или записывать их, даже если соответствующие данные являются закрытыми, поскольку по умолчанию для поставщика не заданы разрешения. Чтобы изменить эти настройки, задайте разрешения для поставщика в файле манифеста с помощью атрибутов элемента <provider> или его дочерних элементов. Можно задать разрешения, которые применяются ко всему поставщику или только к определенным таблицам, либо даже только к определенным записям или всему дереву.

Для задания разрешений используется один или несколько элементов <permission> в файле манифеста. Чтобы разрешения были уникальными для поставщика, используйте области, аналогичные Java, для атрибута android:name. Например, присвойте разрешению на чтение имя com.example.app.provider.permission.READ_PROVIDER.

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

Единичное разрешение на чтение/запись на уровне поставщика
Единичное разрешение, которое управляет доступом как на чтение, так и на запись для всего поставщика, которое задается с помощью атрибута android:permission элемента <provider>.
Отдельное разрешение на чтение/запись на уровне поставщика
Разрешение на чтение и запись для всего поставщика. Такие разрешения задаются с помощью атрибутов android:readPermission и android:writePermission элемента <provider>. Они имеют преимущественную силу над разрешением, заданным с помощью атрибута android:permission.
Разрешение на уровне пути
Разрешение на чтение, запись или чтение/запись для URI контента в поставщике. Каждый URI, которым необходимо управлять, задается с помощью дочернего элемента <path-permission> элемента <provider>. Для каждого указываемого URI контента можно задать разрешение на чтение/запись, только чтение или только запись, либо все три разрешения. Разрешения на чтение и запись имеют преимущественную силу над разрешением на чтение/запись. Кроме того, разрешения на уровне пути имеют преимущественную силу над разрешениями на уровне поставщика.
Временное разрешение
Разрешения этого уровня предоставляют приложению временный доступ, даже если у приложения нет разрешений, которые обычно требуются. Функция временного доступа ограничивает набор разрешений, которые приложению необходимо запросить в своем манифесте. Если включены временные разрешения, единственными приложениями, которым требуются «постоянные» разрешения на работу с поставщиком, являются те, которые непрерывно получают доступ ко всем вашим данным.

Рассмотрим пример с разрешениями, которые необходимо реализовать для поставщика электронной почты и приложения, когда вам необходимо разрешить внешнему приложению для просмотра изображений отображать вложенные в письма фотографии из поставщика. Чтобы предоставить средству просмотра изображений требуемый доступ без запроса разрешений, задайте временные разрешения для URI контента фотографий. Спроектируйте ваше приложение для работы с электронной почтой таким образом, чтобы в случаях, когда пользователь желает отобразить фотографию, приложение отправляло намерение, в котором содержится URI контента фотографии и флаги разрешения для средства просмотра изображений. Затем средство просмотра изображений может отправить поставщику эл. почты запрос на получение фотографии, даже если у средства просмотра отсутствует обычное разрешение на чтение данных из поставщика.

Чтобы включить временные разрешения, задайте атрибут android:grantUriPermissions для элемента <provider>, либо добавьте один или несколько дочерних элементов <grant-uri-permission>в ваш элемент <provider>. Если вы используете временные разрешения, вам необходимо вызывать метод Context.revokeUriPermission() каждый раз, когда вы осуществляете удаленную поддержку URI контента из поставщика, а URI контента связан с временным разрешением.

Значение атрибута определяет, какая часть поставщика доступна. Если для атрибута задано значение true, система предоставит временные разрешения для всего поставщика, отменяя тем самым любые другие разрешения, которые требуются на уровне поставщика или на уровне пути.

Если для флага задано значение false, вам необходимо добавить дочерние элементы <grant-uri-permission> в свой элемент <provider>. Каждый дочерний элемент задает URI контента, для которых предоставляется временное разрешение.

Чтобы делегировать приложению временный доступ, в намерении должны быть указаны флаги FLAG_GRANT_READ_URI_PERMISSION или FLAG_GRANT_WRITE_URI_PERMISSION , либо оба флага. Эти флаги задаются с помощью метода setFlags().

Если атрибут android:grantUriPermissions отсутствует, предполагается, что его значение false.

Элемент <provider>

Как и компоненты Activity и Service, подкласс класса ContentProvider должен быть определен в файле манифеста приложения с помощью элемента <provider>. Ниже указана информация, которую система Android получает из этого элемента.

Центр поставщика (android:authorities)
Символические имена, которые идентифицируют весь поставщик в системе. Дополнительные сведения об этом атрибуте представлены в разделе Проектирование URI контента.
Название класса поставщика ( android:name )
Класс, который реализует класс ContentProvider. Дополнительные сведения об этом классе представлены в разделе Реализация класса ContentProvider.
Разрешения
Атрибуты, которые определяют разрешения, необходимые другим приложениям для доступа к данным в поставщике:

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

Атрибуты запуска и управления
Следующие атрибуты определяют порядок и время запуска поставщика системой Android, характеристики процесса поставщика, а также другие параметры выполнения:
  • android:enabled: флаг, позволяющий системе запускать поставщик;
  • android:exported: флаг, позволяющий другим приложениям использовать этот поставщик;
  • android:initOrder: порядок запуска поставщика (относительно других поставщиков в одном и том же процессе);
  • android:multiProcess: флаг, позволяющий системе запускать поставщик в том же процессе, что и вызывающий клиент;
  • android:process: название процесса, в котором запускается поставщик;
  • android:syncable: флаг, указывающий на то, что данные в поставщике следует синхронизировать с данными на сервере.

Полная документация по атрибутам представлена в статье, посвященной элементу <provider>.

Информационные атрибуты
Дополнительный значок и метка для поставщика:
  • android:icon: графический ресурс, содержащий значок для поставщика. Значок отображается рядом с меткой поставщика в списке приложений в разделе Настройки > Приложения > Все.
  • android:label: информационная метка с описанием поставщика или его данных, либо обоих описаний. Метка отображается в списке приложений в разделе Настройки > Приложения > Все.

Полная документация по атрибутам представлена в статье, посвященной элементу <provider>.

Намерения и доступ к данным

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

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

Обработка входящего намерения для изменения данных поставщика ничем не отличается от обработки других намерений. Дополнительные сведения об использовании намерений представлены в статье Объекты Intent и фильтры объектов Intent.