lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

Nội dung Cơ bản về Trình cung cấp Nội dung

Trình cung cấp nội dung quản lý truy cập vào một kho dữ liệu tập trung. Trình cung cấp là bộ phận của một ứng dụng Android, nó thường cung cấp UI của chính mình để làm việc cùng dữ liệu. Tuy nhiên, trình cung cấp nội dung được thiết kế chủ yếu cho các ứng dụng khác sử dụng, giúp truy cập trình cung cấp bằng cách sử dụng một đối tượng máy khách cung cấp. Cùng nhau, trình cung cấp và máy khách cung cấp sẽ mang đến một giao diện nhất quán, tiêu chuẩn cho dữ liệu, giao diện này cũng đồng thời xử lý truyền thông liên tiến trình và bảo mật truy cập dữ liệu.

Chủ đề này đề cập đến những nội dung cơ bản sau đây:

  • Cách trình cung cấp nội dung hoạt động.
  • API bạn sử dụng để truy xuất dữ liệu từ một trình cung cấp nội dung.
  • API bạn sử dụng để chèn, cập nhật, hoặc xóa dữ liệu trong một trình cung cấp nội dung.
  • Các tính năng API khác tạo điều kiện làm việc cùng các trì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 được tìm thấy trong một cơ sở dữ liệu quan hệ. Mỗi hàng thể hiện một thực thể của một số kiểu dữ liệu mà trình cung cấp thu thập, và mỗi cột trong hàng thể hiện một phần riêng biệt của dữ liệu được thu thập đối với một thực thể.

Ví dụ, một trong các trình cung cấp tích hợp trong nền tảng Android đó là từ điển người dùng, nó lưu giữ chính tả của những từ phi tiêu chuẩn mà người dùng muốn giữ lại. Bảng 1 minh họa cách mà dữ liệu có thể được trình bày trong bảng của trình cung cấp này:

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

từ id ứng dụng tần suất bản địa _ID
mapreduce user1 100 en_US 1
precompiler user14 200 fr_FR 2
applet user2 225 fr_CA 3
const user1 255 pt_BR 4
int user5 100 en_UK 5

Trong bảng 1, mỗi hàng thể hiện một thực thể của một từ mà có thể không thấy có trong từ điển chuẩn. Mỗi cột thể hiện một số dữ liệu cho từ đó, chẳng hạn như từ bản địa được dùng lần đầu cho từ đó. Tiêu đề cột là các tên cột được lưu giữ trong trình cung cấp. Để tham khảo tới bản địa của một hàng, bạn tham khảo tới 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 "khóa chính" mà trình cung cấp tự động duy trì.

Lưu ý: Trình cung cấp không bắt buộc phải có một khóa chính, và không bắt buộc phải sử dụng _ID làm tên cột của một khóa chính nếu có khóa. Tuy nhiên, nếu bạn muốn gắn kết dữ liệu từ một trình cung cấp với một ListView, một trong các tên cột sẽ phải là _ID. Yêu cầu này được giải thích chi tiết hơn trong phần Hiển thị các kết quả truy vấn.

Truy cập một trình cung cấp

Một ứng dụng truy cập dữ liệu từ một trình cung cấp nội dung bằng một đối tượng máy khách ContentResolver. Đối tượng này có các phương pháp để gọi những phương pháp có tên giống nhau trong đối tượng trình cung cấp, một thực thể của một trong những lớp con cụ thể của ContentProvider. Các phương pháp ContentResolver cung cấp các chức năng "CRUD" (tạo, truy xuất, cập nhật, và xóa) cơ bản của thiết bị lưu trữ liên tục.

Đối tượng ContentResolver trong tiến trình của ứng dụng máy khách và đối tượng ContentProvider trong ứng dụng mà sở hữu trình cung cấp sẽ tự động xử lý truyền thông liên tiến trình. ContentProvider cũng đóng vai trò như một lớp rút gọn giữa kho dữ liệu của nó và biểu diễn bên ngoài của dữ liệu dưới dạng bảng.

Lưu ý: Để truy cập 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 bản kê khai của mình. Điều này được mô tả chi tiết hơn trong phần Quyền của Trình cung cấp Nội dung

