← Назад к вопросам
Приведи пример использования ContentProvider
2.0 Middle🔥 111 комментариев
#Android компоненты#Работа с данными
Комментарии (1)
🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Пример использования ContentProvider в Android
ContentProvider — это компонент Android, который инкапсулирует данные и предоставляет унифицированный интерфейс для доступа к ним другим приложениям через стандартизированный API. Это ключевой механизм для обмена данными между приложениями, работающий через URI-based запросы и Cursor-объекты.
Базовый пример: собственный провайдер контактов
Рассмотрим простейший пример — создание ContentProvider для управления списком задач (To-Do). Вот полная реализация:
1. Определение контракта данных
// Contract.kt
object TodoContract {
const val AUTHORITY = "com.example.todo.provider"
object TodoEntry : BaseColumns {
const val TABLE_NAME = "todos"
const val _ID = BaseColumns._ID
const val COLUMN_TITLE = "title"
const val COLUMN_COMPLETED = "completed"
// URI для доступа к данным
val CONTENT_URI = Uri.parse("content://$AUTHORITY/${TABLE_NAME}")
// MIME-типы
const val CONTENT_TYPE = "vnd.android.cursor.dir/vnd.${AUTHORITY}.todo"
const val CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.${AUTHORITY}.todo"
}
}
2. Реализация ContentProvider
// TodoProvider.kt
class TodoProvider : ContentProvider() {
private lateinit var dbHelper: TodoDbHelper
companion object {
private const val TODOS = 1
private const val TODO_ID = 2
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(TodoContract.AUTHORITY, TodoContract.TodoEntry.TABLE_NAME, TODOS)
addURI(TodoContract.AUTHORITY, "${TodoContract.TodoEntry.TABLE_NAME}/#", TODO_ID)
}
}
override fun onCreate(): Boolean {
dbHelper = TodoDbHelper(context!!)
return true
}
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? {
val db = dbHelper.readableDatabase
val cursor: Cursor
when (uriMatcher.match(uri)) {
TODOS -> {
cursor = db.query(
TodoContract.TodoEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
)
}
TODO_ID -> {
val id = ContentUris.parseId(uri)
cursor = db.query(
TodoContract.TodoEntry.TABLE_NAME,
projection,
"${TodoContract.TodoEntry._ID} = ?",
arrayOf(id.toString()),
null,
null,
sortOrder
)
}
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
cursor.setNotificationUri(context!!.contentResolver, uri)
return cursor
}
override fun insert(uri: Uri, values: ContentValues?): Uri {
val db = dbHelper.writableDatabase
val id = db.insert(TodoContract.TodoEntry.TABLE_NAME, null, values)
if (id > 0) {
val newUri = ContentUris.withAppendedId(TodoContract.TodoEntry.CONTENT_URI, id)
context!!.contentResolver.notifyChange(newUri, null)
return newUri
}
throw SQLException("Failed to insert row into $uri")
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
): Int {
val db = dbHelper.writableDatabase
val rowsUpdated: Int
when (uriMatcher.match(uri)) {
TODOS -> {
rowsUpdated = db.update(
TodoContract.TodoEntry.TABLE_NAME,
values,
selection,
selectionArgs
)
}
TODO_ID -> {
val id = ContentUris.parseId(uri)
rowsUpdated = db.update(
TodoContract.TodoEntry.TABLE_NAME,
values,
"${TodoContract.TodoEntry._ID} = ?",
arrayOf(id.toString())
)
}
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
if (rowsUpdated > 0) {
context!!.contentResolver.notifyChange(uri, null)
}
return rowsUpdated
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
val db = dbHelper.writableDatabase
val rowsDeleted: Int
when (uriMatcher.match(uri)) {
TODOS -> {
rowsDeleted = db.delete(
TodoContract.TodoEntry.TABLE_NAME,
selection,
selectionArgs
)
}
TODO_ID -> {
val id = ContentUris.parseId(uri)
rowsDeleted = db.delete(
TodoContract.TodoEntry.TABLE_NAME,
"${TodoContract.TodoEntry._ID} = ?",
arrayOf(id.toString())
)
}
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
if (rowsDeleted > 0) {
context!!.contentResolver.notifyChange(uri, null)
}
return rowsDeleted
}
override fun getType(uri: Uri): String {
return when (uriMatcher.match(uri)) {
TODOS -> TodoContract.TodoEntry.CONTENT_TYPE
TODO_ID -> TodoContract.TodoEntry.CONTENT_ITEM_TYPE
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
}
}
3. Вспомогательные классы
// TodoDbHelper.kt
class TodoDbHelper(context: Context) : SQLiteOpenHelper(
context,
"todo.db",
null,
1
) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("""
CREATE TABLE ${TodoContract.TodoEntry.TABLE_NAME} (
${TodoContract.TodoEntry._ID} INTEGER PRIMARY KEY AUTOINCREMENT,
${TodoContract.TodoEntry.COLUMN_TITLE} TEXT NOT NULL,
${TodoContract.TodoEntry.COLUMN_COMPLETED} INTEGER DEFAULT 0
)
""".trimIndent())
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS ${TodoContract.TodoEntry.TABLE_NAME}")
onCreate(db)
}
}
4. Регистрация в AndroidManifest.xml
<provider
android:name=".TodoProvider"
android:authorities="com.example.todo.provider"
android:exported="true" <!-- или false, если только для внутреннего использования -->
android:readPermission="com.example.todo.READ_PERMISSION"
android:writePermission="com.example.todo.WRITE_PERMISSION" />
5. Использование провайдера из другого приложения
// ClientActivity.kt
class ClientActivity : AppCompatActivity() {
private val TODO_URI = Uri.parse("content://com.example.todo.provider/todos")
fun fetchTodos() {
// Запрос данных
val cursor = contentResolver.query(
TODO_URI,
arrayOf("_id", "title", "completed"),
null,
null,
null
)
cursor?.use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("_id"))
val title = it.getString(it.getColumnIndexOrThrow("title"))
val completed = it.getInt(it.getColumnIndexOrThrow("completed")) == 1
Log.d("TodoProvider", "ID: $id, Title: $title, Completed: $completed")
}
}
}
fun addTodo(title: String) {
// Вставка новой записи
val values = ContentValues().apply {
put("title", title)
put("completed", false)
}
val uri = contentResolver.insert(TODO_URI, values)
Log.d("TodoProvider", "Inserted todo at: $uri")
}
fun updateTodo(id: Long, completed: Boolean) {
// Обновление записи
val values = ContentValues().apply {
put("completed", if (completed) 1 else 0)
}
val selection = "_id = ?"
val selectionArgs = arrayOf(id.toString())
val rowsUpdated = contentResolver.update(
ContentUris.withAppendedId(TODO_URI, id),
values,
selection,
selectionArgs
)
Log.d("TodoProvider", "Updated $rowsUpdated rows")
}
}
Ключевые аспекты реализации:
- UriMatcher — для маршрутизации URI запросов
- Уведомления об изменениях — через
ContentResolver.notifyChange()для автоматического обновления UI в клиентах - Потокобезопасность — каждый вызов метода работает с отдельным соединением к БД
- MIME-типы — обязательны для поддержки системных функций (например, выбора файлов)
Практические сценарии использования:
- Синхронизация данных — через
SyncAdapterс использованием ContentProvider - Виджеты приложений — доступ к данным без запуска основного приложения
- Поиск по приложению — интеграция с системным поиском Android
- Обмен файлами — через специальные провайдеры файлов (
FileProvider)
ContentProvider остаётся фундаментальным компонентом Android для безопасного и структурированного обмена данными между приложениями, несмотря на появление альтернатив типа Room с поддержкой ContentProvider через @RawQuery.