Thông tin cơ bản về trình cung cấp nội dung

Trình cung cấp nội dung quản lý quyền truy cập vào kho lưu trữ dữ liệu trung tâm. Một nhà cung cấp là một phần của ứng dụng Android, thường cung cấp giao diện người dùng riêng để làm việc với dữ liệu. Tuy nhiên, trình cung cấp nội dung chủ yếu được các bên khác các ứng dụng này truy cập trình cung cấp bằng cách sử dụng đối tượng ứng dụng nhà cung cấp. Các nhà cung cấp và cung cấp một giao diện chuẩn, nhất quán cho dữ liệu, đồng thời xử lý giao tiếp liên quy trình và bảo mật truy cập dữ liệu.

Thông thường, bạn sẽ làm việc với nhà cung cấp nội dung ở một trong hai tình huống: triển khai để truy cập trình cung cấp nội dung hiện có trong một ứng dụng khác hoặc tạo trình cung cấp nội dung mới trong ứng dụng của bạn để chia sẻ dữ liệu với các ứng dụng khác.

Trang này bao gồm những kiến thức cơ bản về cách hợp tác với các nhà cung cấp nội dung hiện tại. Để tìm hiểu về cách triển khai nhà cung cấp nội dung trong ứng dụng của riêng bạn, hãy xem Tạo một trình cung cấp nội dung.

Chủ đề này mô tả những nội dung sau:

  • Cách hoạt động của trình cung cấp nội dung.
  • API bạn sử dụng để truy xuất dữ liệu từ trình cung cấp nội dung.
  • API mà bạn sử dụng để chèn, cập nhật hoặc xoá dữ liệu trong một trình cung cấp nội dung.
  • Các tính năng API khác hỗ trợ hợp tác với nhà cung cấp.

Tổng quan

Trình cung cấp nội dung trình bày dữ liệu cho các ứng dụng bên ngoài dưới dạng một hoặc nhiều bảng tương tự như các bảng trong cơ sở dữ liệu quan hệ. Hàng đại diện cho một thực thể thuộc loại nào đó dữ liệu mà nhà cung cấp thu thập và mỗi cột trong hàng đại diện cho một phần dữ liệu đã thu thập cho một đối tượng nào đó.

Nhà cung cấp nội dung điều phối quyền truy cập vào lớp lưu trữ dữ liệu trong ứng dụng của bạn cho một số lượng API và thành phần khác nhau. Như minh hoạ trong Hình 1, các chỉ số này bao gồm:

  • Chia sẻ quyền truy cập vào dữ liệu ứng dụng của bạn với các ứng dụng khác
  • Gửi dữ liệu đến một tiện ích
  • Trả về đề xuất tìm kiếm tùy chỉnh cho ứng dụng của bạn thông qua tìm kiếm khung bằng SearchRecentSuggestionsProvider
  • Đồng bộ hoá dữ liệu ứng dụng với máy chủ của bạn bằng cách triển khai AbstractThreadedSyncAdapter
  • Tải dữ liệu trong giao diện người dùng bằng CursorLoader
Mối quan hệ giữa trình cung cấp nội dung và các thành phần khác.

Hình 1. Mối quan hệ giữa trình cung cấp nội dung và các thành phần khác.

Truy cập vào một nhà cung cấp

Khi muốn truy cập dữ liệu trong một trình cung cấp nội dung, bạn cần sử dụng Đối tượng ContentResolver trong ứng dụng của bạn Context để giao tiếp với nhà cung cấp trong vai trò một khách hàng. Chiến lược phát hành đĩa đơn Đối tượng ContentResolver giao tiếp với đối tượng ứng dụng nhà cung cấp, một đối tượng thực thể của một lớp triển khai ContentProvider.

Nhà cung cấp đối tượng nhận yêu cầu dữ liệu từ máy khách, thực hiện hành động được yêu cầu và trả về kết quả. Đối tượng này có các phương thức gọi các phương thức có tên giống hệt nhau trong đối tượng trình cung cấp, một thực thể của một trong các lớp con cụ thể của ContentProvider. Chiến lược phát hành đĩa đơn Phương thức ContentResolver cung cấp thông tin cơ bản "CRUD" Các chức năng (tạo, truy xuất, cập nhật và xoá) của bộ nhớ liên tục.

Một mẫu chung để truy cập ContentProvider từ giao diện người dùng là sử dụng CursorLoader để chạy truy vấn không đồng bộ trong nền. Chiến lược phát hành đĩa đơn Activity hoặc Fragment trong giao diện người dùng sẽ gọi một CursorLoader cho truy vấn, từ đó nhận được ContentProvider bằng ContentResolver.

Điều này cho phép người dùng tiếp tục truy cập vào giao diện người dùng trong khi truy vấn đang chạy. Chiến dịch này liên quan đến sự tương tác của một số đối tượng khác nhau cũng như cơ chế cơ chế lưu trữ, như minh hoạ trong Hình 2.

Tương tác giữa ContentProvider, các lớp khác và bộ nhớ.

Hình 2. Hoạt động tương tác giữa ContentProvider, các lớp khác và bộ nhớ.

Lưu ý: Để truy cập vào một nhà cung cấp, ứng dụng của bạn thường phải yêu cầu một số thông tin trong tệp kê khai. Mẫu phát triển này được mô tả chi tiết hơn trong Quyền của trình cung cấp nội dung.

Một trong những nhà cung cấp tích hợp sẵn trong nền tảng Android là Nhà cung cấp từ điển người dùng. sẽ lưu trữ những từ không theo chuẩn mà người dùng muốn giữ lại. Bảng 1 minh hoạ những gì dữ liệu có thể trông giống như trong bảng của nhà cung cấp này:

Bảng 1: Bảng từ điển người dùng mẫu.

word id ứng dụng đăng video ngôn ngữ _Mã
mapreduce người dùng1 100 vi_VN 1
precompiler người dùng 14 200 fr_FR 2
applet người dùng2 225 fr_CA 3
const người dùng1 255 pt_BR 4
int người dùng 5 100 vi_VN 5

