Content Provider: kiến trúc thành phần và nguyên lý hoạt động

Tác giả: IT Sectr Đã đăng: 2026-06-17 Thời gian đọc: 8 phút

Content Provider là một thành phần Android cung cấp giao diện thống nhất để truy cập dữ liệu giữa các ứng dụng. Nó trừu tượng hóa lưu trữ vật lý (SQLite, tệp, nguồn mạng) và cho phép trao đổi thông tin an toàn qua ContentResolver. Theo Android Developer Guide, 2026, Content Provider là một trong bốn thành phần chính của ứng dụng Android, cùng với Activity, Service và BroadcastReceiver. Nhiệm vụ của nó là làm cho dữ liệu có sẵn cho các ứng dụng khác với kiểm soát quyền đọc và ghi.

Những điểm chính

  • Content Provider là thành phần Android tiêu chuẩn để trao đổi dữ liệu giữa các ứng dụng qua giao diện ContentResolver.
  • URI (content://) xác định một bảng hoặc bản ghi và được sử dụng cho tất cả các thao tác CRUD với nhà cung cấp.
  • UriMatcher là lớp tiện ích phân tích các URI đến và xác định bảng hoặc hàng nào đang được yêu cầu.
  • Quyền đọc và ghi được xác định trong tệp kê khai và được yêu cầu từ người dùng khi cài đặt.
  • CursorLoader hoặc Room với Content Provider cho phép tải dữ liệu bất đồng bộ từ nhà cung cấp mà không chặn luồng UI.

Content Provider là gì?

Content Provider là thành phần Android quản lý quyền truy cập vào kho dữ liệu tập trung và cung cấp dữ liệu cho các ứng dụng khác qua giao diện hợp đồng thống nhất. Nó ẩn chi tiết triển khai lưu trữ: dữ liệu có thể được lưu trong SQLite, trên hệ thống tệp, trên đám mây hoặc là kết quả của yêu cầu mạng.

Android bao gồm các Content Providers tích hợp sẵn cho dữ liệu hệ thống — ContactsContract, MediaStore, CalendarContract, CallLog. Các ứng dụng bên thứ ba cũng có thể tạo nhà cung cấp riêng để trao đổi dữ liệu an toàn. Mỗi nhà cung cấp được đăng ký trong AndroidManifest.xml với authority — một chuỗi duy nhất tạo thành phần đầu tiên của URI.

Content Provider hoạt động như thế nào

Content Provider hoạt động theo mô hình máy khách-máy chủ. Nhà cung cấp đóng vai trò máy chủ triển khai sáu phương thức bắt buộc: query, insert, update, delete, getType và onCreate. Máy khách (ứng dụng khác) truy cập nhà cung cấp qua ContentResolver, nơi dịch các lời gọi thành các phương thức tương ứng của nhà cung cấp qua cơ chế IPC của Android.

URI và authority

Mỗi Content Provider được xác định bằng URI theo lược đồ content://. Ví dụ: content://com.example.app.provider/items. Phần đầu tiên authority (com.example.app.provider) được liên kết với lớp nhà cung cấp trong tệp kê khai. Đường dẫn /items trỏ đến một bảng, trong khi /items/5 trỏ đến một bản ghi cụ thể có ID=5.

Quy trình gọi

Khi một ứng dụng gọi ContentResolver.query(URI), Android kiểm tra quyền của gói gọi, tìm nhà cung cấp theo authority và khởi động tiến trình của nó nếu chưa chạy. Nhà cung cấp thực thi truy vấn và trả về Cursor — một đối tượng chứa kết quả và cho phép máy khách duyệt qua các bản ghi.

Các phương thức chính của Content Provider

Lớp ContentProvider yêu cầu triển khai sáu phương thức trừu tượng. Mỗi phương thức chấp nhận một URI và trả về kết quả tương ứng với loại thao tác. Hệ thống gọi các phương thức này từ bất kỳ tiến trình nào, vì vậy chúng phải an toàn cho luồng và không chặn thực thi lâu.

Phương thức query

Phương thức query chấp nhận URI, mảng cột projection, chuỗi selection với đối số và thứ tự sắp xếp. Nó trả về Cursor chứa dữ liệu. Trong triển khai, bạn cần phân tích URI bằng UriMatcher và thực thi truy vấn SQL tương ứng trong cơ sở dữ liệu.

Phương thức insert, update, delete

Các phương thức này sửa đổi dữ liệu trong kho. insert nhận ContentValues — cặp khóa-giá trị — và trả về URI của bản ghi mới. update và delete chấp nhận selection để lọc bản ghi và trả về số lượng hàng bị ảnh hưởng. Sau khi thay đổi dữ liệu, nhà cung cấp phải thông báo qua ContentResolver.notifyChange.

Phương thứcMục đíchTrả về
queryLấy dữ liệu theo URICursor hoặc null
insertThêm bản ghi mớiURI của bản ghi mới
updateCập nhật bản ghi hiện cóint (số hàng)
deleteXóa bản ghiint (số hàng)
getTypeLoại MIME cho URIString
onCreateKhởi tạo nhà cung cấpboolean

ContentResolver và URI trong Android

ContentResolver là điểm truy cập duy nhất để làm việc với tất cả Content Providers trong hệ thống. Ứng dụng máy khách không bao giờ gọi trực tiếp phương thức của nhà cung cấp — chỉ qua ContentResolver, mà Android lấy từ ngữ cảnh. Các thao tác CRUD trong ContentResolver có cùng tên như trong nhà cung cấp nhưng chấp nhận URI thay vì tham chiếu trực tiếp.

UriMatcher

Để phân tích các URI đến bên trong nhà cung cấp, UriMatcher được sử dụng. Nó cho phép ánh xạ URI sang mã số — ví dụ: URI content://authority/items cho mã 1, và content://authority/items/# cho mã 2. Điều này loại bỏ nhu cầu phân tích thủ công chuỗi URI trong mỗi phương thức.

kotlin
// Sử dụng ContentResolver để truy cập danh bạ
val uri = ContactsContract.Contacts.CONTENT_URI
val cursor = contentResolver.query(
    uri,
    arrayOf(ContactsContract.Contacts.DISPLAY_NAME),
    null, null, null
)
cursor?.use {
    while (it.moveToNext()) {
        val name = it.getString(it.getColumnIndexOrThrow(
            ContactsContract.Contacts.DISPLAY_NAME
        ))
        Log.d("Danh bạ", "Tên: $name")
    }
}

Cursor phải luôn được đóng sau khi sử dụng — trong ví dụ trên, hàm use (phần mở rộng Kotlin) thực hiện việc này. Nếu Cursor không được đóng, rò rỉ bộ nhớ xảy ra vì nó giữ tham chiếu đến dữ liệu trong nhóm Binder. Đối với các tình huống UI, hãy sử dụng CursorLoader hoặc Room với LiveData/Flow.

Ví dụ Content Provider trong Kotlin

Việc tạo Content Provider riêng bắt đầu bằng việc mở rộng lớp ContentProvider. Nhà cung cấp làm việc với cơ sở dữ liệu SQLite qua SQLiteOpenHelper và sử dụng UriMatcher để xác định loại truy vấn. Hãy xem xét triển khai tối thiểu để quản lý danh sách ghi chú.

Đăng ký trong tệp kê khai

Nhà cung cấp được đăng ký trong AndroidManifest.xml bên trong thẻ application. Thuộc tính authorities đặt định danh duy nhất và exported xác định liệu các ứng dụng khác có thể truy cập nhà cung cấp hay không. Nếu không có exported=true, nhà cung cấp chỉ khả dụng trong ứng dụng của bạn.

kotlin
// Ví dụ Content Provider cho ghi chú
class NotesProvider : ContentProvider() {

    companion object {
        const val AUTHORITY = "com.example.app.notes"
        const val NOTES_PATH = "notes"
        const val NOTES_URI = "content://$AUTHORITY/$NOTES_PATH"
        const val NOTES_ID = "content://$AUTHORITY/$NOTES_PATH/#"

        private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
            addURI(AUTHORITY, NOTES_PATH, 1)
            addURI(AUTHORITY, "$NOTES_PATH/#", 2)
        }
    }

    override fun query(uri: Uri, projection: Array<String>?,
        selection: String?, args: Array<String>?, sort: String?): Cursor? {
        return when (uriMatcher.match(uri)) {
            1 -> dbHelper.readableDatabase.query(TABLE_NOTES,
                projection, selection, args, null, null, sort)
            2 -> dbHelper.readableDatabase.query(TABLE_NOTES,
                projection, "_id=?", arrayOf(uri.lastPathSegment), null, null, null)
            else -> throw IllegalArgumentException("Unknown URI: $uri")
        }
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        val id = dbHelper.writableDatabase.insert(TABLE_NOTES, null, values)
        context?.contentResolver?.notifyChange(uri, null)
        return ContentUris.withAppendedId(uri, id)
    }

    override fun delete(uri: Uri, selection: String?, args: Array<String>?): Int {
        val count = dbHelper.writableDatabase.delete(TABLE_NOTES, selection, args)
        context?.contentResolver?.notifyChange(uri, null)
        return count
    }

    // getType, update, onCreate được bỏ qua cho ngắn gọn
}

