← Назад к вопросам

Приведи пример использования 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.

Приведи пример использования ContentProvider | PrepBro