Trong bảng 1, mỗi hàng đại diện cho một phiên bản của một từ không có trong từ điển chuẩn. Mỗi cột đại diện cho một phần dữ liệu của từ đó, chẳng hạn như ngôn ngữ nơi gặp mã đó lần đầu. Tiêu đề cột là tên cột được lưu trữ trong cho nhà cung cấp. Vì vậy, để tham chiếu đến ngôn ngữ của một hàng, bạn sẽ tham chiếu đến cột locale của hàng đó. Cho nhà cung cấp này, cột _ID đóng vai trò là cột khoá chính mà nhà cung cấp tự động duy trì.

Để nhận danh sách các từ và ngôn ngữ của chúng từ Nhà cung cấp từ điển người dùng, bạn gọi ContentResolver.query(). Phương thức query() gọi phương thức ContentProvider.query() được xác định bởi Nhà cung cấp từ điển người dùng. Các dòng mã sau đây cho thấy Cuộc gọi ContentResolver.query():

Kotlin

// Queries the UserDictionary and returns results
cursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
        projection,                        // The columns to return for each row
        selectionClause,                   // Selection criteria
        selectionArgs.toTypedArray(),      // Selection criteria
        sortOrder                          // The sort order for the returned rows
)

Java

// Queries the UserDictionary and returns results
cursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    projection,                        // The columns to return for each row
    selectionClause,                   // Selection criteria
    selectionArgs,                     // Selection criteria
    sortOrder);                        // The sort order for the returned rows

Bảng 2 cho thấy cách các đối số để query(Uri,projection,selection,selectionArgs,sortOrder) khớp với câu lệnh SELECT SQL:

Bảng 2: query() so với truy vấn SQL.

Đối số query() SELECT từ khóa/thông số Ghi chú
Uri FROM table_name Uri liên kết với bảng trong trình cung cấp có tên là table_name.
projection col,col,col,... projection là một mảng cột được đưa vào mỗi hàng đã truy xuất.
selection WHERE col = value selection chỉ định tiêu chí để chọn hàng.
selectionArgs Không có thông tin tương đương chính xác. Đối số lựa chọn thay thế phần giữ chỗ ? trong phần mệnh đề lựa chọn.
sortOrder ORDER BY col,col,... sortOrder chỉ định thứ tự các hàng sẽ xuất hiện trong phần tử được trả về Cursor

URI nội dung

URI nội dung là một URI xác định dữ liệu trong một trình cung cấp. URI nội dung bao gồm tên tượng trưng của toàn bộ nhà cung cấp – cơ quan của nhà cung cấp đó – và tên trỏ đến một bảng – một đường dẫn. Khi bạn gọi một phương thức ứng dụng khách để truy cập vào bảng trong một nhà cung cấp, URI nội dung cho bảng là một trong các đối số.

Trong các dòng mã trước, hằng số CONTENT_URI chứa URI nội dung của bảng Words của Nhà cung cấp từ điển người dùng. ContentResolver đối tượng phân tích cú pháp quyền của URI và sử dụng quyền này để giải quyết trình cung cấp bằng cách so sánh đơn vị có thẩm quyền với một bảng hệ thống gồm các nhà cung cấp đã biết. Chiến lược phát hành đĩa đơn Sau đó, ContentResolver có thể gửi đối số truy vấn đến đúng Google Cloud.

ContentProvider sử dụng phần đường dẫn của URI nội dung để chọn bảng để truy cập. Nhà cung cấp thường có một đường dẫn cho mỗi bảng mà nó hiển thị.

Trong các dòng mã trước, URI đầy đủ cho bảng Words là:

content://user_dictionary/words
  • Chuỗi content://giản đồ và luôn hiện diện và xác định đây là URI nội dung.
  • Chuỗi user_dictionary là quyền của trình cung cấp.
  • Chuỗi words là đường dẫn của bảng.

Nhiều nhà cung cấp cho phép bạn truy cập vào một hàng trong bảng bằng cách thêm giá trị mã nhận dạng vào cuối URI. Ví dụ: để truy xuất một hàng có _ID4 từ Nhà cung cấp từ điển người dùng, bạn có thể sử dụng URI nội dung này:

Kotlin

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

Java

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

Bạn thường sử dụng giá trị mã nhận dạng khi truy xuất một tập hợp các hàng và sau đó muốn cập nhật hoặc xoá một trong số đó.

Lưu ý: Các lớp UriUri.Builder chứa các phương thức tiện lợi để tạo đối tượng URI được định dạng đúng từ các chuỗi. Chiến lược phát hành đĩa đơn Lớp ContentUris chứa các phương thức tiện lợi để thêm giá trị mã nhận dạng vào URI. Đoạn mã trước sử dụng withAppendedId() để nối mã nhận dạng vào URI nội dung của Nhà cung cấp từ điển người dùng.

Truy xuất dữ liệu từ nhà cung cấp

Phần này mô tả cách truy xuất dữ liệu từ nhà cung cấp, sử dụng Nhà cung cấp từ điển người dùng làm ví dụ.

Để cho rõ ràng, các đoạn mã trong phần này gọi ContentResolver.query() trên luồng giao diện người dùng. Trong mã thực tế, tuy nhiên, truy vấn không đồng bộ trên một luồng riêng. Bạn có thể sử dụng lớp CursorLoader, được mô tả chi tiết hơn trong Hướng dẫn về trình tải. Ngoài ra, các dòng mã chỉ là đoạn mã. Chúng không hiển thị đầy đủ .

Để truy xuất dữ liệu từ một nhà cung cấp, hãy làm theo các bước cơ bản sau:

  1. Hãy yêu cầu nhà cung cấp cấp quyền đọc.
  2. Xác định mã sẽ gửi truy vấn đến trình cung cấp.