Ví dụ, để có một danh sách các từ và nội dung bản địa của chúng từ Trình cung cấp Từ điển Người dùng, bạn hãy gọi ContentResolver.query(). Phương pháp query() sẽ gọi phương pháp ContentProvider.query() được định nghĩa bởi Trình cung cấp Từ điển Người dùng. Các dòng mã sau thể hiện một lệnh gọi ContentResolver.query():

// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
    mProjection,                        // The columns to return for each row
    mSelectionClause                    // Selection criteria
    mSelectionArgs,                     // Selection criteria
    mSortOrder);                        // The sort order for the returned rows

Bảng 2 cho biết các tham đối tới query(Uri,projection,selection,selectionArgs,sortOrder) khớp với một câu lệnh SQL SELECT như thế nào:

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

tham đối query() Từ khóa/tham số SELECT Lưu ý
Uri FROM table_name Uri ánh xạ tới bảng trong trình cung cấp có tên table_name.
projection col,col,col,... projection là một mảng gồm các cột nên được đưa vào đối với mỗi hàng được truy xuất.
selection WHERE col = value selection quy định các tiêu chí để lựa chọn hàng.
selectionArgs (Không có sự tương đương chính xác. Các tham đối lựa chọn sẽ thay thế các chỗ dành sẵn ? trong mệnh đề lựa chọn.)
sortOrder ORDER BY col,col,... sortOrder quy đị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 trong một trình cung cấp. URI nội dung bao gồm tên biểu tượng của toàn bộ trình cung cấp (quyền của nó) và một tên trỏ đến một bảng (đường dẫn). Khi bạn gọi một phương pháp máy khách để truy cập 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 tham đối.

Trong các dòng mã trước, hằng số CONTENT_URI chứa URI nội dung của bảng "từ" của từ điển người dùng. Đối tượng ContentResolver sẽ phân tích quyền của URI, và sử dụng nó để "giải quyết" trình cung cấp bằng cách so sánh quyền với một bảng hệ thống của các trình cung cấp đã biết. Khi đó, ContentResolver có thể phân phối các tham đối truy vấn tới đúng trình cung cấp.

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

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

content://user_dictionary/words

trong đó xâu user_dictionary là quyền của trình cung cấp, và xâu words là đường dẫn của bảng. Xâu content:// (lược đồ) sẽ luôn có mặt, và xác định đây là một URI nội dung.

Nhiều trình cung cấp cho phép bạn truy cập một hàng đơn lẻ trong một bảng bằng cách nối một giá trị ID với đuôi của URI. Ví dụ, để truy xuất một hàng có _ID4 từ một từ điển người dùng, bạn có thể sử dụng URI nội dung này:

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

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

Lưu ý: Các lớp UriUri.Builder chứa các phương pháp thuận tiện để xây dựng đối tượng URI định dạng tốt từ các xâu. ContentUris chứa các phương pháp thuận tiện để nối các giá trị id với một URI. Đoạn mã HTML trước sử dụng withAppendedId() để nối một id với URI nội dung Từ điển Người dùng.

Truy xuất Dữ liệu từ Trì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 bằng cách sử dụng Trình cung cấp Từ điển Người dùng làm ví dụ.

Để giải thích rõ, đoạn mã HTML trong phần này gọi ContentResolver.query() trên "luồng UI"". Tuy nhiên, trong mã thực sự, bạn nên thực hiện các truy vấn không đồng bộ trên một luồng riêng. Một cách để làm điều này đó là sử dụng lớp CursorLoader, nó được mô tả chi tiết hơn trong hướng dẫn Trình tải. Bênh cạnh đó, các dòng mã chỉ là đoạn mã HTML; chúng không thể hiện một ứng dụng hoàn chỉnh.

Để truy xuất dữ liệu từ một trì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 trình cung cấp.
  2. Định nghĩa mã để gửi một truy vấn tới trình cung cấp.

Yêu cầu quyền truy cập đọc

Để truy xuất dữ liệu từ một trình cung cấp, ứng dụng của bạn cần "quyền truy cập đọc" cho trì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 bản kê khai của mình bằng cách sử dụng phần tử <uses-permission> và tên quyền chính xác được định nghĩa bởi trình cung cấp. Khi bạn chỉ định phần tử này trong bản kê khai của mình, bạn đang thực tế hóa "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 hiểu cấp yêu cầu này.

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

