Tạo một 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. Bạn triển khai một nhà cung cấp dưới dạng một hoặc nhiều lớp trong một ứng dụng Android, cùng với các phần tử trong tệp kê khai. Một trong các lớp của bạn triển khai một lớp con của ContentProvider là giao diện giữa ứng dụng nhà cung cấp của bạn và các ứng dụng khác.

Mặc dù mục đích của trình cung cấp nội dung là cung cấp dữ liệu cho các ứng dụng, bạn có thể có các hoạt động trong ứng dụng của mình cho phép người dùng và sửa đổi dữ liệu do nhà cung cấp của bạn quản lý.

Trang này chứa quy trình cơ bản để tạo trình cung cấp nội dung và danh sách để sử dụng.

Trước khi bắt đầu tạo bản dựng

Trước khi bắt đầu tạo một nhà cung cấp, hãy cân nhắc những điều sau:

  • Quyết định xem bạn có cần một nhà cung cấp nội dung hay không. Bạn cần xây dựng một nội dung nhà cung cấp nếu bạn muốn cung cấp một hoặc nhiều tính năng sau:
    • Bạn muốn cung cấp dữ liệu hoặc tệp phức tạp cho các ứng dụng khác.
    • Bạn muốn cho phép người dùng sao chép dữ liệu phức tạp từ ứng dụng của bạn vào các ứng dụng khác.
    • Bạn muốn cung cấp cụm từ tìm kiếm được đề xuất tuỳ chỉnh bằng khung tìm kiếm.
    • Bạn muốn hiển thị dữ liệu ứng dụng của mình cho các tiện ích.
    • Bạn muốn triển khai AbstractThreadedSyncAdapter, CursorAdapter hoặc CursorLoader khác.

    Bạn không cần nhà cung cấp để sử dụng cơ sở dữ liệu hoặc các loại bộ nhớ liên tục nếu việc sử dụng hoàn toàn nằm trong ứng dụng của riêng bạn và bạn không cần bất kỳ tính năng nào nêu trên. Thay vào đó, bạn có thể sử dụng một trong các hệ thống lưu trữ được mô tả trong Tổng quan về lưu trữ dữ liệu và tệp.

  • Nếu bạn chưa đọc, hãy đọc Thông tin cơ bản về trình cung cấp nội dung để tìm hiểu thêm về trình cung cấp và cách hoạt động của họ.

Tiếp theo, hãy làm theo các bước sau để tạo nhà cung cấp của bạn:

  1. Thiết kế bộ nhớ thô cho dữ liệu của bạn. Trình cung cấp nội dung cung cấp dữ liệu theo hai cách:
    Dữ liệu tệp
    Dữ liệu thường được lưu vào các tệp, chẳng hạn như ảnh, âm thanh hoặc video. Lưu trữ tệp ở chế độ riêng tư trong ứng dụng của bạn . Để phản hồi yêu cầu về một tệp từ một ứng dụng khác, có thể cung cấp tên người dùng cho tệp.
    "Có cấu trúc" dữ liệu
    Dữ liệu thường đi vào cơ sở dữ liệu, mảng hoặc cấu trúc tương tự. Lưu trữ dữ liệu ở dạng tương thích với bảng hàng và cột. Một hàng đại diện cho một thực thể, chẳng hạn như người hoặc mặt hàng trong kho hàng. Một cột biểu thị một số dữ liệu về pháp nhân, chẳng hạn như tên hoặc giá của mặt hàng. Một cách phổ biến để lưu trữ loại dữ liệu này là trong cơ sở dữ liệu SQLite, nhưng bạn có thể sử dụng bất kỳ loại dữ liệu nào bộ nhớ liên tục. Để tìm hiểu thêm về các loại bộ nhớ có sẵn trong hệ thống Android, hãy xem Thiết kế bộ nhớ dữ liệu.
  2. Xác định cách triển khai cụ thể của lớp ContentProvider và các phương thức bắt buộc. Lớp này là giao diện giữa dữ liệu của bạn và phần còn lại của Hệ thống Android. Để biết thêm thông tin về lớp này, hãy xem Triển khai phần ContentProvider.
  3. Xác định chuỗi uỷ quyền, URI nội dung và tên cột của trình cung cấp. Nếu bạn muốn ứng dụng của trình cung cấp để xử lý ý định, cũng như xác định các thao tác theo ý định, dữ liệu bổ sung, và cờ. Đồng thời, hãy xác định các quyền bạn yêu cầu cho các ứng dụng muốn để truy cập vào dữ liệu của bạn. Hãy cân nhắc định nghĩa tất cả các giá trị này dưới dạng hằng số trong một hợp đồng riêng biệt. Sau đó, bạn có thể hiển thị lớp này cho các nhà phát triển khác. Để biết thêm thông tin về URI nội dung, hãy xem Phần Thiết kế URI nội dung. Để biết thêm thông tin về ý định, hãy xem Phần Ý định và quyền truy cập dữ liệu.
  4. Thêm các phần không bắt buộc khác, chẳng hạn như dữ liệu mẫu hoặc mã triển khai có thể đồng bộ hoá dữ liệu giữa AbstractThreadedSyncAdapter nhà cung cấp và dữ liệu trên đám mây.

Thiết kế bộ nhớ dữ liệu

Trình cung cấp nội dung là giao diện đối với dữ liệu được lưu ở định dạng có cấu trúc. Trước khi tạo giao diện, quyết định cách lưu trữ dữ liệu. Bạn có thể lưu trữ dữ liệu bằng bất kỳ hình thức nào rồi thiết kế giao diện để đọc và ghi dữ liệu nếu cần.

