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. Trì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, các ứng dụng khác chủ yếu dùng trình cung cấp nội dung. Các ứng dụng này truy cập vào trình cung cấp bằng đối tượng ứng dụng khách của ứng dụng nhà cung cấp. Ứng dụng của nhà cung cấp và ứng dụng khách của nhà cung cấp sẽ cùng nhau cung cấp một giao diện tiêu chuẩn, nhất quán cho dữ liệu. Giao diện này cũng xử lý hoạt động giao tiếp liên quy trình và truy cập dữ liệu an toàn.

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

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

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

  • Cách thức 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ừ nhà cung cấp nội dung.
  • API mà bạn dùng để chèn, cập nhật hoặc xoá dữ liệu trong một nhà cung cấp nội dung.
  • Các tính năng API khác hỗ trợ làm việ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ự với các bảng được tìm thấy trong cơ sở dữ liệu quan hệ. Một hàng đại diện cho một bản sao của một số loại 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 riêng lẻ được thu thập cho một thực thể.

Trình cung cấp nội dung sẽ đ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ố API và thành phần. Như được minh hoạ trong hình 1, các cấu hình 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 vào tiện ích
  • Trả về nội dung đề xuất tìm kiếm tuỳ chỉnh cho ứng dụng của bạn thông qua khung tìm kiếm 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 vào dữ liệu trong một trình cung cấp nội dung, bạn sử dụng đối tượng ContentResolver trong Context của ứng dụng để giao tiếp với nhà cung cấp đó dưới dạng ứng dụng khách. Đối tượng ContentResolver giao tiếp với đối tượng nhà cung cấp, một thực thể của lớp triển khai ContentProvider.

Đối tượng nhà cung cấp sẽ nhận yêu cầu dữ liệu từ ứng dụng, 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 nhà cung cấp, một thực thể của một trong các lớp con cụ thể của ContentProvider. Phương thức ContentResolver cung cấp các chức năng "CRUD" cơ bản (tạo, truy xuất, cập nhật và xoá) của bộ nhớ liên tục.

Một mẫu phổ biến để truy cập ContentProvider từ giao diện người dùng của bạn sử dụng CursorLoader để chạy một truy vấn không đồng bộ ở chế độ nền. Activity hoặc Fragment trong giao diện người dùng của bạn gọi CursorLoader đến truy vấn, rồi nhận ContentProvider bằng cách sử dụng ContentResolver.

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

Hoạt động 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 trình cung cấp, ứng dụng của bạn thường phải yêu cầu các quyền cụ thể trong tệp kê khai. Mẫu phát triển này được mô tả chi tiết hơn trong phần Các quyền của trình cung cấp nội dung.

Một trong những trình cung cấp tích hợp sẵn trong nền tảng Android là Trình cung cấp từ điển người dùng. Trình cung cấp này lưu trữ các từ không theo chuẩn mà người dùng muốn giữ lại. Bảng 1 minh hoạ dữ liệu có thể hiển thị 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ùng 1 100 vi-VN 1
precompiler người dùng 14 200 thứ_fr_FR 2
applet người dùng 2 225 fr_CA 3
const người dùng 1 255 pt_BR 4
int người dùng 5 100 vi_UK 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 tiêu chuẩn. Mỗi cột đại diện cho một phần dữ liệu cho từ đó, chẳng hạn như ngôn ngữ nơi từ đó được gặp lần đầu tiên. Tiêu đề cột là tên cột được lưu trữ trong trình cung cấp. Do đó, để tham chiếu đến ngôn ngữ của một hàng, chẳng hạn như bạn sẽ tham chiếu đến cột locale của hàng đó. Đối với trình cung cấp này, cột _ID đóng vai trò là cột khoá chính mà trình cung cấp tự động duy trì.

Để nhận danh sách các từ và ngôn ngữ của các từ đó từ Nhà cung cấp từ điển người dùng, bạn cần gọi ContentResolver.query(). Phương thức query() gọi phương thức ContentProvider.query() do Nhà cung cấp từ điển người dùng xác định. Các dòng mã sau hiển thị lệnh 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ố tới query(Uri,projection,selection,selectionArgs,sortOrder) khớp với câu lệnh SELECT của SQL:

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