Yêu cầu cấp quyền đọc

Để truy xuất dữ liệu từ một nhà cung cấp, ứng dụng của bạn cần có quyền đọc đối với Google Cloud. Bạn không thể yêu cầu quyền này trong thời gian chạy. Thay vào đó, bạn phải chỉ định rằng bạn cần quyền này trong tệp kê khai, bằng cách sử dụng <uses-permission> và tên quyền chính xác được xác định bởi Google Cloud.

Khi chỉ định phần tử này trong tệp kê khai, bạn sẽ yêu cầu phần tử này cho ứng dụng của bạn. Khi người dùng cài đặt ứng dụng của bạn, họ ngầm cấp cho yêu cầu này.

Để tìm tên chính xác của quyền truy cập đọc của nhà cung cấp mà bạn đang sử dụng, dưới dạng tên cho các quyền truy cập khác mà nhà cung cấp sử dụng, hãy xem tài liệu.

Vai trò của quyền trong việc truy cập vào nhà cung cấp được mô tả chi tiết hơn trong Quyền của trình cung cấp nội dung.

Trình cung cấp từ điển người dùng xác định quyền android.permission.READ_USER_DICTIONARY trong tệp kê khai, vì vậy, ứng dụng muốn đọc từ nhà cung cấp phải yêu cầu quyền này.

Tạo truy vấn

Bước tiếp theo trong việc truy xuất dữ liệu từ nhà cung cấp là tạo một truy vấn. Đoạn mã sau xác định một số biến để truy cập vào Nhà cung cấp từ điển người dùng:

Kotlin

// A "projection" defines the columns that are returned for each row
private val mProjection: Array<String> = arrayOf(
        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
private var selectionClause: String? = null

// Declares an array to contain selection arguments
private lateinit var selectionArgs: Array<String>

Java

// A "projection" defines the columns that are 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 selectionClause = null;

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

Đoạn mã tiếp theo cho biết cách sử dụng ContentResolver.query(), sử dụng Từ điển người dùng Nhà cung cấp là ví dụ. Truy vấn ứng dụng của nhà cung cấp cũng tương tự như truy vấn SQL và chứa tập hợp cột cần trả về, một tập hợp các tiêu chí chọn và thứ tự sắp xếp.

Tập hợp các cột mà truy vấn trả về được gọi là phép chiếu và biến là mProjection.

Biểu thức chỉ định các hàng cần truy xuất được chia thành mệnh đề lựa chọn và đối số lựa chọn. Mệnh đề lựa chọn là sự kết hợp giữa biểu thức logic và boolean, tên cột và giá trị cột. Biến này là mSelectionClause. Nếu bạn chỉ định tham số có thể thay thế ? thay vì một giá trị, phương thức truy vấn sẽ truy xuất giá trị từ mảng đối số lựa chọn, chính là biến mSelectionArgs.

Trong đoạn mã tiếp theo, nếu người dùng không nhập từ, mệnh đề lựa chọn sẽ được đặt thành null và truy vấn trả về tất cả các từ có trong trình cung cấp. Nếu người dùng nhập một từ, mệnh đề lựa chọn được đặt thành UserDictionary.Words.WORD + " = ?" và phần tử đầu tiên của mảng đối số lựa chọn được đặt thành từ mà người dùng nhập.

Kotlin

/*
 * This declares a String array to contain the selection arguments.
 */
private lateinit var selectionArgs: Array<String>

// Gets a word from the UI
searchString = searchWord.text.toString()

// Insert code here to check for invalid or malicious input

// If the word is the empty string, gets everything
selectionArgs = searchString?.takeIf { it.isNotEmpty() }?.let {
    selectionClause = "${UserDictionary.Words.WORD} = ?"
    arrayOf(it)
} ?: run {
    selectionClause = null
    emptyArray<String>()
}

// Does a query against the table and returns a Cursor object
mCursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI, // The content URI of the words table
        projection,                       // The columns to return for each row
        selectionClause,                  // Either null or the word the user entered
        selectionArgs,                    // Either empty or the string the user entered
        sortOrder                         // The sort order for the returned rows
)