Sau khi tạo lớp nhà cung cấp, nó phải được đăng ký trong tệp kê khai với các thuộc tính android:authorities và android:exported (true nếu nhà cung cấp công khai). Hệ thống tạo phiên bản nhà cung cấp khi truy cập lần đầu — điều này xảy ra trong luồng UI, vì vậy onCreate phải thực thi nhanh.

Bảo vệ dữ liệu qua quyền

Content Provider cho phép quản lý quyền truy cập dữ liệu ở hai cấp độ: quyền đọc và quyền ghi. Chúng được đặt trong tệp kê khai với các thuộc tính android:readPermission và android:writePermission. Nếu ứng dụng máy khách không có quyền tương ứng, hệ thống từ chối lời gọi với SecurityException.

Quyền cấp URI

Android hỗ trợ quyền tạm thời qua các cờ FLAG_GRANT_READ_URI_PERMISSION và FLAG_GRANT_WRITE_URI_PERMISSION. Điều này hữu ích khi một ứng dụng chuyển URI tệp cho ứng dụng khác qua Intent — bên nhận chỉ có quyền truy cập vào URI cụ thể đó trong thời gian giới hạn. Hệ thống thu hồi quyền tạm thời sau khi ứng dụng nhận kết thúc.

Đối với nhà cung cấp hệ thống, Android yêu cầu chỉ định quyền cụ thể trong tệp kê khai ứng dụng. Ví dụ: truy cập danh bạ yêu cầu READ_CONTACTS và truy cập lịch yêu cầu READ_CALENDAR. Bắt đầu từ Android 6, các quyền này được yêu cầu trong thời gian chạy, không phải khi cài đặt.