Vai trò của quyền trong việc truy cập các 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 sẽ định nghĩa quyền android.permission.READ_USER_DICTIONARY trong tệp bản kê khai của nó, vì vậy một ứng dụng muốn đọc từ trình cung cấp sẽ 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 trình cung cấp đó là xây dựng một truy vấn. Đoạn mã HTML đầu tiên này định nghĩa một số biến cho việc truy cập Trình cung cấp Từ điển Người dùng:


// A "projection" defines the columns that will be 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 mSelectionClause = null;

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

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

Tập hợp các cột mà truy vấn cần trả về được gọi là dự thảo (biến 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à tham đối lựa chọn. Mệnh đề lựa chọn là sự kết hợp giữa các biểu thức lô-gic và biểu thức Boolean, tên cột, và giá trị (biến mSelectionClause). Nếu bạn chỉ định tham số thay thế được ? thay vì một giá trị, phương pháp truy vấn sẽ truy xuất giá trị từ mảng tham đối lựa chọn (biến mSelectionArgs).

Trong đoạn mã HTML tiếp theo, nếu người dùng không điền từ thì mệnh đề lựa chọn được đặt thành null, và truy vấn 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 được đặt thành UserDictionary.Words.WORD + " = ?" và phần tử đầu tiên của mảng tham đối lựa chọn được đặt thành từ mà người dùng đã nhập.

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

// Gets a word from the UI
mSearchString = mSearchWord.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(mSearchString)) {
    // Setting the selection clause to null will return all words
    mSelectionClause = null;
    mSelectionArgs[0] = "";

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

    // Moves the user's input string to the selection arguments.
    mSelectionArgs[0] = mSearchString;

}

// 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
    mProjection,                       // The columns to return for each row
    mSelectionClause                   // Either null, or the word the user entered
    mSelectionArgs,                    // Either empty, or the string the user entered
    mSortOrder);                       // 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 may want to
     * 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 was unsuccessful. This isn't necessarily
     * an error. You may 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

}

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

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 sử dụng thay vì các hằng số lớp hợp đồng.

Bảo vệ trước mục nhập độc hại

Nếu dữ liệu được quản lý bởi trình cung cấp nội dung nằm trong một cơ sở dữ liệu SQL, việc điền dữ liệu không được tin cậy từ bên ngoài vào các câu lệnh SQL thô có thể dẫn đến tiêm lỗi SQL.

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

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

Nếu bạn làm vậy, bạn đang cho phép người dùng ghép nối SQL độc hại lên câu lệnh SQL của mình. Ví dụ, người dùng có thể điền "nothing; DROP TABLE *;" cho mUserInput, làm vậy sẽ dẫn đến mệnh đề lựa chọn var = nothing; DROP TABLE *;. Do mệnh đề lựa chọn được coi như một câu lệnh SQL, điều này có thể khiến trình cung cấp xóa tất cả bảng trong cơ sở dữ liệu SQLite cơ bản (trừ khi trình cung cấp được thiết lập để bắt những lần thử tiêm lỗi SQL).

Để tránh vấn đề này, hãy sử dụng một mệnh đề lựa chọn mà sử dụng ? làm tham số thay thế được và một mảng các tham đối lựa chọn riêng. Khi bạn làm như vậy, mục nhập của người dùng được gắn kết trực tiếp với truy vấn thay vì được giải nghĩa như một phần của câu lệnh SQL. Vì nó không được coi như SQL, mục nhập của người dùng không thể tiêm lỗi SQL độc hại. Thay vì sử dụng ghép nối để điền mục nhập của người dùng, hãy sử dụng mệnh đề lựa chọn này:

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

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

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

Đặt một giá trị trong mảng các tham đối lựa chọn như sau:

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

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

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

Phương pháp máy khách ContentResolver.query() luôn trả về một Cursor chứa các cột được chỉ định bởi dự thảo của truy vấn cho các hàng khớp với các tiêu chí lựa chọn của truy vấn. Một đối tượng Cursor cung cấp truy cập đọc ngẫu nhiên vào các hàng và cột mà nó chứa. Bằng cách sử dụng phương pháp Cursor, bạn có thể lặp lại các hàng trong kết quả, xác định kiểu 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 tính chất khác của kết quả. Một số 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, hoặc kích khởi các phương pháp trong một đối tượng quan sát khi Cursor thay đổi, hoặc cả hai.

Lưu ý: Một trình cung cấp có thể hạn chế 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ế truy cập đối với một số cột cho các trình điều hợp đồng bộ, vì thế nó sẽ không trả chúng về một hoạt động hay dịch vụ.