Đối số query() CHỌN 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ác cột được đưa vào cho mỗi hàng được truy xuất.
selection WHERE col = value selection chỉ định tiêu chí để chọn hàng.
selectionArgs Không có phiên bản tương đương chính xác. Các đối số lựa chọn sẽ thay thế phần giữ chỗ ? trong mệnh đề lựa chọn.
sortOrder ORDER BY col,col,... sortOrder chỉ định thứ tự các hàng xuất hiện trong Cursor được trả về.

URI nội dung

URI nội dung là một URI xác định dữ liệu của một nhà cung cấp. URI nội dung bao gồm tên tượng trưng của toàn bộ trình cung cấp – cơ quan của nhà cung cấp đó – và tên trỏ đến một bảng – đường dẫn. Khi bạn gọi một phương thức ứng dụng để truy cập vào một bảng trong một trì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ên, hằng số CONTENT_URI chứa URI nội dung của bảng Words của Trình cung cấp từ điển cho người dùng. Đối tượng ContentResolver phân tích cú pháp thẩm quyền của URI và dùng quyền này để phân giải trình cung cấp bằng cách so sánh thẩm quyền với bảng hệ thống của các trình cung cấp đã biết. Sau đó, ContentResolver có thể gửi các đối số truy vấn đến đúng trình cung cấp.

ContentProvider sử dụng phần đường dẫn của URI nội dung để chọn bảng cần 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://lược đồ, luôn hiện diện và xác định chuỗi này là một URI nội dung.
  • Chuỗi user_dictionary là thẩm quyền của trình cung cấp.
  • Chuỗi words là đường dẫn của bảng.

Nhiều trình cung cấp cho phép bạn truy cập vào một hàng duy nhất 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 sau:

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 các hàng đó.

Lưu ý: Các lớp UriUri.Builder chứa các phương thức thuận tiện để tạo đối tượng URI được định dạng tốt từ các chuỗi. Lớp ContentUris chứa các phương thức thuận tiện để thêm giá trị mã nhận dạng vào URI. Đoạn mã trước sử dụng withAppendedId() để thêm một 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ừ một nhà cung cấp, lấy 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 sẽ gọi ContentResolver.query() trên luồng giao diện người dùng. Tuy nhiên, trong mã thực tế, hãy thực hiện các truy vấn không đồng bộ trên một luồng riêng biệt. Bạn có thể 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ị một ứng dụng hoàn chỉnh.

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

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

Yêu cầu quyền truy cập đọ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 dữ liệu của nhà cung cấp đó. 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 phần tử <uses-permission> và tên chính xác của quyền do trình cung cấp xác định.

Khi chỉ định phần tử này trong tệp kê khai, tức là bạn đang yêu cầu quyền này cho ứng dụng của mình. Khi người dùng cài đặt ứng dụng của bạn, họ sẽ ngầm cấp 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, cũng như tên của các quyền truy cập khác mà trình cung cấp đó sử dụng, hãy xem tài liệu của trình cung cấp đó.

Vai trò của các quyền khi truy cập vào trình cung cấp được mô tả chi tiết hơn trong phần Các 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, một ứng dụng muốn đọc từ trình cung cấp phải yêu cầu quyền này.

Xây dựng truy vấn

Bước tiếp theo trong việc truy xuất dữ liệu từ một nhà cung cấp là tạo 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(), lấy Nhà cung cấp từ điển người dùng làm ví dụ. Truy vấn ứng dụng của nhà cung cấp tương tự như truy vấn SQL và chứa một tập hợp các cột cần trả về, một nhóm tiêu chí lựa 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 sẽ được chia thành một mệnh đề lựa chọn và các đối số lựa chọn. Mệnh đề lựa chọn là sự kết hợp giữa các biểu thức logic và boolean, tên cột và giá trị. Biến này là mSelectionClause. Nếu bạn chỉ định tham số có thể thay thế ? thay vì một giá trị, thì phương thức truy vấn sẽ truy xuất giá trị từ mảng đối số lựa chọn, là biến mSelectionArgs.