Dưới đây là một số công nghệ lưu trữ dữ liệu có trên Android:

  • Nếu bạn đang làm việc với dữ liệu có cấu trúc, hãy cân nhắc sử dụng một cơ sở dữ liệu quan hệ như dưới dạng SQLite hoặc kho dữ liệu khoá-giá trị không có quan hệ, chẳng hạn như levelDB. Nếu bạn đang làm việc với dữ liệu phi cấu trúc như âm thanh, hình ảnh hoặc phương tiện video, thì hãy cân nhắc việc lưu trữ dưới dạng tệp. Bạn có thể kết hợp nhiều loại bộ nhớ rồi để chúng xuất hiện thông qua một trình cung cấp nội dung nếu cần.
  • Hệ thống Android có thể tương tác với thư viện Room về dữ liệu cố định. Thư viện này cung cấp quyền truy cập vào API cơ sở dữ liệu SQLite mà các nhà cung cấp của Android cung cấp sử dụng để lưu trữ dữ liệu hướng bảng. Để tạo cơ sở dữ liệu bằng thư viện, tạo thực thể cho một lớp con của RoomDatabase, như được mô tả trong Lưu dữ liệu trong cơ sở dữ liệu cục bộ bằng Room.

    Bạn không phải sử dụng cơ sở dữ liệu để triển khai kho lưu trữ. Một nhà cung cấp xuất hiện bên ngoài dưới dạng một tập hợp bảng, tương tự như cơ sở dữ liệu quan hệ, nhưng đây là không phải là yêu cầu đối với việc triển khai nội bộ của nhà cung cấp.

  • Để lưu trữ dữ liệu tệp, Android có nhiều API hướng tệp. Để tìm hiểu thêm về việc lưu trữ tệp, hãy đọc Tổng quan về lưu trữ dữ liệu và tệp. Nếu bạn khi thiết kế một nhà cung cấp dữ liệu liên quan đến phương tiện truyền thông, chẳng hạn như nhạc hoặc video, bạn có thể có một nhà cung cấp kết hợp dữ liệu bảng và tệp.
  • Trong một số ít trường hợp, bạn có thể hưởng lợi từ việc triển khai nhiều trình cung cấp nội dung cho một ứng dụng duy nhất. Ví dụ: bạn có thể muốn chia sẻ một số dữ liệu với một tiện ích sử dụng một trình cung cấp nội dung và hiển thị một tập dữ liệu khác để chia sẻ với .
  • Để làm việc với dữ liệu dựa trên mạng, hãy sử dụng các lớp trong java.netandroid.net Bạn cũng có thể đồng bộ hoá dữ liệu dựa trên mạng với một dữ liệu cục bộ lưu trữ như cơ sở dữ liệu rồi cung cấp dữ liệu dưới dạng bảng hoặc tệp.

Lưu ý: Nếu bạn thực hiện thay đổi đối với kho lưu trữ nhưng không được tương thích ngược, nên bạn cần đánh dấu kho lưu trữ bằng một phiên bản mới số. Bạn cũng cần tăng số phiên bản cho ứng dụng triển khai trình cung cấp nội dung mới. Việc thực hiện thay đổi này sẽ ngăn hệ thống hạ cấp từ khiến hệ thống gặp sự cố khi cố gắng cài đặt lại ứng dụng có trình cung cấp nội dung không tương thích.

Những điều cần cân nhắc khi thiết kế dữ liệu

Dưới đây là một số mẹo để thiết kế cấu trúc dữ liệu của nhà cung cấp:

  • Dữ liệu trong bảng phải luôn có một "khoá chính" mà nhà cung cấp duy trì dưới dạng một giá trị số duy nhất cho mỗi hàng. Bạn có thể sử dụng giá trị này để liên kết hàng đến hàng trong các bảng khác (sử dụng nó làm "khoá ngoại"). Mặc dù bạn có thể sử dụng bất kỳ tên nào cho cột này, sử dụng BaseColumns._ID là tốt nhất vì liên kết các kết quả của một truy vấn nhà cung cấp với một ListView yêu cầu một trong các cột được truy xuất phải có tên _ID.
  • Nếu bạn muốn cung cấp hình ảnh bitmap hoặc các phần dữ liệu định hướng tệp rất lớn khác, hãy lưu trữ dữ liệu vào một tệp và sau đó cung cấp dữ liệu đó một cách gián tiếp thay vì lưu trữ trực tiếp trong một tệp bảng. Nếu làm như vậy, bạn cần cho người dùng của nhà cung cấp của mình biết rằng họ cần sử dụng Phương thức tệp ContentResolver để truy cập vào dữ liệu.
  • Sử dụng loại dữ liệu đối tượng lớn nhị phân (BLOB) để lưu trữ dữ liệu có kích thước khác nhau hoặc có cấu trúc khác nhau. Ví dụ: bạn có thể sử dụng cột BLOB để lưu trữ vùng đệm giao thức hoặc Cấu trúc JSON.

    Bạn cũng có thể sử dụng BLOB để triển khai bảng độc lập với giản đồ. Trong loại bảng này, bạn sẽ xác định cột khoá chính, cột loại MIME và một hoặc các cột chung chung hơn dưới dạng BLOB. Ý nghĩa của dữ liệu trong các cột BLOB được chỉ định theo giá trị trong cột loại MIME. Điều này cho phép bạn lưu trữ các loại hàng khác nhau trong cùng một bảng. "Dữ liệu" của Trình cung cấp danh bạ cái bàn ContactsContract.Data là một ví dụ về một giản đồ độc lập bảng.

Thiết kế URI nội dung