Nếu không hàng nào khớp với các tiêu chí lựa chọn, trình cung cấp sẽ trả về một đối tượng Cursor mà trong đó Cursor.getCount() bằng 0 (con chạy trống).

Nếu xảy ra một lỗi nội bộ, các kết quả của truy vấn sẽ phụ thuộc vào trình cung cấp cụ thể. Nó có thể chọn trả về null, hoặc nó có thể đưa ra một lỗi Exception.

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

Đoạn mã HTML sau tiếp tục từ đoạn mã HTML trước. Nó tạo một đối tượng SimpleCursorAdapter chứa Cursor được truy xuất bởi truy vấn, và đặt đối tượng này thành trình điều hợp cho một ListView:

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

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

// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);

Lưu ý: Để lùi ListView bằng một Cursor, con chạy phải chứa một cột có tên _ID. Vì điều này, truy vấn được hiện lúc trước truy xuất cột _ID cho bảng "từ" mặc dù ListView không hiển thị nó. Hạn chế này cũng giải thích lý do tại sao phần lớn trình cung cấp đều có một cột _ID cho mỗi bảng của nó.

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

Thay vì chỉ hiển thị các 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 chính tả từ một từ điển người dùng, rồi sau đó tìm kiếm từ đó trong các trình cung cấp khác. Để làm điều này, bạn lặp lại các hàng trong Cursor:


// 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 may 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 will 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.
}

Triển khai Cursor sẽ chứa một vài phương pháp “get" để truy xuất các kiểu dữ liệu khác nhau từ đối tượng. Ví dụ, đoạn mã HTML trước sử dụng getString(). Chúng cũng có một phương pháp getType() để trả về một giá trị cho biết kiểu dữ liệu của cột.

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

Ứng dụng của một trình cung cấp có thể chỉ định các quyền mà ứng dụng khác có thể có để truy cập dữ liệu của trình cung cấp đó. Những quyền này đảm bảo rằng người dùng biết một ứng dụng sẽ cố gắng truy cập dữ liệu nào. Dựa trên các yêu cầu của trình cung cấp, các ứng dụng khác yêu cầu quyền mà chúng cần để truy cập trình dữ liệu. Người dùng cuối 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 một trình cung cấp không chỉ định bất kỳ quyền nào, khi đó các ứng dụng khác không có quyền truy cập dữ liệu của trình cung cấp. Tuy nhiên, các thành phần trong ứng dụng của trình cung cấp luôn có đầy đủ quyền truy nhập đọc và ghi, không phụ thuộc vào các quyền được chỉ định.

Như đã lưu ý, Trình cung cấp Từ điển Người dùng sẽ yêu cầu quyền android.permission.READ_USER_DICTIONARY để truy xuất dữ liệu từ nó. Trình cung cấp có quyền android.permission.WRITE_USER_DICTIONARY riêng để chèn, cập nhật, hoặc xóa dữ liệu.

Để nhận các quyền cần để truy cập một trình cung cấp, ứng dụng yêu cầu chúng bằng một phần tử <uses-permission> trong tệp bản kê khai của nó. Khi Trình quản lý Gói Android cài đặt các ứng dụng, người dùng phải phê chuẩn tất cả quyền mà ứng dụng yêu cầu. Nếu người dùng phê chuẩn tất cả quyền, khi đó Trình quản lý Gói sẽ tiếp tục cài đặt; nếu người dùng không phê chuẩn chúng, Trình quản lý Gói sẽ hủy bỏ việc cài đặt.

Phần tử <uses-permission> sau yêu cầu quyền truy cập đọc vào 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 tới việc truy cập trình cung cấp được giải thích chi tiết hơn trong hướng dẫn Bảo mật và Quyền.

Chèn, Cập nhật, và Xóa Dữ liệu

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

Chèn dữ liệu

Để chèn dữ liệu vào một trình cung cấp, bạn gọi phương pháp ContentResolver.insert() . Phương pháp 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ã HTML nà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:

// Defines a new Uri object that receives the result of the insertion
Uri mNewUri;

...

// Defines an object to contain the new values to insert
ContentValues mNewValues = new ContentValues();

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

mNewUri = getContentResolver().insert(
    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
    mNewValues                          // the values to insert
);