// Some providers return null if an error occurs, others throw an exception
when (mCursor?.count) {
    null -> {
        /*
         * Insert code here to handle the error. Be sure not to use the cursor!
         * You might want to call android.util.Log.e() to log this error.
         */
    }
    0 -> {
        /*
         * Insert code here to notify the user that the search is unsuccessful. This isn't
         * necessarily an error. You might 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
    }
}

Java

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

// Gets a word from the UI
searchString = searchWord.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(searchString)) {
    // Setting the selection clause to null returns all words
    selectionClause = null;
    selectionArgs[0] = "";

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

    // Moves the user's input string to the selection arguments
    selectionArgs[0] = searchString;

}

// 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
    projection,                       // The columns to return for each row
    selectionClause,                  // Either null or the word the user entered
    selectionArgs,                    // Either empty or the string the user entered
    sortOrder);                       // 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 can
     * 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 is unsuccessful. This isn't necessarily
     * an error. You can 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

}

Truy vấn này tương tự như câu lệnh SQL sau:

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

Trong câu lệnh SQL này, tên cột thực tế sẽ được dùng thay cho hằng số lớp hợp đồng.

Bảo vệ khỏi dữ liệu đầu vào độc hại

Nếu dữ liệu do trình cung cấp nội dung quản lý nằm trong cơ sở dữ liệu SQL, bao gồm cả dữ liệu không đáng tin cậy bên ngoài vào câu lệnh SQL thô có thể dẫn đến việc chèn SQL.

Hãy xem xét mệnh đề lựa chọn sau:

Kotlin

// Constructs a selection clause by concatenating the user's input to the column name
var selectionClause = "var = $mUserInput"

Java

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

Nếu làm như vậy, bạn sẽ cho phép người dùng ghép nối SQL độc hại vào câu lệnh SQL của bạn. Ví dụ: người dùng có thể nhập "nothing; BẢNG THẤP *;" cho mUserInput, tức là cho kết quả trong mệnh đề lựa chọn var = nothing; DROP TABLE *;.

Vì Mệnh đề lựa chọn được coi là câu lệnh SQL, điều này có thể khiến trình cung cấp xoá tất cả các bảng trong cơ sở dữ liệu SQLite cơ bản, trừ phi trình cung cấp được thiết lập để nắm bắt Chèn SQL.

Để tránh vấn đề này, hãy sử dụng mệnh đề lựa chọn sử dụng ? làm thành phần có thể thay thế và một mảng các đối số lựa chọn riêng biệt. Bằng cách này, thông tin đầu vào của người dùng được liên kết trực tiếp với truy vấn thay vì được hiểu như một phần của câu lệnh SQL. Vì không được xem là SQL nên hoạt động đầu vào của người dùng không thể chèn SQL độc hại. Thay vì sử dụng nối để bao gồm hoạt động đầu vào của người dùng, hãy sử dụng mệnh đề lựa chọn sau:

Kotlin

// Constructs a selection clause with a replaceable parameter
var selectionClause = "var = ?"

Java

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

Thiết lập mảng các đối số lựa chọn như sau:

Kotlin

// Defines a mutable list to contain the selection arguments
var selectionArgs: MutableList<String> = mutableListOf()

Java

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

Đặt một giá trị vào mảng đối số lựa chọn như sau:

Kotlin

// Adds the user's input to the selection argument
selectionArgs += userInput

Java

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

Mệnh đề lựa chọn sử dụng ? làm tham số có thể thay thế và một mảng Mảng đối số lựa chọn là cách ưu tiên để chỉ định lựa chọn, ngay cả khi đối số cung cấp không phải là dựa trên cơ sở dữ liệu SQL.

Hiển thị kết quả truy vấn

Phương thức ứng dụng ContentResolver.query() luôn trả về một Cursor chứa các cột được chỉ định bởi phép chiếu cho các hàng phù hợp với tiêu chí lựa chọn của truy vấn. Đáp Đối tượng Cursor cung cấp quyền đọc ngẫu nhiên vào các hàng và cột đối tượng đó chứa.

Khi sử dụng các phương thức Cursor, bạn có thể lặp lại các hàng trong phần tử kết quả, xác định loại dữ liệu của mỗi cột, lấy dữ liệu ra khỏi cột và tìm hiểu các dữ liệu khác thuộc tính của kết quả.

Một số quá trình triển khai Cursor tự động cập nhật đối tượng khi dữ liệu của trình cung cấp thay đổi, các phương thức kích hoạt trong đối tượng tiếp nhận dữ liệu khi Cursor thay đổi hoặc cả hai.

Lưu ý: Nhà cung cấp có thể hạn chế quyền truy cập vào các cột tuỳ theo bản chất của tạo truy vấn. Ví dụ: Trình cung cấp danh bạ hạn chế quyền truy cập của một số cột để các bộ điều hợp đồng bộ hoá để không trả về chúng cho một hoạt động hoặc dịch vụ.

Nếu không có hàng nào khớp với tiêu chí lựa chọn, thì trình cung cấp sẽ trả về đối tượng Cursor Cursor.getCount() là 0 – tức là con trỏ trống.

Nếu xảy ra lỗi nội bộ, kết quả của truy vấn phụ thuộc vào nhà cung cấp cụ thể. Có thể trả về null hoặc có thể gửi một Exception.

Cursor là một danh sách các hàng nên một cách hay để hiển thị nội dung của Cursor là liên kết nó với một ListView bằng SimpleCursorAdapter.

Đoạn mã sau đây sẽ tiếp tục mã của đoạn mã trước đó. Chiến dịch này tạo ra Đối tượng SimpleCursorAdapter chứa Cursor được truy xuất bởi truy vấn và đặt đối tượng này làm bộ chuyển đổi cho ListView.

Kotlin

// Defines a list of columns to retrieve from the Cursor and load into an output row
val wordListColumns : Array<String> = arrayOf(
        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 receive the Cursor columns for each row
val wordListItems = intArrayOf(R.id.dictWord, R.id.locale)

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

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter)

Java

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] wordListColumns =
{
    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 receive the Cursor columns for each row
int[] wordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
cursorAdapter = 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
    wordListColumns,                       // A string array of column names in the cursor
    wordListItems,                         // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter);

Lưu ý: Để sao lưu ListView bằng Cursor thì con trỏ phải chứa cột có tên _ID. Do đó, truy vấn đã hiển thị trước đó sẽ truy xuất cột _ID cho thuộc tính Bảng Words, mặc dù ListView không hiển thị bảng đó. Quy định hạn chế này cũng giải thích lý do hầu hết các nhà cung cấp đều có một cột _ID cho mỗi bảng của họ.

Lấy dữ liệu từ kết quả truy vấn

Ngoài việc hiển thị kết quả truy vấn, bạn có thể sử dụng các kết quả này cho các tác vụ khác. Cho ví dụ: bạn có thể truy xuất cách viết từ Nhà cung cấp từ điển người dùng rồi tra cứu trong các nhà cung cấp khác. Để thực hiện việc này, bạn lặp lại các hàng trong Cursor, như trong ví dụ sau:

Kotlin

/*
* Only executes if the cursor is valid. The User Dictionary Provider returns null if
* an internal error occurs. Other providers might throw an Exception instead of returning null.
*/
mCursor?.apply {
    // Determine the column index of the column named "word"
    val index: Int = getColumnIndex(UserDictionary.Words.WORD)

    /*
     * 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 get an
     * exception.
     */
    while (moveToNext()) {
        // Gets the value from the column
        newWord = getString(index)

        // Insert code here to process the retrieved word
        ...
        // End of while loop
    }
}

Java

// 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 might 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 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
}

Quá trình triển khai Cursor chứa một số lệnh "get" phương thức cho truy xuất các loại dữ liệu khác nhau từ đối tượng. Ví dụ: đoạn mã trước sử dụng getString(). Chúng cũng có Phương thức getType() trả về một giá trị cho biết loại dữ liệu của cột.

Quyền của trình cung cấp nội dung

Ứng dụng của nhà cung cấp có thể chỉ định các quyền mà các ứng dụng khác phải có truy cập vào dữ liệu của nhà cung cấp. Các quyền này cho người dùng biết dữ liệu nào một ứng dụng cố gắng truy cập. Dựa trên yêu cầu của nhà cung cấp, các ứng dụng khác yêu cầu các quyền họ cần để truy cập nhà cung cấp. Người dùng cuối sẽ thấy khi cài đặt ứng dụng.

Nếu ứng dụng của nhà cung cấp không chỉ định bất kỳ quyền nào thì các ứng dụng khác sẽ không có quyền truy cập vào dữ liệu của nhà cung cấp, trừ phi nhà cung cấp được xuất. Ngoài ra, các thành phần trong ứng dụng của nhà cung cấp luôn có quyền đọc và ghi đầy đủ, bất kể quyền đã chỉ định.

Nhà cung cấp từ điển người dùng yêu cầu Quyền của android.permission.READ_USER_DICTIONARY để truy xuất dữ liệu qua đó. Nhà cung cấp có một android.permission.WRITE_USER_DICTIONARY riêng quyền chèn, cập nhật hoặc xoá dữ liệu.

Để có được các quyền cần thiết để truy cập vào một nhà cung cấp, một ứng dụng sẽ yêu cầu họ bằng một <uses-permission> trong tệp kê khai. Khi Android Package Manager (Trình quản lý gói Android) cài đặt ứng dụng, người dùng phải phê duyệt tất cả các quyền mà ứng dụng yêu cầu. Nếu người dùng phê duyệt, Trình quản lý gói sẽ tiếp tục cài đặt. Nếu người dùng không phê duyệt, Trình quản lý gói hãy dừng cài đặt.

Mẫu sau đây <uses-permission> phần tử yêu cầu quyền đọc đối với Nhà cung cấp từ điển người dùng:

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

Tác động của các quyền đối với quyền truy cập của nhà cung cấp được giải thích chi tiết hơn trong Mẹo bảo mật.

Chèn, cập nhật và xoá dữ liệu

Giống như cách truy xuất dữ liệu từ một nhà cung cấp, bạn cũng sử dụng tương tác giữa ứng dụng của nhà cung cấp và ContentProvider của nhà cung cấp để sửa đổi dữ liệu. Bạn gọi một phương thức của ContentResolver với các đối số được truyền đến phương thức tương ứng của ContentProvider. Nhà cung cấp ứng dụng khách tự động xử lý vấn đề bảo mật và giao tiếp liên quy trình.

Chèn dữ liệu

Để chèn dữ liệu vào một nhà cung cấp, bạn gọi hàm ContentResolver.insert() . Phương thức này sẽ chèn một hàng mới vào trình cung cấp và trả về một URI nội dung cho hàng đó. Đoạn mã sau đây cho biết cách chèn một từ mới vào Trình cung cấp từ điển người dùng:

Kotlin

// Defines a new Uri object that receives the result of the insertion
lateinit var newUri: Uri
...
// Defines an object to contain the new values to insert
val newValues = ContentValues().apply {
    /*
     * Sets the values of each column and inserts the word. The arguments to the "put"
     * method are "column name" and "value".
     */
    put(UserDictionary.Words.APP_ID, "example.user")
    put(UserDictionary.Words.LOCALE, "en_US")
    put(UserDictionary.Words.WORD, "insert")
    put(UserDictionary.Words.FREQUENCY, "100")

}

newUri = contentResolver.insert(
        UserDictionary.Words.CONTENT_URI,   // The UserDictionary content URI
        newValues                           // The values to insert
)

Java

// Defines a new Uri object that receives the result of the insertion
Uri newUri;
...
// Defines an object to contain the new values to insert
ContentValues newValues = new ContentValues();

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

newUri = getContentResolver().insert(
    UserDictionary.Words.CONTENT_URI,   // The UserDictionary content URI
    newValues                           // The values to insert
);

Dữ liệu cho hàng mới sẽ chuyển vào một đối tượng ContentValues duy nhất. Điều này có dạng tương tự như con trỏ một hàng. Các cột trong đối tượng này không cần phải có cùng một loại dữ liệu và nếu bạn không muốn chỉ định một giá trị, bạn có thể đặt cột đến null bằng ContentValues.putNull().

Đoạn mã trước không thêm cột _ID vì cột này vẫn được duy trì tự động. Nhà cung cấp này chỉ định một giá trị duy nhất là _ID cho mỗi hàng đã thêm. Nhà cung cấp thường sử dụng giá trị này làm khoá chính của bảng.

URI nội dung được trả về trong newUri xác định hàng mới được thêm chứa dưới định dạng sau:

content://user_dictionary/words/<id_value>

<id_value> là nội dung của _ID cho hàng mới. Hầu hết các nhà cung cấp đều có thể tự động phát hiện dạng URI nội dung này, sau đó thực hiện yêu cầu trên hàng cụ thể đó.

Để nhận giá trị của _ID từ Uri được trả về, hãy gọi ContentUris.parseId().

Cập nhật dữ liệu

Để cập nhật một hàng, bạn sử dụng đối tượng ContentValues với thuộc tính các giá trị, giống như cách bạn thực hiện với tiêu chí chèn và lựa chọn, như cách bạn thực hiện với truy vấn. Phương thức ứng dụng bạn sử dụng là ContentResolver.update(). Bạn chỉ cần thêm vào đối tượng ContentValues cho các cột mà bạn đang cập nhật. Nếu bạn muốn xoá nội dung của cột, hãy đặt giá trị thành null.

Đoạn mã sau đây thay đổi tất cả các hàng có ngôn ngữ có ngôn ngữ "en" thành có ngôn ngữ null. Giá trị trả về là số hàng đã được cập nhật.

Kotlin

// Defines an object to contain the updated values
val updateValues = ContentValues().apply {
    /*
     * Sets the updated value and updates the selected words.
     */
    putNull(UserDictionary.Words.LOCALE)
}

// Defines selection criteria for the rows you want to update
val selectionClause: String = UserDictionary.Words.LOCALE + "LIKE ?"
val selectionArgs: Array<String> = arrayOf("en_%")

// Defines a variable to contain the number of updated rows
var rowsUpdated: Int = 0
...
rowsUpdated = contentResolver.update(
        UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
        updateValues,                      // The columns to update
        selectionClause,                   // The column to select on
        selectionArgs                      // The value to compare to
)

Java

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

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

// Defines a variable to contain the number of updated rows
int rowsUpdated = 0;
...
/*
 * Sets the updated value and updates the selected words.
 */
updateValues.putNull(UserDictionary.Words.LOCALE);

rowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
    updateValues,                      // The columns to update
    selectionClause,                   // The column to select on
    selectionArgs                      // The value to compare to
);