Trong đoạn mã tiếp theo, nếu người dùng không nhập một từ thì mệnh đề lựa chọn sẽ được đặt thành null và truy vấn sẽ trả về tất cả các từ trong trình cung cấp. Nếu người dùng nhập một từ, mệnh đề lựa chọn sẽ được đặt thành UserDictionary.Words.WORD + " = ?" và phần tử đầu tiên của mảng đối số lựa chọn sẽ đượ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ự với 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ế được dùng thay cho các 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, thì việc đưa 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 có thể nối SQL độc hại vào câu lệnh SQL. Ví dụ: người dùng có thể nhập "nothing; DROP TABLE *;" đối với mUserInput. Thao tác này sẽ dẫn đến mệnh đề lựa chọn var = nothing; DROP TABLE *;.

Vì mệnh đề lựa chọn được coi là một câu lệnh SQL, nên đ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 để phát hiện các lượt chèn SQL.

Để tránh vấn đề này, hãy dùng mệnh đề lựa chọn trong đó ? làm tham số 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, hoạt động đầ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 là một phần của câu lệnh SQL. Vì không được coi là SQL nên dữ liệu đầu vào của người dùng không thể chèn SQL độc hại. Thay vì sử dụng phép nối để đưa dữ liệu đầu vào của người dùng, hãy sử dụng mệnh đề lựa chọn này:

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 các đối số lựa chọn là cách ưu tiên để chỉ định lựa chọn, ngay cả khi trình cung cấp không 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 do phép chiếu của truy vấn chỉ định cho các hàng khớp với tiêu chí lựa chọn của truy vấn. Đối tượng Cursor cung cấp quyền đọc ngẫu nhiên các hàng và cột trong đối tượng đó.

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

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

Lưu ý: Trình cung cấp có thể hạn chế quyền truy cập vào các cột dựa trên tính chất của đối tượng thực hiện truy vấn. Ví dụ: Trình cung cấp danh bạ hạn chế quyền truy cập đối với một số cột để đồng bộ hoá bộ chuyển đổi để không trả về 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, trình cung cấp sẽ trả về đối tượng CursorCursor.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 sẽ phụ thuộc vào nhà cung cấp cụ thể. Phương thức này có thể trả về null hoặc có thể gửi một Exception.

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

Đoạn mã sau tiếp tục mã từ đoạn mã trước. Thao tác này sẽ tạo một đối tượng SimpleCursorAdapter chứa Cursor được truy xuất bằng truy vấn và đặt đối tượng này làm đối tượng 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 ý: Để quay lại ListViewCursor, con trỏ phải chứa một cột có tên là _ID. Do đó, truy vấn hiển thị trước đó sẽ truy xuất cột _ID cho bảng Words, mặc dù ListView không hiển thị cột này. Quy định hạn chế này cũng giải thích lý do hầu hết nhà cung cấp đều có cột _ID cho mỗi bảng.

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 chúng cho các tác vụ khác. Ví dụ: bạn có thể truy xuất cách viết chính tả từ Nhà cung cấp từ điển người dùng rồi tra cứu cách viết đó bằng 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ố phương thức "get" để 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(). Các phương thức này cũng có phương thức getType() trả về 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. Những quyền này cho người dùng biết dữ liệu mà một ứng dụng cố gắng truy cập. Dựa trên yêu cầu của trình cung cấp, các ứng dụng khác sẽ yêu cầu các quyền cần thiết để truy cập vào ứng dụng này. Người dùng cuối sẽ thấy các quyền được yêu cầu khi họ cài đặt ứng dụng.

Nếu ứng dụng của nhà cung cấp không chỉ định 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 ứng dụng nhà cung cấp đó được xuất. Ngoài ra, các thành phần trong ứng dụng của trình cung cấp luôn có toàn quyền đọc và ghi, bất kể các quyền được chỉ định là gì.

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

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

Phần tử <uses-permission> mẫu sau đây 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 phần Mẹo bảo mật.

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

Tương tự như khi truy xuất dữ liệu từ một nhà cung cấp, bạn cũng sử dụng sự 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 ContentResolver bằng các đối số được truyền đến phương thức tương ứng của ContentProvider. Ứng dụng của nhà cung cấp và ứng dụng của nhà cung cấp tự động xử lý hoạt động giao tiếp liên quy trình và bảo mật.

Chèn dữ liệu