Dữ liệu cho hàng mới đi vào trong một đối tượng ContentValues đơn lẻ, đối tượng này có dạng tương tự như con chạy một hàng. Các cột trong đối tượng này không cần có cùng kiểu dữ liệu, và nếu hoàn toàn không muốn chỉ định một giá trị, bạn có thể đặt một cột thành null bằng cách sử dụng ContentValues.putNull().

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

URI nội dung được trả về trong newUri sẽ xác định hàng mới thêm, có định dạng như 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 rồi thực hiện thao tác được yêu cầu trên hàng cụ thể đó.

Để nhận giá trị _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 một đối tượng ContentValues với các giá trị được cập nhật giống như cách bạn làm với việc chèn, và các tiêu chí lựa chọn giống như cách bạn làm với truy vấn. Phương pháp máy khách mà bạn sử dụng là ContentResolver.update(). Bạn chỉ cần thêm các 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 xóa các nội dung của một cột, hãy đặt giá trị thành null.

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

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

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

// Defines a variable to contain the number of updated rows
int mRowsUpdated = 0;

...

/*
 * Sets the updated value and updates the selected words.
 */
mUpdateValues.putNull(UserDictionary.Words.LOCALE);

mRowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mUpdateValues                       // the columns to update
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

Bạn cũng nên thanh lọc thông tin đầu vào của người dùng khi gọi ContentResolver.update(). Để tìm hiểu thêm về điều này, hãy đọc phần Bảo vệ trước mục nhập độc hại.

Xóa dữ liệu

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


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

// Defines a variable to contain the number of rows deleted
int mRowsDeleted = 0;

...

// Deletes the words that match the selection criteria
mRowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

Bạn cũng nên thanh lọc thông tin đầu vào của người dùng khi gọi ContentResolver.delete(). Để tìm hiểu thêm về điều này, hãy đọc phần Bảo vệ trước mục nhập độc hại.

Các Kiểu Dữ liệu của Trình cung cấp

Trình cung cấp nội dung có thể cung cấp nhiều kiểu dữ liệu khác nhau. Trình cung cấp Từ điển Người dùng chỉ cung cấp văn bản, nhưng trình cung cấp cũng có thể cung cấp các định dạng sau:

  • integer
  • long integer (long)
  • floating point
  • long floating point (double)

Một kiểu dữ liệu khác mà các trình cung cấp thường sử dụng đó là Binary Large OBject (BLOB) được triển khai như một mảng 64KB byte. Bạn có thể xem các kiểu dữ liệu có sẵn bằng cách xem các phương pháp "get" lớp Cursor.