Dọn dẹp hoạt động đầu vào của người dùng khi bạn gọi ContentResolver.update(). Để tìm hiểu thêm về hãy đọc phần Bảo vệ khỏi dữ liệu nhập độc hại.

Xoá dữ liệu

Xoá hàng tương tự như truy xuất dữ liệu hàng. Bạn chỉ định tiêu chí lựa chọn cho các hàng bạn muốn xoá và phương thức ứng dụng trả về số hàng đã xoá. Đoạn mã sau đây sẽ xoá các hàng có mã ứng dụng khớp với "user". Phương thức này trả về số hàng đã xoá.

Kotlin

// Defines selection criteria for the rows you want to delete
val selectionClause = "${UserDictionary.Words.APP_ID} LIKE ?"
val selectionArgs: Array<String> = arrayOf("user")

// Defines a variable to contain the number of rows deleted
var rowsDeleted: Int = 0
...
// Deletes the words that match the selection criteria
rowsDeleted = contentResolver.delete(
        UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
        selectionClause,                   // The column to select on
        selectionArgs                      // The value to compare to
)

Java

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

// Defines a variable to contain the number of rows deleted
int rowsDeleted = 0;
...
// Deletes the words that match the selection criteria
rowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
    selectionClause,                   // The column to select on
    selectionArgs                      // The value to compare to
);