Để chèn dữ liệu vào một trình cung cấp, bạn cần gọi phương thức ContentResolver.insert(). Phương thức này sẽ chèn một dòng mới vào trình cung cấp và trả về 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 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, 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 không muốn chỉ định một giá trị nào đó, bạn có thể đặt một cột thành null bằng cách sử dụng ContentValues.putNull().

Đoạn mã trước không thêm cột _ID vì cột này được duy trì tự động. Trình cung cấp này chỉ định một giá trị duy nhất là _ID cho mỗi hàng được thêm vào. 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 thêm có đị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 trì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 thao tác được 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 các giá trị đã cập nhật, giống như cách bạn thực hiện với tiêu chí chèn và lựa chọn, giống như cách bạn thực hiện với truy vấn. Phương thức ứng dụng mà bạn sử dụng là ContentResolver.update(). Bạn chỉ cần thêm giá trị 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 một 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ữ "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ề điều này, hãy đọc phần Bảo vệ khỏi dữ liệu đầu vào độc hại.

Xoá dữ liệu

Việc xoá hàng cũng tương tự như việc truy xuất dữ liệu hàng. Bạn chỉ định tiêu chí lựa chọn cho những hàng bạn muốn xoá và phương thức ứng dụng sẽ trả về số lượng hàng đã xoá. Đoạn mã sau sẽ xoá các hàng có mã ứng dụng khớp với "user". Phương thức này sẽ trả về số lượng 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ề điều này, hãy đọc phần Bảo vệ khỏi dữ liệu đầu vào độc hại.

Loại dữ liệu của 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 văn bản, 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 (kép)

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 các phương thức "get" của lớp Cursor.

Loại dữ liệu cho mỗi cột trong nhà cung cấp thường được liệt kê trong tài liệu của cột đó. 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 về lớp hợp đồng UserDictionary.Words. Các lớp hợp đồng được mô tả trong phần 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().

Trì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à chúng xác định. Bạn có thể sử dụng thông tin về loại MIME để tìm hiểu xem ứng dụng của bạn có thể xử lý dữ liệu mà nhà cung cấp cung cấp hay không hoặc để chọn một kiểu xử lý dựa trên loại MIME. Bạn thường cần loại MIME khi làm việc với một nhà cung cấp có chứa tệp hoặc cấu trúc dữ liệu phức tạp.

Ví dụ: bảng 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 người liên hệ được lưu trữ trong mỗi hàng. Để lấy loại MIME tương ứng với một 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ú pháp của cả loại MIME chuẩn và tuỳ chỉnh.

Các hình thức khác để cấp quyền truy cập cho nhà cung cấp

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

  • Truy cập hàng loạt: 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 các lệnh gọi đó bằng 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 biệt. Bạn có thể dùng đối tượng CursorLoader. Các ví dụ trong hướng dẫn về Trình tải minh hoạ cách thực hiện việc này.
  • Truy cập dữ liệu bằng ý định: mặc dù không thể gửi ý định trực tiếp đến trình cung cấp, nhưng bạn có thể gửi ý định đến ứng dụng của trình cung cấp. Ý định này thường được trang bị tốt nhất để sửa đổi dữ liệu của trình cung cấp.

Các phần sau đây mô tả về việc truy cập hàng loạt và sửa đổi bằng cách sử dụng ý định.

Truy cập hàng loạt

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

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

Điều này cho phép mỗi đối tượng ContentProviderOperation trong mảng hoạt động trên 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ả của 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 cách sử dụng ý định

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

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