Kiểu dữ liệu đối với mỗi cột trong một trình cung cấp thường được liệt kê trong tài liệu của trình cung cấp đó. Các kiểu dữ liệu dành 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 nó (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 kiểu dữ liệu bằng cách gọi Cursor.getType().

Trình cung cấp cũng duy trì thông tin về kiểu dữ liệu MIME cho mỗi URI nội dung mà chúng định nghĩa. Bạn có thể sử dụng thông tin về kiểu MIME để tìm hiểu xem ứng dụng của mình có thể xử lý dữ liệu mà trình cung cấp đưa ra hay không, hoặc để chọn một kiểu xử lý dựa trên kiểu MIME. Bạn thường cần kiểu MIME khi đang làm việc với một trì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 Trình cung cấp Danh bạ sử dụng các kiểu MIME để dán nhãn kiểu dữ liệu liên lạc được lưu trữ trong từng hàng. Để nhận được kiểu MIME tương ứng với một URI nội dung, hãy gọi ContentResolver.getType().

Phần Tham khảo Kiểu MIME mô tả cú pháp của cả kiểu MIME tiêu chuẩn lẫn tùy chỉnh.

Các Hình thức Truy cập Trình cung cấp Thay thế

Có ba hình thức truy cập trình cung cấp thay thế quan trọng trong 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 pháp trong lớp ContentProviderOperation, rồi sau đó áp dụng chúng với ContentResolver.applyBatch().
  • Truy vấn không đồng bộ: Bạn nên thực hiện các truy vấn trong một luồng riêng. Một cách để làm điều này đó là sử dụng một đối tượng CursorLoader. Các ví dụ trong hướng dẫn Trình tải sẽ minh họa cách làm điều này.
  • Truy cập dữ liệu thông qua ý định: Mặc dù không thể gửi một ý định trực tiếp tới một trình cung cấp, bạn có thể gửi một ý định tới ứng dụng của trình cung cấp đó, đây thường là cách tốt nhất để sửa đổi dữ liệu của trình cung cấp.

Truy cập hàng loạt và sửa đổi thông qua ý định được mô tả trong các phần sau.

Truy cập hàng loạt

Truy cập hàng loạt vào một trình cung cấp là cách hữu ích để chèn nhiều hàng, hoặc để chèn các hàng vào nhiều bảng trong cùng lệnh gọi phương pháp, hoặc nhìn chung để thực hiện một tập hợp thao tác qua các ranh giới tiến trình như một giao tác (thao tác nguyên tử).

Để truy cập một trình cung cấp trong "chế độ hàng loạt", bạn tạo một mảng đối tượng ContentProviderOperation rồi phân phối chúng tới một trình cung cấp nội dung bằng ContentResolver.applyBatch(). Bạn chuyển quyền của trình cung cấp nội dung cho phương pháp này thay vì một URI nội dung cụ thể. Điều này cho phép đối tượng ContentProviderOperation trong mảng có tác dụng đối với một bảng khác. Một lệnh gọi tới ContentResolver.applyBatch() trả về một mảng kết quả.

Mô tả lớp hợp đồng ContactsContract.RawContacts bao gồm một đoạn mã HTML thể hiện việc chèn hàng loạt. Ứng dụng mẫu Trình quản lý Danh bạ có một ví dụ về truy cập hàng loạt trong tệp nguồn ContactAdder.java của nó.

Truy cập dữ liệu thông qua ý định

Ý định có thể cho phép truy cập gián tiếp vào một trình cung cấp nội dung. Bạn cho phép người dùng truy cập 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, hoặc bằng cách nhận lại một ý đị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ó phép và cho phép người dùng được làm việc trong nó.

Được truy cập với 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 bạn không có quyền truy nhập phù hợp, bằng cách gửi một ý định tới một ứng dụng có quyền và nhận lại một ý định kết quả chứa quyền "URI". Đây là những quyền cho một URI nội dung cụ thể kéo dài tới khi hoạt động nhận chúng được hoàn thành. Ứng dụng có quyền lâu dài sẽ cấp quyền tạm thời bằng cách đặt một cờ trong ý định kết quả:

Lưu ý: Những cờ này không cấp quyền truy cập đọc và ghi nói chung cho trình cung cấp mà có quyền được chứa trong URI nội dung. Quyền truy cập này chỉ áp dụng cho chính URI đó.

Trình cung cấp sẽ định nghĩa quyền URI cho các URI nội dung trong bản kê khai của nó, bằng cách sử dụng thuộc tính android:grantUriPermission 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 này được giải thích chi tiết hơn trong hướng dẫn Bảo mật và Quyền, trong phần "Quyền URI".

Ví dụ, bạn có thể truy xuất dữ liệu cho một liên lạc trong Trình cung cấp Danh bạ, ngay cả khi bạn không có quyền READ_CONTACTS. Bạn có thể muốn thực hiện điều này trong một ứng dụng gửi thiệp mừng điện tử tới một liên lạc vào ngày sinh nhật của người đó. Thay vì yêu cầu READ_CONTACTS, là nơi cấp cho bạn quyền truy cập tất cả liên lạc của người dùng và tất cả thông tin của họ, bạn nên cho phép người dùng kiểm soát những liên lạc nào được sử dụng bởi ứng dụng của bạn. Để làm điều này, bạn sử dụng tiến trình sau:

  1. Ứng dụng của bạn gửi một ý định chứa hành động ACTION_PICK và kiểu MIME "danh bạ" CONTENT_ITEM_TYPE, bằng cách sử dụng phương pháp 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 Danh bạ, hoạt động sẽ đi đến tiền cảnh.
  3. Trong hoạt động lựa chọn, người dùng chọn một liên lạc để 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 nhằm gửi lại ứng dụng của bạn. Ý định chứa URI nội dung của liên lạc mà người dùng đã chọn, và các cờ "phụ thêm" 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 liên lạc được trỏ đến bởi URI nội dung. Sau đó, hoạt động lựa chọn gọi finish() để trả kiểm soát về ứng dụng của bạn.
  4. Hoạt động của bạn trả về tiền cảnh, và hệ thống sẽ gọi phương pháp onActivityResult() của hoạt động của bạn. Phương pháp này nhận được ý định kết quả do hoạt động lựa chọn tạo trong ứng dụng Danh bạ.
  5. Với URI nội dung từ ý định kết quả, bạn có thể đọc dữ liệu của liên lạc 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 lâu dài vào trình cung cấp trong bản kê khai của mình. Sau đó, bạn có thể nhận thông tin ngày sinh của liên lạc hoặc địa chỉ e-mail của người đó rồi gửi thiệp mừng điện tử.

Sử dụng một ứng dụng khác

Một cách đơn giản để 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 làm việc ở đó.

Ví dụ, ứng dụng Lịch chấp nhận một ý định ACTION_INSERT, nó cho phép bạn kích hoạt UI chèn của ứng dụng. Bạn có thể chuyển dữ liệu "phụ thêm" trong ý định này mà được ứng dụng sử dụng để điền trước vào UI. Vì các sự kiện định kỳ có cú pháp phức tạp, 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 với một ACTION_INSERT rồi để người dùng chèn sự kiện tại đó.

Các Lớp Hợp đồng

Lớp hợp đồng định nghĩa các hằng số sẽ giúp ứng dụng hoạt động với các URI nội dung, tên cột, hành động ý định, và các tính năng khác của một trình cung cấp nội dung. Các lớp hợp đồng không được tự động đưa vào cùng một trình cung cấp; nhà phát triển của trình cung cấp phải định nghĩa chúng rồi cung cấp chúng cho các nhà phát triển khác. Nhiều trình cung cấp được bao gồm cùng 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 các hằng số URI nội dung và tên cột. URI nội dung đối với bảng "từ" được định nghĩa 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, chúng được sử dụng trong đoạn mã HTML mẫu trong hướng dẫn này. Ví dụ, một dự thảo truy vấn có thể được định nghĩa là:

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

Một lớp hợp đồng khác là ContactsContract dành 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ã HTML mẫu. Một trong số các lớp con của nó, 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.

Tham khảo Kiểu MIME

Trình cung cấp nội dung có thể trả về các kiểu phương tiện MIME tiêu chuẩn, hoặc xâu kiểu MIME tùy chỉnh, hoặc cả hai.

Các kiểu MIME có định dạng

type/subtype

Ví dụ, kiểu MIME thông dụng text/html có kiểu text và kiểu con html. Nếu trình cung cấp trả về loại này cho một URI, điều đó có nghĩa rằng một truy vấn đang sử dụng URI đó sẽ trả về văn bản chứa thẻ HTML.

Các xâu kiểu MIME tùy chỉnh, còn gọi là kiểu MIME "theo nhà cung cấp", có các giá trị kiểukiểu con phức tạp hơn. Giá trị kiểu luôn luôn

vnd.android.cursor.dir

áp dụng cho nhiều hàng, hoặc

vnd.android.cursor.item

áp dụng cho một hàng.

Giá trị kiểu con áp dụng theo trình cung cấp. Các trình cung cấp được tích hợp trong Android thường có một kiểu con đơ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, nó đặt kiểu MIME sau trong hàng:

vnd.android.cursor.item/phone_v2

Để ý rằng giá trị kiểu con đơn giản là phone_v2.

Các nhà phát triển trình cung cấp khác có thể tạo mẫu hình kiểu con của riêng mình dựa trên quyền và tên bảng của trình cung cấp. Ví dụ, xét một trình cung cấp chứa các biểu thời gian lịch tàu. Quyền của trình cung cấp là com.example.trains, và nó chứa các bảng Line1, Line2, và Line3. Để phản hồi lại URI nội dung

content://com.example.trains/Line1

đối với bảng Line1, trình cung cấp trả về kiểu MIME

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

Để phản hồi lại URI nội dung

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

đối với hàng 5 trong bảng Line2, trình cung cấp trả về kiểu MIME

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

Hầu hết các trình cung cấp nội dung đều định nghĩa hằng số lớp hợp đồng cho các kiểu MIME mà chúng sử dụng. Ví dụ như lớp hợp đồng của Trình cung cấp Danh bạ ContactsContract.RawContacts, sẽ định nghĩa hằng số CONTENT_ITEM_TYPE cho kiểu MIME của một hàng liên lạc thô duy nhất.

Các URI nội dung đối với hàng duy nhất được mô tả trong phần URI nội dung.