Dọn dẹp hoạt động đầu vào của người dùng khi bạn gọi ContentResolver.delete(). Để tìm hiểu thêm về hãy đọc phần Bảo vệ khỏi dữ liệu nhập độc hại.

Loại dữ liệu nhà cung cấp

Trình cung cấp nội dung có thể cung cấp nhiều loại dữ liệu. Nhà cung cấp từ điển người dùng chỉ cung cấp nhưng nhà cung cấp cũng có thể cung cấp các định dạng sau:

  • số nguyên
  • số nguyên dài (dài)
  • dấu phẩy động
  • dấu phẩy động dài (gấp đôi)

Một loại dữ liệu khác mà nhà cung cấp thường sử dụng là đối tượng nhị phân lớn (BLOB) được triển khai dưới dạng Mảng byte 64 KB. Bạn có thể xem các loại dữ liệu có sẵn bằng cách xem Cursor lớp "nhận" .

Loại dữ liệu cho mỗi cột của nhà cung cấp thường được liệt kê trong tài liệu tương ứng. Các loại dữ liệu cho Nhà cung cấp từ điển người dùng được liệt kê trong tài liệu tham khảo cho lớp hợp đồng UserDictionary.Words. Các lớp hợp đồng là được mô tả trong phần Các lớp hợp đồng. Bạn cũng có thể xác định loại dữ liệu bằng cách gọi Cursor.getType().

Nhà cung cấp cũng duy trì thông tin loại dữ liệu MIME cho từng URI nội dung mà họ xác định. Bạn có thể sử dụng thông tin loại MIME để tìm hiểu xem ứng dụng của bạn có thể xử lý dữ liệu mà hoặc để chọn một loại xử lý dựa trên loại MIME. Bạn thường cần Loại MIME khi bạn làm việc với một nhà cung cấp có chứa mã tệp hoặc cấu trúc dữ liệu.

Ví dụ: ContactsContract.Data trong Trình cung cấp danh bạ sử dụng loại MIME để gắn nhãn loại dữ liệu liên hệ được lưu trữ trong mỗi hàng. Để lấy loại MIME tương ứng với URI nội dung, hãy gọi ContentResolver.getType().

Phần tài liệu tham khảo về loại MIME mô tả của cả hai loại MIME chuẩn và tuỳ chỉnh.

Các hình thức truy cập khác của nhà cung cấp

Có 3 hình thức quyền truy cập khác của trình cung cấp rất quan trọng trong quá trình phát triển ứng dụng:

  • Truy cập theo lô: bạn có thể tạo một loạt lệnh gọi truy cập bằng các phương thức trong lớp ContentProviderOperation, sau đó áp dụng chúng với ContentResolver.applyBatch().
  • Truy vấn không đồng bộ: thực hiện các truy vấn trong một chuỗi riêng. Bạn có thể hãy sử dụng đối tượng CursorLoader. Các ví dụ trong Minh hoạ hướng dẫn về trình tải cách thực hiện việc này.
  • Truy cập dữ liệu bằng ý định: mặc dù bạn không thể gửi ý định trực tiếp đến ứng dụng nhà cung cấp, bạn có thể gửi một ý định đến ứng dụng của nhà cung cấp, tức là thường được trang bị tốt nhất để chỉnh sửa dữ liệu của nhà cung cấp.

Quyền truy cập theo lô và sửa đổi bằng ý định được mô tả trong các phần sau.

