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. 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 để xử lý dữ liệu. Tuy nhiên, các trình cung cấp nội dung chủ yếu được các ứng dụng khác sử dụng để truy cập vào trình cung cấp bằng đối tượng ứng dụng của trình cung cấp. Cùng nhau, nhà cung cấp và ứng dụng của nhà cung cấp cung cấp một giao diện tiêu chuẩn, nhất quán cho dữ liệu cũng xử lý việc giao tiếp giữa các quy trình và truy cập dữ liệu một cách 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 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 trình bày những 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 có. Để tìm hiểu về cách triển khai trình cung cấp nội dung trong ứng dụng của riêng bạn, hãy xem phần Tạo 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 mà bạn sử dụng để truy xuất dữ liệu từ 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 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 một cơ sở dữ liệu quan hệ. Một hàng đại diện cho một thực thể 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 đ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ư 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ề các đề 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ủ bằng cách triển khai
AbstractThreadedSyncAdapter
- Tải dữ liệu trong giao diện người dùng bằng
CursorLoader
Truy cập vào 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 trình cung cấp đó dưới dạng một ứng dụng. Đối tượng ContentResolver
giao tiếp với đối tượng trình cung cấp, một thực thể của lớp triển khai ContentProvider
.
Đối tượng nhà cung cấp 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 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
. Các phương thức ContentResolver
cung cấp các chức năng "CRUD" (tạo, truy xuất, cập nhật và xoá) cơ bản của bộ nhớ cố định.
Một mẫu phổ biến để truy cập vào ContentProvider
từ giao diện người dùng sử dụng CursorLoader
để chạy truy vấn không đồng bộ ở chế độ nền. Activity
hoặc Fragment
trong giao diện người dùng gọi CursorLoader
đến truy vấn, từ đó lấy ContentProvider
bằng ContentResolver
.
Điều này cho phép người dùng tiếp tục sử dụng giao diện người dùng trong khi truy vấn đang chạy. Mẫu này liên quan đến hoạt động tương tác của một số đối tượng khác nhau, cũng như cơ chế lưu trữ cơ bản, như minh hoạ trong hình 2.
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 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 Quyền của 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ữ những từ không theo chuẩn mà người dùng muốn giữ lại. Bảng 1 minh hoạ nội dung dữ liệu có thể xuất hiện trong bảng của nhà cung cấp này:
word | id ứng dụng | đăng video | ngôn_ngữ | _Mã |
---|---|---|---|---|
mapreduce |
user1 | 100 | vi_VN | 1 |
precompiler |
user14 | 200 | fr_FR | 2 |
applet |
người dùng2 | 225 | fr_CA | 3 |
const |
user1 | 255 | pt_BR | 4 |
int |
user5 | 100 | en_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 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ữ mà từ đó gặp lần đầu. Tiêu đề cột là tên cột được lưu trữ trong nhà cung cấp. Ví dụ: để tham chiếu đến ngôn ngữ của một hàng, bạn tham chiếu đến cột locale
của hàng đó. Đối với 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ì.
Để lấy danh sách từ và ngôn ngữ của từ đó 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()
do Trình cung cấp từ điển người dùng xác định. Các dòng mã sau đây cho thấy 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ố đến query(Uri,projection,selection,selectionArgs,sortOrder)
khớp với câu lệnh SELECT của SQL:
Đối số query() |
SELECT từ khoá/thông số | Ghi chú |
---|---|---|
Uri |
FROM table_name |
Uri liên kết đến bảng trong nhà cung cấp có tên 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ó giá trị tương đương chính xác. Đối số lựa chọn 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à URI xác định dữ liệu trong 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 – quyền hạn của trì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 để truy cập vào một bảng trong 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 Trình cung cấp từ điển người dùng. Đối tượng ContentResolver
phân tích cú pháp authority của URI và sử dụng authority đó để giải quyết nhà cung cấp bằng cách so sánh authority với bảng hệ thống của các nhà cung cấp đã biết. Sau đó, ContentResolver
có thể gửi các đối số truy vấn đến đúng 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à nhà cung cấp đó 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à giao thức, luôn xuất hiện và xác định đây là URI nội dung. - Chuỗi
user_dictionary
là quyền của 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ó _ID
là 4
từ Trì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 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 Uri
và Uri.Builder
chứa các phương thức tiện lợi để tạo các đối tượng URI có định dạng hợp lệ từ các chuỗi. 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ộ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 trì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. 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ị ứng dụng hoàn chỉnh.
Để 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:
- Hãy yêu cầu nhà cung cấp cấp quyền đọc.
- Xác định mã sẽ 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 truy cập đọc cho 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 quyền chính xác do nhà cung cấp xác định.
Khi chỉ định phần tử này trong tệp kê khai, 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ọ ngầm cấp quyền yêu cầu này.
Để tìm tên chính xác của quyền truy cập đọc cho 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à nhà cung cấp sử dụng, hãy xem tài liệu của nhà cung cấp.
Vai trò của quyền trong việc truy cập vào trình cung cấp được mô tả chi tiết hơn trong phần 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ừ trì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 quá trình truy xuất dữ liệu từ nhà cung cấp là tạo truy vấn. Đoạn mã sau đây xác định một số biến để truy cập vào Trì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 Trì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 cũng 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 tập hợp tiêu chí lựa chọn và thứ tự sắp xếp.
Tập hợp 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ộ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 của 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ì 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, tức 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 sẽ trả về tất 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ự 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 bên ngoài không đáng tin cậy vào câu lệnh SQL thô, thì điều này 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; DROP TABLE *;" đối với mUserInput
, dẫn đến 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, nên điều này có thể khiến trình cung cấp xoá tất 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 nỗ lực 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 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 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 tính năng nối để đưa dữ liệu đầu vào của người dùng vào, 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 các đối số lựa chọn là cách ưu tiên để chỉ định một lựa chọn, ngay cả khi 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 được chỉ định bởi phép chiếu của truy vấn cho những hàng phù hợ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 vào các hàng và cột mà đối tượng đó chứa.
Khi sử dụng 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 ra khỏi cột và kiểm tra các thuộc tính khác của kết quả.
Một số cách triển khai Cursor
sẽ tự động cập nhật đối tượng khi dữ liệu của nhà cung cấp thay đổi, kích hoạt các phương thức 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 đối tượng 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 vào trình chuyển đổi đồng bộ hoá, vì vậy, trình cung cấp danh bạ không trả về các cột đó 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ề một đối tượng Cursor
có Cursor.getCount()
là 0, tức là một 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
.
Vì Cursor
là một danh sách hàng, nên một cách hay để hiển thị nội dung của Cursor
là liên kết nội dung đó với ListView
bằng SimpleCursorAdapter
.
Đoạn mã sau đây sẽ tiếp tục mã của đ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 theo 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 ý: Để sao lưu ListView
bằng Cursor
, 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 các 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 kết quả này cho các tác vụ khác. Ví dụ: bạn có thể truy xuất cách viết từ từ Nhà cung cấp từ điển người dùng, sau đó tra cứu cách viết đó 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 }
Các phương thức triển khai Cursor
chứa một số phương thức "get" để truy xuất nhiều loại dữ liệu từ đối tượng. Ví dụ: đoạn mã trước sử dụng getString()
. Các lớp này 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.
Phát hành tài nguyên kết quả truy vấn
Bạn phải đóng các đối tượng Cursor
nếu không cần đến nữa để các tài nguyên liên kết với các đối tượng đó được giải phóng sớm hơn. Bạn có thể thực hiện việc này bằng cách gọi close()
hoặc sử dụng câu lệnh try-with-resources
trong ngôn ngữ lập trình Java hoặc hàm use()
trong ngôn ngữ lập trình Kotlin.
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 ứng dụng cố gắng truy cập vào dữ liệu nào. Dựa trên các yêu cầu của 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 nhà cung cấp. Người dùng cuối sẽ thấy các quyền được yêu cầu 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ó toàn quyền đọc và ghi, bất kể quyền được chỉ định.
Trì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ừ trì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.
Để có các quyền cần thiết để truy cập vào một 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, 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 cài đặt.
Phần tử <uses-permission>
mẫu sau đây yêu cầu quyền đọc Trì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ư cách truy xuất dữ liệu từ một nhà cung cấp, bạn cũng sử dụng hoạt độ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
. Trình cung cấp và ứng dụng của trình cung cấp sẽ tự động xử lý việc bảo mật và giao tiếp giữa các quy trình.
Chèn dữ liệu
Để chèn dữ liệu vào một trình cung cấp, bạn hãy gọi phương thức ContentResolver.insert()
. Phương thức này 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 của 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, 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 loại dữ liệu và nếu không muốn chỉ định giá trị nào cả, bạn có thể đặt cột thành null
bằ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. Nhà cung cấp gán một giá trị duy nhất là _ID
cho mọi hàng được 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
sẽ xác định hàng mới được thêm vào theo đị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 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ể đó.
Để lấy 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
có các giá trị được cập nhật, tương tự như với tiêu chí chèn và chọn, giống như 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 ngôn ngữ null
. Giá trị trả về là số lượng 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ề vấn đề 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
Thao tác 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 sẽ trả về số lượng 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ố 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 dữ liệu đầu vào của người dùng khi bạn gọi ContentResolver.delete()
. Để tìm hiểu thêm về vấn đề 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
Nhà cung cấp nội dung có thể cung cấp nhiều loại dữ liệu. Trình cung cấp từ điển người dùng chỉ cung cấp văn bản, nhưng các trì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 (long)
- dấu phẩy động
- dấu phẩy động dài (double)
Một loại dữ liệu khác mà các nhà cung cấp thường sử dụng là đối tượng lớn nhị phâ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 một nhà cung cấp thường được liệt kê trong tài liệu của nhà cung cấp đó.
Các loại dữ liệu cho Trì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ủa trình cung cấp đó. Các lớp hợp đồng đượ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 về loại MIME để tìm hiểu xem ứng dụng của mình có thể xử lý dữ liệu mà nhà cung cấp cung cấp hay không hoặc để chọn một loại hình 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 chứa các cấu trúc hoặc tệp dữ liệu phức tạp.
Ví dụ: bảng ContactsContract.Data
trong Nhà cung cấp danh bạ sử dụng các 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 Tham chiếu 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 truy cập thay thế của nhà cung cấp
Có 3 hình thức truy cập thay thế của nhà cung cấp 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ằngContentResolver.applyBatch()
. -
Truy vấn không đồng bộ: thực hiện truy vấn trong một luồng riêng. Bạn có thể sử 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 nhà cung cấp, nhưng bạn có thể gửi ý định đến ứng dụng của nhà cung cấp. Ứng dụng này thường được trang bị tốt nhất để sửa đổi dữ liệu của nhà cung cấp.
Việc truy cập và sửa đổi hàng loạt bằng ý định được mô tả trong các phần sau.
Quyền truy cập theo lô
Quyền truy cập hàng loạt vào một 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 là để thực hiện một tập hợp các thao tác trên các 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 các đối tượng ContentProviderOperation
, sau đó điều phố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 hạn của trình cung cấp nội dung cho 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 dựa 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ả 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 vào dữ liệu trong một trình cung cấp ngay cả khi ứng dụng của bạn không có quyền truy cập bằng cách lấy 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 ứ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 đó.
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 một 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ả 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 các quyền đó hoàn tất. Ứng dụng có quyền vĩnh viễn cấp quyền tạm thời bằng cách đặt cờ trong ý định kết quả:
-
Quyền đọc:
FLAG_GRANT_READ_URI_PERMISSION
-
Quyền ghi:
FLAG_GRANT_WRITE_URI_PERMISSION
Lưu ý: Các cờ này không cấp quyền đọc hoặc ghi chung cho nhà cung cấp có thẩm quyền được chứa 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 các cờ này. Các cờ này cung cấp các chức 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 dữ liệu mà URI nội dung biểu thị, tuỳ thuộc vào cờ có trong ý định.
- Xem thông tin về 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.
Trì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 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 không có quyền READ_CONTACTS
. Bạn có thể muốn làm việc này trong một ứng dụng gửi lời chúc sinh nhật qua email cho một người liên hệ. Thay vì yêu cầu READ_CONTACTS
để cấp cho bạn quyền truy cập vào tất cả danh bạ và thông tin của người dùng, hãy cho phép người dùng kiểm soát danh bạ 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:
-
Trong ứng dụng, hãy gửi một ý định chứa thao tác
ACTION_PICK
và loại MIME "contacts" (danh bạ)CONTENT_ITEM_TYPE
bằng phương thứcstartActivityForResult()
. - 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 People, nên hoạt động này sẽ chuyển sang nền trước.
-
Trong hoạt động lựa chọn, người dùng chọn một người 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ả về ứng dụng của bạn. Ý định này chứa URI nội dung của người liên hệ mà người dùng đã chọn và cờ "bổ sung"FLAG_GRANT_READ_URI_PERMISSION
. Các 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ệ mà URI nội dung trỏ đến. Sau đó, hoạt động lựa chọn sẽ gọifinish()
để trả lại quyền kiểm soát cho ứng dụng. -
Hoạt động của bạn quay lại nền trước và hệ thống sẽ 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. - Với URI nội dung từ ý định kết quả, bạn có thể đọc dữ liệu của mục liên hệ từ Trình cung cấp danh bạ, mặc dù bạn không yêu cầu quyền truy cập đọc vĩnh viễn cho nhà cung cấp trong tệp kê khai của mình. Sau đó, bạn có thể nhận 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 một ứ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" trong ý định này mà ứng dụng sử dụng để điền sẵn giao diện người dùng. Vì các sự kiện định kỳ có cú pháp phức tạp, nên cách tốt nhất để 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 có 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 về lịch mà không cần 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 phần Tổng quan về trình cung cấp lịch.
Ứng dụng mà bạn gửi ý định đến 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 một mục liên hệ từ Nhà cung cấp danh bạ, sau đó gửi ý định ACTION_VIEW
chứa URI nội dung cho hình ảnh của mục liên hệ đến 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 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 tự động được đưa vào trong trình cung cấp. Nhà phát triển của nhà cung cấp phải xác định các giá trị này 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ụ: Trì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, được dùng trong các đoạn mã ví dụ trong hướng dẫn này. Ví dụ: bạn có thể xác định một 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 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, ContactsContract.Intents.Insert
, là một lớp hợp đồng chứa các 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 như sau:
type/subtype
Ví dụ: loại MIME text/html
nổi tiếng có loại text
và loại phụ html
. Nếu nhà cung cấp trả về loại này cho một URI, thì tức là 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ị type và subtype phức tạp hơn. Đối với nhiều hàng, giá trị loại luôn như sau:
vnd.android.cursor.dir
Đối với một hàng, giá trị loại luôn như sau:
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 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 số điện thoại, ứng dụng 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 khác cung cấp trình cung cấp có thể tạo mẫu phụ của riêng họ dựa trên quyền hạn và tên bảng của trình cung cấp đó. Ví dụ: hãy cân nhắc một nhà cung cấp có cung cấp lịch tàu chạy.
Thẩm 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 đâ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. Ví dụ: lớp hợp đồng Nhà cung cấp danh bạ ContactsContract.RawContacts
xác định hằng số CONTENT_ITEM_TYPE
cho loại MIME của một hàng danh bạ thô.
URI nội dung cho các hàng đơn lẻ được mô tả trong phần URI nội dung.