URI nội dung là một URI xác định dữ liệu trong một trình cung cấp. URI nội dung bao gồm tên tượng trưng của toàn bộ nhà cung cấp (cơ quan của tổ chức đó) và tên trỏ đến một bảng hoặc tệp (đường dẫn). Phần mã nhận dạng không bắt buộc trỏ đến một hàng riêng lẻ trong bảng. Mọi phương thức truy cập dữ liệu của ContentProvider có một URI nội dung làm đối số. Điều này cho phép bạn xác định bảng, hàng hoặc tệp cần truy cập.

Để biết thông tin về URI nội dung, hãy xem Thông tin cơ bản về trình cung cấp nội dung.

Thiết kế một cơ quan quản lý

Nhà cung cấp thường có một đơn vị quản lý duy nhất, đóng vai trò là tên nội bộ trong Android. Người nhận tránh xung đột với các nhà cung cấp khác, sử dụng quyền sở hữu miền Internet (ngược lại) làm cơ sở cho thẩm quyền của nhà cung cấp. Vì đề xuất này cũng đúng với Android tên gói, bạn có thể xác định thẩm quyền của trình cung cấp dưới dạng phần mở rộng của tên của gói chứa trình cung cấp.

Ví dụ: nếu tên gói Android của bạn là com.example.<appname>, hãy cung cấp cho nhà cung cấp của bạn cơ quan cấp chứng nhận com.example.<appname>.provider.

Thiết kế cấu trúc đường dẫn

Nhà phát triển thường tạo URI nội dung từ đơn vị có thẩm quyền bằng cách thêm các đường dẫn trỏ đến bảng riêng lẻ. Ví dụ: nếu bạn có 2 bảng, table1table2, bạn có thể kết hợp chúng với đơn vị quản lý trong ví dụ trước để có được URI nội dung com.example.<appname>.provider/table1com.example.<appname>.provider/table2. Đường dẫn không bị giới hạn cho một phân đoạn và không cần phải có bảng cho mỗi cấp của đường dẫn.

Xử lý mã nhận dạng URI nội dung

Theo quy ước, nhà cung cấp sẽ cấp quyền truy cập vào một hàng trong bảng bằng cách chấp nhận URI nội dung có giá trị mã nhận dạng cho hàng ở cuối URI. Cũng theo quy ước, nhà cung cấp khớp với giá trị mã nhận dạng cho cột _ID của bảng và thực hiện quyền truy cập theo yêu cầu đối với hàng phù hợp.

Quy ước này tạo điều kiện cho một mẫu thiết kế chung cho các ứng dụng truy cập vào trình cung cấp. Ứng dụng thực hiện một truy vấn với trình cung cấp và hiển thị Cursor kết quả trong ListView bằng cách sử dụng CursorAdapter. Định nghĩa của CursorAdapter yêu cầu một trong các cột trong Cursor thành _ID

Sau đó, người dùng chọn một trong các hàng hiển thị trên giao diện người dùng để xem hoặc sửa đổi . Ứng dụng sẽ nhận được hàng tương ứng từ Cursor sao lưu ListView, nhận giá trị _ID cho hàng này, thêm giá trị đó vào URI nội dung rồi gửi yêu cầu truy cập đến nhà cung cấp. Sau đó, nhà cung cấp có thể thực hiện truy vấn hoặc sửa đổi so với hàng chính xác mà người dùng đã chọn.

Mẫu URI nội dung

Để giúp bạn chọn hành động cần thực hiện cho URI nội dung sắp tới, API nhà cung cấp bao gồm lớp tiện lợi UriMatcher, giúp ánh xạ các mẫu URI nội dung đến giá trị số nguyên. Bạn có thể dùng các giá trị số nguyên trong câu lệnh switch chọn thao tác mong muốn cho URI nội dung hoặc URI khớp với một mẫu cụ thể.

Mẫu URI nội dung khớp với các URI nội dung bằng cách dùng ký tự đại diện:

  • * khớp với một chuỗi gồm mọi ký tự hợp lệ có độ dài bất kỳ.
  • # khớp với một chuỗi các ký tự số có độ dài bất kỳ.

Hãy xem ví dụ về cách thiết kế và lập trình để xử lý URI nội dung, hãy cân nhắc sử dụng nhà cung cấp có đơn vị quản lý com.example.app.provider nhận dạng các URI nội dung sau trỏ vào bảng:

  • content://com.example.app.provider/table1: một bảng có tên là table1.
  • content://com.example.app.provider/table2/dataset1: một bảng được gọi dataset1
  • content://com.example.app.provider/table2/dataset2: một bảng được gọi dataset2
  • content://com.example.app.provider/table3: một bảng có tên là table3.

Nhà cung cấp cũng nhận ra những URI nội dung này nếu chúng có một mã hàng được thêm vào, chẳng hạn như content://com.example.app.provider/table3/1 cho hàng được xác định bởi 1 trong table3.

Có thể có các mẫu URI nội dung sau đây:

content://com.example.app.provider/*
Khớp với mọi URI nội dung trong trình cung cấp.
content://com.example.app.provider/table2/*
So khớp một URI nội dung cho các bảng dataset1dataset2, nhưng không khớp với URI nội dung cho table1 hoặc table3.
content://com.example.app.provider/table3/#
So khớp với một URI nội dung cho các hàng đơn trong table3, chẳng hạn như content://com.example.app.provider/table3/6 cho hàng được xác định bởi 6.

Đoạn mã sau đây minh hoạ cách hoạt động của các phương thức trong UriMatcher. Mã này xử lý URI cho toàn bộ bảng khác với URI cho một một hàng bằng cách sử dụng mẫu URI nội dung content://<authority>/<path> cho bảng và content://<authority>/<path>/<id> cho các hàng đơn.

Phương thức addURI() ánh xạ một quyền truy cập và đường dẫn đến một giá trị số nguyên. Phương thức match() trả về giá trị số nguyên cho một URI. Câu lệnh switch bạn chọn giữa truy vấn toàn bộ bảng và truy vấn một bản ghi.

Kotlin

private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    /*
     * The calls to addURI() go here for all the content URI patterns that the provider
     * recognizes. For this snippet, only the calls for table 3 are shown.
     */

    /*
     * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
     * in the path.
     */
    addURI("com.example.app.provider", "table3", 1)

    /*
     * Sets the code for a single row to 2. In this case, the # wildcard is
     * used. content://com.example.app.provider/table3/3 matches, but
     * content://com.example.app.provider/table3 doesn't.
     */
    addURI("com.example.app.provider", "table3/#", 2)
}
...
class ExampleProvider : ContentProvider() {
    ...
    // Implements ContentProvider.query()
    override fun query(
            uri: Uri?,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        var localSortOrder: String = sortOrder ?: ""
        var localSelection: String = selection ?: ""
        when (sUriMatcher.match(uri)) {
            1 -> { // If the incoming URI was for all of table3
                if (localSortOrder.isEmpty()) {
                    localSortOrder = "_ID ASC"
                }
            }
            2 -> {  // If the incoming URI was for a single row
                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                localSelection += "_ID ${uri?.lastPathSegment}"
            }
            else -> { // If the URI isn't recognized,
                // do some error handling here
            }
        }

        // Call the code to actually do the query
    }
}

Java

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        /*
         * The calls to addURI() go here for all the content URI patterns that the provider
         * recognizes. For this snippet, only the calls for table 3 are shown.
         */

        /*
         * Sets the integer value for multiple rows in table 3 to one. No wildcard is used
         * in the path.
         */
        uriMatcher.addURI("com.example.app.provider", "table3", 1);

        /*
         * Sets the code for a single row to 2. In this case, the # wildcard is
         * used. content://com.example.app.provider/table3/3 matches, but
         * content://com.example.app.provider/table3 doesn't.
         */
        uriMatcher.addURI("com.example.app.provider", "table3/#", 2);
    }
...
    // Implements ContentProvider.query()
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * Choose the table to query and a sort order based on the code returned for the incoming
         * URI. Here, too, only the statements for table 3 are shown.
         */
        switch (uriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                selection = selection + "_ID = " + uri.getLastPathSegment();
                break;

            default:
            ...
                // If the URI isn't recognized, do some error handling here
        }
        // Call the code to actually do the query
    }

Một lớp khác là ContentUris cung cấp các phương thức thuận tiện để làm việc với phần id của URI nội dung. Các lớp UriUri.Builder bao gồm các phương thức thuận tiện để phân tích cú pháp hiện có Các đối tượng Uri và tạo các đối tượng mới.

Triển khai lớp ContentProvider

Thực thể ContentProvider quản lý quyền truy cập vào một tập dữ liệu có cấu trúc bằng cách xử lý yêu cầu từ các ứng dụng khác. Tất cả biểu mẫu cuối cùng sẽ gọi ContentResolver, sau đó gọi một cụ thể ContentProvider để có quyền truy cập.

Phương thức bắt buộc

Lớp trừu tượng ContentProvider xác định 6 phương thức trừu tượng mà bạn triển khai như một phần của lớp con cụ thể. Tất cả các phương pháp này, ngoại trừ onCreate() được ứng dụng gọi đang cố truy cập vào trình cung cấp nội dung của bạn.

query()
Truy xuất dữ liệu từ nhà cung cấp của bạn. Sử dụng các đối số để chọn bảng nhằm truy vấn, các hàng và cột cần trả về và thứ tự sắp xếp của kết quả. Trả về dữ liệu dưới dạng đối tượng Cursor.
insert()
Chèn một hàng mới vào mục nhà cung cấp của bạn. Sử dụng các đối số để chọn bảng đích đến và nhận các giá trị cột để sử dụng. Trả về một URI nội dung cho hàng mới được chèn.
update()
Cập nhật các hàng hiện có trong ứng dụng nhà cung cấp của bạn. Dùng các đối số để chọn bảng và hàng để cập nhật và nhận các giá trị cột được cập nhật. Trả về số lượng hàng đã cập nhật.
delete()
Xoá hàng khỏi nhà cung cấp của bạn. Dùng các đối số để chọn bảng và các hàng để xoá. Trả về số lượng hàng đã xoá.
getType()
Trả về loại MIME tương ứng với một URI nội dung. Phương pháp này được mô tả trong phần chi tiết trong phần Triển khai loại MIME của nhà cung cấp nội dung.
onCreate()
Khởi tạo trình cung cấp. Hệ thống Android gọi phương thức này ngay sau nó tạo nhà cung cấp của bạn. Nhà cung cấp của bạn chưa được tạo cho đến khi Đối tượng ContentResolver cố gắng truy cập vào đối tượng đó.

Các phương thức này có cùng chữ ký với các phương thức có tên giống hệt nhau ContentResolver phương thức.

Khi triển khai các phương thức này, bạn cần tính đến những yếu tố sau:

  • Tất cả các phương thức này, ngoại trừ onCreate() có thể được nhiều luồng gọi cùng lúc, vì vậy, các luồng này cần phải an toàn cho luồng. Để tìm hiểu về nhiều chuỗi, hãy xem Tổng quan về quy trình và luồng.
  • Tránh thực hiện các thao tác dài trong onCreate(). Hoãn các tác vụ khởi chạy cho đến khi thực sự cần thiết. Phần về cách triển khai phương thức onCreate() hãy thảo luận chi tiết hơn về vấn đề này.
  • Mặc dù bạn phải triển khai các phương thức này, mã của bạn không phải làm gì ngoại trừ sẽ trả về kiểu dữ liệu dự kiến. Ví dụ: bạn có thể ngăn các ứng dụng khác chèn dữ liệu vào một số bảng bằng cách bỏ qua lệnh gọi đến insert() trở về 0.

Triển khai phương thức query()

Chiến lược phát hành đĩa đơn Phương thức ContentProvider.query() phải trả về một đối tượng Cursor hoặc nếu phương thức này không thành công, gửi Exception. Nếu bạn đang sử dụng cơ sở dữ liệu SQLite làm dữ liệu bộ nhớ, bạn có thể trả lại Cursor do một trong Phương thức query() của lớp SQLiteDatabase.

Nếu truy vấn không khớp với hàng nào, hãy trả về một Cursor thực thể có phương thức getCount() trả về 0. Chỉ trả về null nếu xảy ra lỗi nội bộ trong quá trình truy vấn.

Nếu bạn không sử dụng cơ sở dữ liệu SQLite làm bộ nhớ dữ liệu, hãy sử dụng một trong các lớp con cụ thể trong tổng số Cursor. Ví dụ: lớp MatrixCursor triển khai con trỏ, trong đó mỗi hàng là một mảng gồm các thực thể Object. Với lớp này, sử dụng addRow() để thêm hàng mới.

Hệ thống Android phải có khả năng giao tiếp Exception xuyên qua ranh giới của quá trình. Android có thể thực hiện việc này cho các trường hợp ngoại lệ hữu ích sau đây trong việc xử lý lỗi truy vấn:

Triển khai phương thức insert()

Phương thức insert() sẽ thêm một hàng mới vào bảng thích hợp, sử dụng các giá trị trong ContentValues đối số. Nếu tên cột không có trong đối số ContentValues, bạn có thể muốn cung cấp giá trị mặc định cho mã đó trong mã nhà cung cấp hoặc trong cơ sở dữ liệu của bạn giản đồ.

Phương thức này trả về URI nội dung của hàng mới. Để tạo hàm này, hãy thêm tham số mới khoá chính của hàng, thường là giá trị _ID, cho URI nội dung của bảng, sử dụng withAppendedId()

Triển khai phương thức delete()

Phương thức delete() không phải xoá các hàng khỏi bộ nhớ dữ liệu của bạn. Nếu bạn đang sử dụng bộ điều hợp đồng bộ hoá với nhà cung cấp của bạn, hãy cân nhắc đánh dấu một hàng đã xoá bằng thao tác "xoá" gắn cờ thay vì xoá hoàn toàn hàng. Bộ điều hợp đồng bộ hoá có thể hãy kiểm tra các hàng đã xoá và loại bỏ chúng khỏi máy chủ trước khi xoá chúng khỏi trình cung cấp.

Triển khai phương thức update()

Phương thức update() lấy cùng một đối số ContentValues được sử dụng bởi insert() và cùng một đối số selectionselectionArgs được sử dụng bởi delete()ContentProvider.query(). Điều này có thể cho phép bạn sử dụng lại mã giữa các phương thức này.

Triển khai phương thức onCreate()

Hệ thống Android gọi onCreate() khi khởi động ứng dụng nhà cung cấp. Chỉ thực hiện quá trình khởi chạy chạy nhanh tác vụ trong phương thức này và trì hoãn việc tạo cơ sở dữ liệu cũng như tải dữ liệu cho đến khi trình cung cấp thực sự sẽ nhận được một yêu cầu về dữ liệu. Nếu bạn thực hiện các tác vụ dài trong onCreate(), bạn giảm tốc độ của nhà cung cấp. Đổi lại, điều này làm chậm phản hồi từ trình cung cấp đến các .

Hai đoạn mã sau đây minh hoạ sự tương tác giữa ContentProvider.onCreate() Room.databaseBuilder(). Đầu tiên đoạn mã cho biết cách triển khai ContentProvider.onCreate() nơi đối tượng cơ sở dữ liệu được tạo và xử lý các đối tượng truy cập dữ liệu được tạo:

Kotlin

// Defines the database name
private const val DBNAME = "mydb"
...
class ExampleProvider : ContentProvider() {

    // Defines a handle to the Room database
    private lateinit var appDatabase: AppDatabase

    // Defines a Data Access Object to perform the database operations
    private var userDao: UserDao? = null

    override fun onCreate(): Boolean {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build()

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.userDao

        return true
    }
    ...
    // Implements the provider's insert method
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Java

public class ExampleProvider extends ContentProvider

    // Defines a handle to the Room database
    private AppDatabase appDatabase;

    // Defines a Data Access Object to perform the database operations
    private UserDao userDao;

    // Defines the database name
    private static final String DBNAME = "mydb";

    public boolean onCreate() {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build();

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.getUserDao();

        return true;
    }
    ...
    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Triển khai các loại MIME ContentProvider

Lớp ContentProvider có hai phương thức để trả về loại MIME:

getType()
Một trong các phương thức bắt buộc mà bạn triển khai cho mọi nhà cung cấp.
getStreamTypes()
Phương thức mà bạn dự kiến sẽ triển khai nếu nhà cung cấp của bạn cung cấp tệp.

Loại MIME cho bảng

Phương thức getType() trả về một String ở định dạng MIME mô tả loại dữ liệu mà nội dung trả về Đối số URI. Đối số Uri có thể là một mẫu thay vì một URI cụ thể. Trong trường hợp này, hãy trả về loại dữ liệu được liên kết với URI nội dung khớp với .

Đối với các loại dữ liệu phổ biến như văn bản, HTML hoặc JPEG, getType() trả về giá trị chuẩn Loại MIME cho dữ liệu đó. Danh sách đầy đủ các loại tiêu chuẩn này có trên Loại nội dung đa phương tiện IANA MIME của bạn.

Đối với các URI nội dung trỏ đến một hàng hoặc các hàng dữ liệu trong bảng, Trả lại hàng với mức phí getType() loại MIME ở định dạng MIME dành riêng cho nhà cung cấp của Android:

  • Phần kiểu: vnd
  • Phần loại phụ:
    • Nếu mẫu URI dành cho một hàng duy nhất: android.cursor.item/
    • Nếu mẫu URI dành cho nhiều hàng: android.cursor.dir/
  • Phần dành riêng cho nhà cung cấp: vnd.<name>.<type>

    Bạn cung cấp <name><type>. Giá trị <name> là duy nhất trên toàn hệ thống, và giá trị <type> là duy nhất cho URI tương ứng . Lựa chọn phù hợp cho <name> là tên công ty bạn hoặc một số phần tên gói Android của ứng dụng. Lựa chọn tốt cho <type> là một chuỗi xác định bảng liên kết với URI.

Ví dụ: nếu thẩm quyền của nhà cung cấp là com.example.app.provider để hiển thị một bảng có tên table1, loại MIME cho nhiều hàng trong table1 là:

vnd.android.cursor.dir/vnd.com.example.provider.table1

Đối với một hàng table1, loại MIME là:

vnd.android.cursor.item/vnd.com.example.provider.table1

Loại MIME cho tệp

Nếu nhà cung cấp của bạn cung cấp tệp, hãy triển khai getStreamTypes(). Phương thức này trả về một mảng String gồm các loại MIME cho các tệp mà nhà cung cấp của bạn có thể trả về cho một URI nội dung nhất định. Lọc các loại MIME mà bạn cung cấp theo loại MIME đối số bộ lọc để bạn chỉ trả về các loại MIME mà ứng dụng muốn xử lý.

Ví dụ: hãy xem xét một nhà cung cấp hình ảnh dưới dạng tệp JPG, PNG và GIF. Nếu ứng dụng gọi ContentResolver.getStreamTypes() bằng chuỗi bộ lọc image/*, thì đối với giá trị nào đó là "hình ảnh" thì phương thức ContentProvider.getStreamTypes() sẽ trả về mảng:

{ "image/jpeg", "image/png", "image/gif"}

Nếu chỉ quan tâm đến tệp JPG, ứng dụng có thể gọi ContentResolver.getStreamTypes() với chuỗi bộ lọc *\/jpeggetStreamTypes() trả về:

{"image/jpeg"}

Nếu nhà cung cấp của bạn không cung cấp bất kỳ loại MIME nào được yêu cầu trong chuỗi bộ lọc, getStreamTypes() sẽ trả về null.

Triển khai một lớp hợp đồng

Lớp hợp đồng là lớp public final chứa các định nghĩa không đổi cho URI, tên cột, loại MIME và siêu dữ liệu khác liên quan đến nhà cung cấp. Lớp thiết lập hợp đồng giữa nhà cung cấp và các ứng dụng khác bằng cách đảm bảo rằng nhà cung cấp có thể được truy cập chính xác ngay cả khi có thay đổi đối với các giá trị thực tế của URI, tên cột, v.v.

Lớp hợp đồng cũng giúp ích cho nhà phát triển vì lớp này thường có tên dễ ghi nhớ cho các hằng số, nhờ đó, nhà phát triển ít có khả năng sử dụng giá trị không chính xác cho tên cột hoặc URI. Vì đó là , lớp này có thể chứa tài liệu Javadoc. Môi trường phát triển tích hợp như Android Studio có thể tự động hoàn thành tên hằng số từ lớp hợp đồng và hiển thị Javadoc cho hằng số.

Nhà phát triển không thể truy cập vào tệp lớp của lớp hợp đồng từ ứng dụng, nhưng họ có thể biên dịch tĩnh tệp đó vào ứng dụng từ tệp JAR mà bạn cung cấp.

Ví dụ về lớp ContactsContract và các lớp lồng ghép các lớp hợp đồng.

Triển khai quyền của trình cung cấp nội dung

Quyền và quyền truy cập vào mọi khía cạnh của hệ thống Android được mô tả chi tiết trong Mẹo bảo mật. Bài viết Tổng quan về lưu trữ dữ liệu và tệp cũng mô tả tính bảo mật và các quyền đang có hiệu lực đối với nhiều loại hình lưu trữ. Tóm lại, những điểm quan trọng sau đây:

  • Theo mặc định, các tệp dữ liệu được lưu trữ trên bộ nhớ trong của thiết bị chỉ dành cho bạn ứng dụng và nhà cung cấp.
  • SQLiteDatabase cơ sở dữ liệu mà bạn tạo sẽ ở chế độ riêng tư ứng dụng và nhà cung cấp.
  • Theo mặc định, các tệp dữ liệu mà bạn lưu vào bộ nhớ ngoài sẽ ở chế độ công khaicó thể đọc được. Bạn không thể sử dụng nhà cung cấp nội dung để hạn chế quyền truy cập vào các tệp trong bộ nhớ ngoài, vì các ứng dụng khác có thể sử dụng các lệnh gọi API khác để đọc và ghi chúng.
  • Phương thức này gọi để mở hoặc tạo tệp hoặc cơ sở dữ liệu SQLite trên bộ nhớ trong của thiết bị có thể cấp quyền đọc và ghi cho tất cả các ứng dụng khác. Nếu bạn sử dụng một tệp hoặc cơ sở dữ liệu nội bộ làm kho lưu trữ của nhà cung cấp và bạn cung cấp "có thể đọc được trên thế giới" hoặc "có thể ghi vào thế giới" và các quyền mà bạn đặt cho nhà cung cấp của mình trong tệp kê khai của tệp kê khai không bảo vệ dữ liệu của bạn. Quyền truy cập mặc định đối với các tệp và cơ sở dữ liệu trong bộ nhớ trong là "riêng tư"; đừng thay đổi thông tin này đối với kho lưu trữ của nhà cung cấp.

Nếu bạn muốn sử dụng quyền của trình cung cấp nội dung để kiểm soát quyền truy cập vào dữ liệu của mình, hãy lưu trữ dữ liệu của mình trong các tệp nội bộ, cơ sở dữ liệu SQLite hoặc đám mây, chẳng hạn như trên máy chủ từ xa, đồng thời giữ riêng tư cho các tệp và cơ sở dữ liệu đối với ứng dụng của bạn.

Triển khai quyền

Theo mặc định, tất cả các ứng dụng đều có thể đọc hoặc ghi vào nhà cung cấp của bạn, ngay cả khi dữ liệu cơ bản là riêng tư vì theo mặc định nhà cung cấp của bạn chưa thiết lập các quyền. Để thay đổi chế độ cài đặt này, thiết lập quyền cho ứng dụng nhà cung cấp trong tệp kê khai, bằng cách sử dụng các thuộc tính hoặc phần tử con phần tử của phần tử <provider>. Bạn có thể đặt các quyền áp dụng cho toàn bộ nhà cung cấp, vào một số bảng, vào một số bản ghi nhất định hoặc cả ba bản ghi.

Bạn xác định các quyền cho nhà cung cấp của mình bằng một hoặc nhiều quyền Phần tử <permission> trong tệp kê khai. Để giúp quyền dành riêng cho nhà cung cấp của bạn, hãy sử dụng tính năng xác định phạm vi kiểu Java cho Thuộc tính android:name. Ví dụ: đặt tên cho quyền đọc com.example.app.provider.permission.READ_PROVIDER.

Danh sách sau đây mô tả phạm vi quyền của nhà cung cấp, bắt đầu bằng quyền áp dụng cho toàn bộ nhà cung cấp rồi sau đó chi tiết hơn. Các quyền chi tiết hơn sẽ được ưu tiên so với các quyền có phạm vi lớn hơn.

Quyền đọc-ghi ở cấp nhà cung cấp
Một quyền kiểm soát cả quyền đọc và ghi đối với toàn bộ trình cung cấp, được chỉ định với thuộc tính android:permission của Phần tử <provider>.
Phân tách quyền đọc và ghi ở cấp nhà cung cấp
Quyền đọc và quyền ghi đối với toàn bộ trình cung cấp. Bạn chỉ định các cột đó với android:readPermission và Thuộc tính android:writePermission của Phần tử <provider>. Các quyền này được ưu tiên hơn quyền mà android:permission.
Quyền ở cấp đường dẫn
Quyền đọc, ghi hoặc đọc/ghi đối với URI nội dung trong nhà cung cấp của bạn. Bạn chỉ định từng URI mà bạn muốn kiểm soát bằng Phần tử con <path-permission> của Phần tử <provider>. Đối với mỗi URI nội dung mà bạn chỉ định, bạn có thể chỉ định quyền đọc/ghi, quyền đọc, quyền ghi hoặc cả ba. Thông số đã đọc và quyền ghi được ưu tiên hơn quyền đọc/ghi. Ngoài ra, cấp đường dẫn sẽ được ưu tiên hơn so với quyền cấp nhà cung cấp.
Quyền tạm thời
Cấp quyền cấp quyền truy cập tạm thời vào một ứng dụng, ngay cả khi ứng dụng đó không có các quyền thường được yêu cầu. Phương thức tạm thời Tính năng truy cập giúp giảm số lượng quyền mà một ứng dụng phải yêu cầu tệp kê khai. Khi bạn bật quyền tạm thời, các ứng dụng duy nhất cần quyền cố định cho nhà cung cấp là những quyền liên tục truy cập vào dữ liệu của bạn.

Ví dụ: hãy xem xét các quyền bạn cần nếu bạn đang triển khai một ứng dụng và nhà cung cấp dịch vụ email và bạn muốn cho phép một ứng dụng xem ảnh bên ngoài hiển thị tệp đính kèm ảnh từ Google Cloud. Để cấp cho người xem hình ảnh quyền truy cập cần thiết mà không yêu cầu quyền, bạn có thể thiết lập quyền tạm thời cho URI nội dung cho ảnh.

Thiết kế ứng dụng email sao cho khi người dùng muốn hiển thị một ảnh, ứng dụng sẽ gửi ý định chứa URI nội dung của ảnh và cờ quyền cho trình xem hình ảnh. Người xem hình ảnh có thể sau đó truy vấn nhà cung cấp dịch vụ email của bạn để truy xuất ảnh, mặc dù người xem không có quyền đọc thông thường dành cho nhà cung cấp của mình.

Để bật quyền tạm thời, hãy đặt Thuộc tính android:grantUriPermissions của Phần tử <provider> hoặc thêm một hay nhiều phần tử <grant-uri-permission> phần tử con vào Phần tử <provider>. Gọi điện Context.revokeUriPermission() bất cứ khi nào bạn ngừng hỗ trợ URI nội dung có liên kết với quyền tạm thời khỏi Google Cloud.

Giá trị của thuộc tính này xác định mức độ truy cập của nhà cung cấp. Nếu bạn đặt thuộc tính này thành "true", thì hệ thống sẽ cấp quyền tạm thời quyền cho toàn bộ trình cung cấp của bạn, ghi đè mọi quyền khác bắt buộc theo quyền ở cấp nhà cung cấp hoặc cấp đường dẫn.

Nếu bạn đặt cờ này thành "false", hãy thêm <grant-uri-permission> phần tử con vào Phần tử <provider>. Mỗi phần tử con chỉ định URI nội dung hoặc Các URI được cấp quyền truy cập tạm thời.

Để uỷ quyền truy cập tạm thời vào một ứng dụng, một ý định phải chứa cờ FLAG_GRANT_READ_URI_PERMISSION, cờ FLAG_GRANT_WRITE_URI_PERMISSION hoặc cả hai. Các được đặt bằng phương thức setFlags().

Nếu không có thuộc tính android:grantUriPermissions, thuộc tính này được coi là "false"

<provider> phần tử

Giống như các thành phần ActivityService, một lớp con của ContentProvider được xác định trong tệp kê khai cho ứng dụng bằng cách sử dụng Phần tử <provider>. Hệ thống Android sẽ lấy thông tin sau từ phần tử:

Cơ quan (android:authorities)
Tên tượng trưng xác định toàn bộ trình cung cấp trong hệ thống. Chiến dịch này được mô tả chi tiết hơn trong Phần Thiết kế URI nội dung.
Tên lớp của nhà cung cấp (android:name)
Lớp triển khai ContentProvider. Lớp này là được mô tả chi tiết hơn trong Triển khai phần ContentProvider.
Quyền
Các thuộc tính chỉ định quyền mà các ứng dụng khác cần có để truy cập vào dữ liệu của nhà cung cấp:

Nội dung về quyền và các thuộc tính tương ứng được mô tả trong phần mô tả chi tiết hơn trong phần Phần Triển khai quyền của trình cung cấp nội dung.

Thuộc tính khởi động và kiểm soát
Các thuộc tính này xác định cách thức và thời điểm hệ thống Android khởi động ứng dụng nhà cung cấp, đặc điểm quy trình của trình cung cấp cũng như các chế độ cài đặt khác trong thời gian chạy:
  • android:enabled: gắn cờ cho phép hệ thống khởi động ứng dụng nhà cung cấp
  • android:exported: gắn cờ cho phép các ứng dụng khác sử dụng trình cung cấp này
  • android:initOrder: thứ tự bắt đầu của trình cung cấp này, so với các nhà cung cấp khác trong cùng một quy trình
  • android:multiProcess: gắn cờ cho phép hệ thống khởi động ứng dụng nhà cung cấp trong cùng một quy trình với ứng dụng gọi
  • android:process: tên của quy trình mà trình cung cấp chạy
  • android:syncable: cờ cho biết dữ liệu của nhà cung cấp đã đồng bộ hoá với dữ liệu trên máy chủ

Các thuộc tính này được ghi lại đầy đủ trong hướng dẫn về Phần tử <provider>.

Thuộc tính cung cấp thông tin
Biểu tượng và nhãn không bắt buộc cho nhà cung cấp:
  • android:icon: một tài nguyên có thể vẽ chứa biểu tượng cho ứng dụng nhà cung cấp. Biểu tượng xuất hiện bên cạnh nhãn của nhà cung cấp trong danh sách các ứng dụng trong Cài đặt > Ứng dụng > Tất cả.
  • android:label: nhãn thông tin mô tả nhà cung cấp hoặc cả hai. Nhãn xuất hiện trong danh sách ứng dụng trong Cài đặt > Ứng dụng > Tất cả.

Các thuộc tính này được ghi lại đầy đủ trong hướng dẫn về Phần tử <provider>.

Lưu ý: Nếu bạn đang nhắm đến Android 11 trở lên, hãy tham khảo tài liệu về chế độ hiển thị gói cho các nhu cầu định cấu hình khác.

Ý định và quyền truy cập dữ liệu

Các ứng dụng có thể truy cập gián tiếp vào trình cung cấp nội dung bằng Intent. Ứng dụng không gọi phương thức nào của ContentResolver hoặc ContentProvider Thay vào đó, mã này sẽ gửi một ý định bắt đầu một hoạt động, thường là một phần trong ứng dụng riêng của nhà cung cấp. Hoạt động đích chịu trách nhiệm truy xuất và hiển thị dữ liệu trong giao diện người dùng.

Tuỳ thuộc vào hành động trong ý định, hoạt động đích cũng có thể nhắc người dùng sửa đổi dữ liệu của trình cung cấp. Một ý định cũng có thể chứa "thông tin bổ sung" dữ liệu mà hoạt động đích hiển thị trong giao diện người dùng. Sau đó, người dùng có thể thay đổi dữ liệu này trước khi sử dụng để sửa đổi trong nhà cung cấp.

Bạn có thể sử dụng quyền truy cập ý định để giúp đảm bảo tính toàn vẹn của dữ liệu. Nhà cung cấp của bạn có thể phụ thuộc vào về việc chèn, cập nhật và xoá dữ liệu theo logic nghiệp vụ được xác định nghiêm ngặt. Nếu trong trường hợp này, việc cho phép các ứng dụng khác trực tiếp sửa đổi dữ liệu của bạn có thể dẫn đến dữ liệu không hợp lệ.

Nếu bạn muốn nhà phát triển sử dụng quyền truy cập theo ý định, hãy nhớ ghi lại kỹ lưỡng. Giải thích lý do khiến việc truy cập ý định bằng giao diện người dùng của ứng dụng lại hiệu quả hơn là cố gắng sửa đổi dữ liệu bằng mã của họ.

Việc xử lý ý định đến muốn sửa đổi dữ liệu của nhà cung cấp không khác với việc xử lý xử lý các ý định khác. Bạn có thể đọc bài viết để tìm hiểu thêm về cách sử dụng ý định Ý định và bộ lọc ý định.

Để biết thêm thông tin liên quan, hãy tham khảo Tổng quan về nhà cung cấp lịch.