Bạn có thể truy cập dữ liệu qua trình cung cấp nội dung ngay cả khi không có quyền truy cập phù 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ả 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 những quyền đó 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 truy cập nằm 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 đưa vào ít nhất một trong các cờ này. Cờ này cung cấp các tính năng sau cho mọi ứng dụng nhận được ý đị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 chế độ hiển thị gói trong ứ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 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 các URI nội dung trong tệp kê khai bằng cách sử dụng thuộc tính android:grantUriPermissions của phần tử <provider> cũng như phần tử con <grant-uri-permission> của phần tử <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 thực hiện việc này trong một ứng dụng gửi lời chào điện tử đến 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 (việc này cấp cho bạn quyền truy cập vào toàn bộ danh bạ của người dùng và mọi thông tin của họ), hãy cho phép người dùng kiểm soát những mục liên hệ mà ứng dụng của bạn sử dụng. Để làm việc này, hãy sử dụng quy trình sau:

  1. Trong ứng dụng, hãy gửi một ý định chứa thao tác ACTION_PICK và loại MIME "danh bạ" CONTENT_ITEM_TYPE, sử dụng phương thức startActivityForResult().
  2. Vì ý định này khớp với bộ lọc ý định cho hoạt động "lựa chọn" của ứng dụng Mọi người, nên hoạt động đó sẽ xuất hiệ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 một ý định trả về ứng dụng. Ý định này chứa URI nội dung của liên hệ mà người dùng đã chọn và cờ "bổ sung" FLAG_GRANT_READ_URI_PERMISSION. Những cờ này cấp quyền URI cho ứng dụng của bạn để đọc dữ liệu cho người liên hệ do URI nội dung trỏ đến. Sau đó, hoạt động lựa chọn sẽ gọi finish() để trả về quyền kiểm soát cho ứng dụng.
  4. Hoạt động của bạn sẽ quay lại nền trước và hệ thống gọi phương thức onActivityResult() của hoạt động. Phương thức này nhận ý định kết quả do hoạt động lựa chọn trong ứng dụng Mọi người tạo ra.
  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 đối với nhà cung cấp trong tệp kê khai. Sau đó, bạn có thể lấy thông tin ngày sinh hoặc địa chỉ email của người liên hệ rồi 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 ở đó.

Ví dụ: ứng dụng Lịch chấp nhận ý đị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ể truyền dữ liệu "bổ sung" vào ý định này. Ứng dụng sẽ sử dụng dữ liệu này để điền sẵn giao diện người dùng. Vì sự kiện định kỳ có cú pháp phức tạp, nên cách ưu tiên để chèn sự kiện vào Trình cung cấp lịch là kích hoạt ứng dụng Lịch bằng ACTION_INSERT rồi 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, thì 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 ý đị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 cần phải tạo giao diện người dùng riêng. Để tìm hiểu thêm về tính năng này, hãy xem bài viết Tổng quan về nhà cung cấp dịch vụ Lịch.

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

Các lớp theo hợp đồng

Lớp hợp đồng xác định các hằng số giúp ứng dụng hoạt động với URI nội dung, tên cột, 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 nhà cung cấp tự động đưa vào. Nhà phát triển của nhà cung cấp phải xác định các mã đó rồi cung cấp cho các nhà phát triển khác. Nhiều nhà cung cấp đi kèm với nền tảng Android 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. URI nội dung cho bảng Words được xác định trong hằng số UserDictionary.Words.CONTENT_URI. Lớp UserDictionary.Words cũng chứa các hằng số tên cột dùng trong các đoạn mã mẫu trong hướng dẫn này. Ví dụ: bạn có thể xác định phép chiếu truy vấn 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 các lớp con của lớp này là ContactsContract.Intents.Insert là lớp hợp đồng chứa các hằng số cho dữ liệu ý định và ý định.

Tài liệu tham khảo 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ì tức là một truy vấn sử dụng URI đó sẽ trả về văn bản chứa thẻ HTML.

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

vnd.android.cursor.dir

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

vnd.android.cursor.item

subtype là dành riêng cho nhà cung cấp. Các trình cung cấp tích hợp sẵn của Android thường có một loại phụ đơn giản. Ví dụ: khi ứng dụng Danh bạ tạo một hàng cho một số điện thoại, ứng dụng sẽ thiết lập loại MIME sau trong hàng:

vnd.android.cursor.item/phone_v2

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

Các nhà phát triển khác của nhà cung cấp có thể tạo mẫu loại phụ riêng dựa trên thẩm quyền và tên bảng của trình cung cấp. Ví dụ: hãy xem xét một nhà cung cấp có lịch trình tàu chạy. Quyền của trình cung cấp là com.example.trains và chứa các bảng Line1, Line2 và Line3. Phản hồi URI nội dung sau cho bảng Line1:

content://com.example.trains/Line1

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

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

Phản hồi URI nội dung sau 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:

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

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

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