Truy cập theo đợt

Quyền truy cập theo lô vào một trình cung cấp rất hữu ích khi chèn một số lượng lớn hàng, để chèn hàng trong nhiều bảng trong cùng một lệnh gọi phương thức và nói chung để thực hiện một tập hợp hoạt động xuyên qua ranh giới của quy trình dưới dạng giao dịch, được gọi là hoạt động nguyên tử.

Để truy cập vào nhà cung cấp ở chế độ hàng loạt, tạo một mảng các đối tượng ContentProviderOperation, sau đó gửi chúng đến một nhà cung cấp nội dung có ContentResolver.applyBatch() Bạn truyền quyền hạn của trình cung cấp nội dung đối với phương thức này, thay vì một URI nội dung cụ thể.

Điều này cho phép từng đối tượng ContentProviderOperation trong mảng hoạt động so với một bảng khác. Lệnh gọi đến ContentResolver.applyBatch() trả về một mảng kết quả.

Nội dung mô tả về lớp hợp đồng ContactsContract.RawContacts bao gồm một đoạn mã minh hoạ tính năng chèn hàng loạt.

Truy cập dữ liệu bằng ý định

Ý định có thể cung cấp quyền truy cập gián tiếp vào trình cung cấp nội dung. Bạn có thể cho phép người dùng truy cập trong một nhà cung cấp ngay cả khi ứng dụng của bạn không có quyền truy cập nhận lại ý định kết quả từ một ứng dụng có quyền hoặc bằng cách kích hoạt một có quyền và cho phép người dùng làm việc trong ứng dụng đó.

Nhận quyền truy cập bằng các quyền tạm thời

Bạn có thể truy cập dữ liệu trong trình cung cấp nội dung, ngay cả khi không có quyền truy cập thích hợp bằng cách gửi ý định đến một ứng dụng có quyền và nhận lại ý định kết quả có chứa quyền URI. Đây là các quyền cho một URI nội dung cụ thể kéo dài cho đến khi hoạt động nhận được chúng đã hoàn tất. Ứng dụng có quyền vĩnh viễn sẽ cấp quyền tạm thời bằng cách gắn cờ trong ý định kết quả:

Lưu ý: Những cờ này không cấp quyền đọc hoặc ghi chung cho trình cung cấp có quyền hạn trong URI nội dung. Quyền truy cập chỉ dành cho chính URI đó.

Khi bạn gửi URI nội dung đến một ứng dụng khác, hãy thêm ít nhất một trong hai thuộc tính này cờ. Cờ cung cấp các khả năng sau cho bất kỳ ứng dụng nào nhận được một ý định và nhắm đến Android 11 (API cấp 30) trở lên:

  • Đọc hoặc ghi vào dữ liệu mà URI nội dung biểu thị, tuỳ thuộc vào cờ có trong ý định.
  • Nhận gói chế độ hiển thị vào ứng dụng chứa trình cung cấp nội dung khớp với Đơn vị quản lý URI. Ứng dụng gửi ý định và ứng dụng gửi ý định chứa trình cung cấp nội dung có thể là hai ứng dụng khác nhau.

Nhà cung cấp xác định quyền URI cho URI nội dung trong tệp kê khai, bằng cách sử dụng android:grantUriPermissions của trang web <provider> cũng như <grant-uri-permission> là phần tử con của <provider> . Cơ chế cấp quyền URI được giải thích chi tiết hơn trong Hướng dẫn về Quyền trên Android.

Ví dụ: bạn có thể truy xuất dữ liệu về một người liên hệ trong Trình cung cấp danh bạ, ngay cả khi bạn không có quyền READ_CONTACTS. Bạn nên điều này bằng một ứng dụng gửi lời chào điện tử cho một người liên hệ vào ngày sinh nhật của họ. Thay vì yêu cầu READ_CONTACTS, giúp bạn có quyền truy cập vào tất cả danh bạ và tất cả thông tin của họ, cho phép người dùng kiểm soát địa chỉ liên hệ mà ứng dụng của bạn sử dụng. Để thực hiện việc này, hãy sử dụng quy trình sau:

  1. Trong ứng dụng của bạn, hãy gửi một ý định chứa thao tác ACTION_PICK và "địa chỉ liên hệ" Loại MIME CONTENT_ITEM_TYPE bằng cách sử dụng phương thức startActivityForResult().
  2. Vì ý định này khớp với bộ lọc ý định cho "Lựa chọn" của ứng dụng người dùng hoạt động, hoạt động đó sẽ xuất hiện trên nền trước.
  3. Trong hoạt động lựa chọn, người dùng chọn một địa chỉ liên hệ để cập nhật. Khi điều này xảy ra, hoạt động lựa chọn sẽ gọi setResult(resultcode, intent) để thiết lập ý định trả lại ứng dụng của bạn. Ý định chứa URI nội dung của thông tin liên hệ mà người dùng đã chọn và các "thông tin bổ sung" cờ FLAG_GRANT_READ_URI_PERMISSION. Các cờ này cấp URI quyền truy cập vào ứng dụng của bạn để đọc dữ liệu cho địa chỉ liên hệ mà URI nội dung. Sau đó, hoạt động lựa chọn sẽ gọi finish() tới trả về quyền kiểm soát cho ứng dụng của bạn.
  4. Hoạt động của bạn quay lại nền trước và hệ thống sẽ gọi onActivityResult() . Phương thức này nhận ý định kết quả do hoạt động lựa chọn tạo ra trong ứng dụng Liên hệ.
  5. Với URI nội dung trong ý định kết quả, bạn có thể đọc dữ liệu của người liên hệ từ Trình cung cấp danh bạ, ngay cả khi bạn không yêu cầu quyền truy cập đọc vĩnh viễn cho trình cung cấp trong tệp kê khai của bạn. Sau đó, bạn có thể nhận thông tin ngày sinh của người liên hệ đó hoặc địa chỉ email, sau đó gửi lời chào điện tử.

Sử dụng ứng dụng khác

Một cách khác để cho phép người dùng sửa đổi dữ liệu mà bạn không có quyền truy cập là kích hoạt một ứng dụng có quyền và cho phép người dùng thực hiện công việc trong ứng dụng đó.

Ví dụ: ứng dụng Lịch chấp nhận một Ý định ACTION_INSERT cho phép bạn kích hoạt giao diện người dùng chèn của ứng dụng. Bạn có thể chuyển "thông tin bổ sung" dữ liệu trong ý định này. Ứng dụng sẽ sử dụng để điền sẵn vào giao diện người dùng. Vì sự kiện định kỳ có cú pháp phức tạp, nên phương thức cách chèn sự kiện vào Nhà cung cấp lịch là kích hoạt ứng dụng Lịch bằng ACTION_INSERT, sau đó cho phép người dùng chèn sự kiện vào đó.

Hiển thị dữ liệu bằng ứng dụng trợ giúp

Nếu ứng dụng của bạn quyền truy cập, bạn vẫn có thể sử dụng ý định hiển thị dữ liệu trong một ứng dụng khác. Ví dụ: ứng dụng Lịch chấp nhận một Ý định ACTION_VIEW hiển thị một ngày hoặc sự kiện cụ thể. Điều này cho phép bạn hiển thị thông tin lịch mà không phải tạo giao diện người dùng của riêng mình. Để tìm hiểu thêm về tính năng này, hãy xem Tổng quan về nhà cung cấp lịch.

Ứng dụng mà bạn gửi ý định không nhất thiết phải là ứng dụng liên kết với nhà cung cấp. Ví dụ: bạn có thể truy xuất người liên hệ từ Liên hệ với Nhà cung cấp, sau đó gửi ý định ACTION_VIEW chứa URI nội dung dành cho hình ảnh của người liên hệ với trình xem hình ảnh.

Lớp học theo hợp đồng

Lớp hợp đồng xác định các hằng số giúp ứng dụng làm việc với URI nội dung, cột tên, thao tác theo ý định và các tính năng khác của trình cung cấp nội dung. Các lớp hợp đồng không có tự động đi kèm với một nhà cung cấp. Nhà phát triển của nhà cung cấp phải xác định chúng, sau đó cung cấp chúng cho các nhà phát triển khác. Nhiều nhà cung cấp đi kèm với Android nền tảng có các lớp hợp đồng tương ứng trong gói android.provider.

Ví dụ: Nhà cung cấp từ điển người dùng có một lớp hợp đồng UserDictionary chứa URI nội dung và hằng số tên cột. Chiến lược phát hành đĩa đơn URI nội dung cho bảng Words được định nghĩa trong hằng số UserDictionary.Words.CONTENT_URI Lớp UserDictionary.Words cũng chứa hằng số tên cột, được dùng trong đoạn mã ví dụ trong hướng dẫn này. Ví dụ: phép chiếu truy vấn có thể là được xác định như sau:

Kotlin

val projection : Array<String> = arrayOf(
        UserDictionary.Words._ID,
        UserDictionary.Words.WORD,
        UserDictionary.Words.LOCALE
)

Java

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

Một lớp hợp đồng khác là ContactsContract cho Trình cung cấp danh bạ. Tài liệu tham khảo cho lớp này bao gồm các đoạn mã mẫu. Một trong những lớp con, ContactsContract.Intents.Insert, là một hợp đồng lớp chứa hằng số cho ý định và dữ liệu ý định.

Tài liệu tham khảo về loại MIME

Nhà cung cấp nội dung có thể trả về các loại nội dung nghe nhìn MIME chuẩn, chuỗi loại MIME tuỳ chỉnh hoặc cả hai.

Loại MIME có định dạng sau:

type/subtype

Ví dụ: loại MIME phổ biến text/html có loại text và loại phụ html. Nếu trình cung cấp trả về loại này cho một URI, thì có nghĩa là một bằng cách sử dụng URI đó.

Chuỗi loại MIME tuỳ chỉnh, còn được gọi là loại MIME dành riêng cho nhà cung cấp, có nhiều kiểu MIME hơn các giá trị typesubtype phức. Đối với nhiều hàng, giá trị loại luôn là:

vnd.android.cursor.dir

Đối với một hàng, giá trị loại luôn là:

vnd.android.cursor.item

subtype là dành riêng cho từng nhà cung cấp. Các nhà cung cấp tích hợp sẵn cho Android thường có loại phụ. Ví dụ: khi ứng dụng Danh bạ tạo một hàng cho số điện thoại, mã này sẽ đặt loại MIME sau đây trong hàng:

vnd.android.cursor.item/phone_v2

Giá trị loại phụ là phone_v2.

Các nhà phát triển nhà cung cấp khác có thể tạo mẫu phụ của riêng họ dựa trên tên tổ chức và tên bảng. Ví dụ: hãy cân nhắc một nhà cung cấp có chứa lịch trình tàu hoả. Thẩm quyền của trình cung cấp là com.example.trains và có chứa các bảng Dòng 1, Dòng 2 và Dòng 3. Để phản hồi URI nội dung sau đây cho bảng Line1:

content://com.example.trains/Line1

trình cung cấp sẽ trả về loại MIME sau đây:

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

Để phản hồi URI nội dung sau đây cho hàng 5 trong bảng Line2:

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

trình cung cấp sẽ trả về loại MIME sau đây:

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

Hầu hết trình cung cấp nội dung đều xác định hằng số lớp hợp đồng cho loại MIME mà họ sử dụng. Chiến lược phát hành đĩa đơn Lớp hợp đồng Trình cung cấp danh bạ ContactsContract.RawContacts, ví dụ: xác định hằng số CONTENT_ITEM_TYPE cho loại MIME của một hàng thông tin liên hệ thô.

URI nội dung cho các hàng đơn được mô tả trong phần phần URI nội dung.