Câu hỏi thường gặp

Content Provider trong Android là gì?

Content Provider là thành phần Android cung cấp giao diện tiêu chuẩn để trao đổi dữ liệu giữa các ứng dụng qua ContentResolver. Nó trừu tượng hóa phương thức lưu trữ (SQLite, tệp, mạng) và đảm bảo truy cập dữ liệu an toàn với kiểm soát quyền đọc và ghi.

Authority trong Content Provider là gì?

Authority là chuỗi định danh duy nhất của nhà cung cấp được chỉ định trong AndroidManifest.xml. Nó tạo thành phần đầu tiên của URI content://authority/path và được hệ thống sử dụng để định tuyến các lời gọi ContentResolver đến đúng nhà cung cấp. Authority phải là duy nhất giữa tất cả các ứng dụng trên thiết bị.

UriMatcher hoạt động như thế nào trong Content Provider?

UriMatcher ánh xạ URI sang mã số. Bạn thêm mẫu qua addURI, sau đó gọi match để lấy mã cho URI đến. Điều này cho phép các phương thức query, insert, update, delete xác định bảng hoặc bản ghi nào được yêu cầu và thực hiện thao tác tương ứng trong cơ sở dữ liệu.

Có cần đóng Cursor sau khi làm việc với ContentResolver không?

Có, Cursor phải luôn được đóng sau khi sử dụng. Nếu Cursor không được đóng, rò rỉ bộ nhớ xảy ra vì nó giữ tham chiếu Binder đến dữ liệu của nhà cung cấp. Trong Kotlin, sử dụng hàm use để tự động đóng, và trong Java — try-with-resources hoặc cursor.close() trong khối finally.

Content Provider khác SQLiteDatabase như thế nào?

Content Provider là thành phần truy cập dữ liệu giữa các ứng dụng, trong khi SQLiteDatabase là cơ chế lưu trữ nội bộ cho một ứng dụng duy nhất. Content Provider cung cấp giao diện URI và kiểm soát quyền, còn SQLiteDatabase làm việc trực tiếp với cơ sở dữ liệu mà không có cơ chế bảo mật cấp hệ điều hành.

Tóm tắt

  • Content Provider là thành phần Android tiêu chuẩn để trao đổi dữ liệu an toàn giữa các ứng dụng qua giao diện ContentResolver thống nhất.
  • URI theo lược đồ content://authority/path/id xác định nhà cung cấp, bảng và bản ghi cụ thể — mỗi phân đoạn có mục đích riêng.
  • UriMatcher đơn giản hóa việc phân tích URI đến trong nhà cung cấp, loại bỏ phân tích chuỗi thủ công trong mỗi phương thức CRUD.
  • Nhà cung cấp được đăng ký trong AndroidManifest.xml với các thuộc tính authorities và exported — đây là cơ chế hệ thống mà không có nó nhà cung cấp sẽ không hoạt động.
  • ContentValues được truyền cho insert và update dưới dạng cặp khóa-giá trị, và Cursor được trả về từ query để duyệt qua kết quả.
  • readPermission và writePermission trong tệp kê khai kiểm soát quyền truy cập dữ liệu, trong khi FLAG_GRANT_URI_PERMISSION cung cấp quyền truy cập tạm thời vào một URI cụ thể.
  • Để tải dữ liệu bất đồng bộ từ Content Provider, hãy sử dụng CursorLoader, Room với ContentProvider hoặc LoaderManager — điều này ngăn chặn việc chặn luồng UI.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm