lightbulb_outline Please take our October 2018 developer survey. Start survey

Tạo một 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. Bạn triển khai một trình cung cấp thành một hoặc nhiều lớp trong một ứng dụng Android, bên cạnh các phần tử trong tệp bản kê khai. Một trong các lớp của bạn triển khai một lớp con ContentProvider, đây là giao diện giữa trình cung cấp của bạn và các ứng dụng khác. Mặc dù mục đích của các trình cung cấp nội dung khác là cung cấp dữ liệu có sẵn cho các ứng dụng khác, dĩ nhiên bạn có thể ra lệnh cho các hoạt động trong ứng dụng của mình truy vấn và sửa đổi dữ liệu được quản lý bởi trình cung cấp của bạn.

Phần còn lại của chủ đề này là một danh sách cơ bản về các bước để xây dựng một trình cung cấp nội dung và một danh sách các API để sử dụng.

Trước khi Bạn Bắt đầu Xây dựng

Trước khi bạn bắt đầu xây dựng một trình cung cấp, hãy làm việc sau:

  1. Quyết định xem bạn có cần một trình cung cấp nội dung không. Bạn cần xây dựng một trình cung cấp nội dung nếu muốn cung cấp một hoặc nhiều tính năng sau đây:
    • 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ác gợi ý tìm kiếm tùy chỉnh bằng cách sử dụng khuôn khổ tìm kiếm.

    Bạn không cần trình cung cấp phải sử dụng một cơ sở dữ liệu SQLite nếu việc sử dụng hoàn toàn diễn ra trong ứng dụng của bạn.

  2. Nếu bạn chưa làm như vậy, hãy đọc chủ đề Nội dung Cơ bản về Trình cung cấp Nội dung để tìm hiểu thêm về trình cung cấp.

Tiếp theo, hãy làm theo những bước sau để xây dựng trình cung cấp của bạn:

  1. Thiết kế kho lưu trữ thô cho dữ liệu của bạn. Một trình cung cấp nội dung sẽ cung cấp dữ liệu theo hai cách:
    Dữ liệu tệp
    Dữ liệu mà thường đến các tệp chẳng hạn như ảnh, âm thanh, hoặc video. Lưu trữ các tệp ở không gian riêng tư trong ứng dụng của bạn. Để hồi đáp lại một yêu cầu tệp từ một ứng dụng khác, trình cung cấp của bạn có thể cung cấp một núm điều tác cho tệp.
    Dữ liệu "cấu trúc"
    Dữ liệu mà thường đến một cơ sở dữ liệu, mảng, hoặc cấu trúc tương tự. Lưu trữ dữ liệu dưới dạng tương thích với các bảng hàng cột. Hàng biểu diễn một đối tượng, chẳng hạn như một người hoặc khoản mục trong kiểm kê. Cột biểu diễn một số dữ liệu cho đối tượng, chẳng hạn như tên của một người hoặc giá của một khoản mục. Một cách thường dùng để 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 kho lưu trữ lâu dài nào. Để tìm hiểu thêm về các loại kho lưu trữ có sẵn trong hệ thống Android, hãy xem phần Thiết kế Kho lưu trữ Dữ liệu.
  2. Định nghĩa một triển khai cụ thể của lớp ContentProvider và các phương pháp được yêu cầu của nó. 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 phần Triển khai Lớp ContentProvider.
  3. Định nghĩa xâu thẩm quyền của trình cung cấp, URI nội dung của nó, và các tên cột. Nếu bạn muốn ứng dụng của trình cung cấp xử lý các ý định, hãy định nghĩa các hành động ý định, dữ liệu phụ thêm, và cờ. Đồng thời, hãy định nghĩa các quyền mà bạn sẽ yêu cầu cho những ứng dụng muốn truy cập dữ liệu của bạn. Bạn nên cân nhắc định nghĩa tất cả những giá trị này là hằng số trong một lớp riêng; sau đó, bạn có thể cho hiện lớp này ra với 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à Truy cập Dữ liệu.
  4. Thêm các nội dung tùy chọn khác, chẳng hạn như dữ liệu mẫu hoặc triển khai AbstractThreadedSyncAdapter mà có thể đồng bộ hoá dữ liệu giữa trình cung cấp và dữ liệu nền đám mây.

Thiết kế Kho lưu trữ Dữ liệu

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

Có một số công nghệ lưu trữ dữ liệu có sẵn trong Android:

  • Hệ thống Android bao gồm một API cơ sở dữ liệu SQLite mà các trình cung cấp của chính Androi sử dụng để lưu trữ dữ liệu theo định hướng bảng. Lớp SQLiteOpenHelper giúp bạn tạo cơ sở dữ liệu, và lớp SQLiteDatabase là lớp cơ bản để đánh giá các cơ sở dữ liệu.

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

  • Để lưu trữ dữ liệu tệp, Android có nhiều API định hướng tệp khác nhau. Để tìm hiểu thêm về lưu trữ tệp, hãy đọc chủ đề Kho lưu trữ Dữ liệu. Nếu bạn đang thiết kế một trình cung cấp dữ liệu liên quan tới phương tiện chẳng hạn như nhạc hay video, bạn có thể có một trình cung cấp cho phép kết hợp dữ liệu bảng và các tệp.
  • Để làm việc với dữ liệu trên nề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 trên nền mạng với một kho lưu trữ dữ liệu cục bộ chẳng hạn như một cơ sở dữ liệu, rồi cung cấp dữ liệu dưới dạng bảng hoặc tệp. Ứng dụng mẫu Trình điều hợp Đồng bộ Mẫu minh họa loại đồng bộ hoá này.

Những nội dung cần xem xét khi thiết kế dữ liệu

Sau đây là một số mẹo để thiết kế cấu trúc dữ liệu cho trình cung cấp của bạn:

  • Dữ liệu bảng nên luôn có một cột "khóa chính" mà trình cung cấp duy trì như 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 với các hàng có liên quan trong các bảng khác (sử dụng nó làm "khóa ngoại"). Mặc dù bạn có thể sử dụng bất kỳ tên gọi nào cho cột này, sử dụng BaseColumns._ID là lựa chọn tốt nhất vì việc liên kết các kết quả của một truy vấn trình cung cấp với ListView đòi hỏi 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 các hình ảnh bitmap hoặc nội dung 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 rồi cung cấp nó gián tiếp thay vì lưu trữ nó trực tiếp trong một bảng. Nếu làm vậy, bạn cần báo cho người dùng trình cung cấp của bạn rằng họ cần sử dụng một phương pháp tệp ContentResolver để truy cập dữ liệu.
  • Sử dụng kiểu dữ liệu Binary Large OBject (BLOB) để lưu trữ dữ liệu có kích cỡ khác nhau hoặc có một cấu trúc thay đổi. Ví dụ, bạn có thể sử dụng cột BLOB để lưu trữ một bộ đệm giao thức hay cấu trúc JSON.

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

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 mang tính 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 hoặc tệp (đường dẫn). Phần id tùy chọn chỉ đến một hàng riêng lẻ trong một bảng. Mọi phương thức truy cập dữ liệu ContentProvider đều có một URI nội dung là một tham đối; điều này cho phép bạn xác định bảng, hàng, hoặc tệp để truy cập.

Nội dung cơ bản của URI nội dung được mô tả trong chủ đề Nội dung Cơ bản về Trình cung cấp Nội dung.

Thiết kế một thẩm quyền

Một trình cung cấp thường có một thẩm quyền duy nhất, đóng vai trò là tên nội bộ Android của nó. Để tránh xung đột với các trình cung cấp khác, bạn nên sử dụng quyền sở hữu miền Internet (đảo ngược) làm cơ sở cho thẩm quyền của trình cung cấp của mình. Vì đề xuất này cũng đúng đối với tên gói Android, bạn có thể định nghĩa thẩm quyền trình cung cấp của mình là phần mở rộng của tên gói chứa trình cung cấp. Ví dụ, nếu tên gói Android là com.example.<appname>, bạn nên cấp cho trình cung cấp của mình thẩm quyền com.example.<appname>.provider.

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

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

Xử lý ID URI nội dung

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

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

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

Kiểu mẫu URI nội dung

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

Kiểu mẫu URI nội dung sẽ so khớp các URI nội dung bằng cách sử dụng ký tự đại diện:

  • *: Khớp một xâu ký tự hợp lệ bất kỳ với chiều dài bất kỳ.
  • #: Khớp một xâu ký tự số có chiều dài bất kỳ.

Lấy một ví dụ về thiết kế và tạo mã xử lý URI nội dung, hãy xét một trình cung cấp có thẩm quyền com.example.app.provider mà nhận ra các URI nội dung trỏ đến các bảng sau:

  • content://com.example.app.provider/table1: Một bảng gọi là table1.
  • content://com.example.app.provider/table2/dataset1: Một bảng gọi là dataset1.
  • content://com.example.app.provider/table2/dataset2: Một bảng gọi là dataset2.
  • content://com.example.app.provider/table3: Một bảng gọi là table3.

Trình cung cấp cũng nhận ra những URI nội dung này nếu chúng có một ID hàng được nối kèm, như ví dụ content://com.example.app.provider/table3/1 đối với hàng được nhận biết bởi 1 trong table3.

Sẽ có thể có các kiểu mẫu URI nội dung sau:

content://com.example.app.provider/*
Khớp với bất kỳ URI nội dung nào trong trình cung cấp.
content://com.example.app.provider/table2/*:
Khớp với 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/#: 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 đối với hàng được xác định bởi 6.

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

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

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher sUriMatcher;
...
    /*
     * The calls to addURI() go here, for all of the content URI patterns that the provider
     * should recognize. 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
     */
    sUriMatcher.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.
     */
    sUriMatcher.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 (sUriMatcher.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 is not recognized, you should do some error handling here.
        }
        // call the code to actually do the query
    }

Một lớp khác, ContentUris, sẽ cung cấp các phương pháp 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 pháp thuận tiện cho việc phân tích các đối tượng Uri hiện có và xây dựng các đối tượng mới.

Triển khai Lớp Trình cung cấp Nội dung

Thực thể ContentProvider quản lý truy cập vào một tập dữ liệu 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ả các dạng truy cập cuối cùng đều gọi ContentResolver, sau đó nó gọi ra một phương pháp cụ thể của ContentProvider để lấy quyền truy cập.

Phương pháp được yêu cầu

Lớp tóm tắt ContentProvider sẽ định nghĩa sáu phương pháp tóm tắt mà bạn phải triển khai như một phần lớp con cụ thể của mình. Tất cả những phương pháp này ngoại trừ onCreate() đều được gọi ra bởi một ứng dụng máy khách đang cố truy cập trình cung cấp nội dung của bạn:

query()
Truy xuất dữ liệu từ trình cung cấp của bạn. Sử dụng các tham đối để chọn bảng để truy vấn, các hàng và cột để trả về, và thứ tự sắp xếp của kết quả. Trả về dữ liệu như một đối tượng Cursor.
insert()
Chèn một hàng mới vào trình cung cấp của bạn. Sử dụng các tham đối để lựa chọn bảng đích 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 chèn.
update()
Cập nhật các hàng hiện tại trong trình cung cấp của bạn. Sử dụng các tham đối để lựa 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ố hàng được cập nhật.
delete()
Xóa hàng khỏi trình cung cấp của bạn. Sử dụng các tham đối để lựa chọn bảng và các hàng cần xóa. Trả về số hàng được xóa.
getType()
Trả về kiểu MIME tương ứng với một URI nội dung. Phương pháp này được mô tả chi tiết hơn trong phần Triển khai Kiểu MIME của Trình cung cấp Nội dung.
onCreate()
Khởi tạo trình cung cấp của bạn. Hệ thống Android sẽ gọi ra phương pháp này ngay lập tức sau khi nó tạo trình cung cấp của bạn. Để ý rằng trình cung cấp của bạn không được tạo cho đến khi đối tượng ContentResolver cố truy cập nó.

Để ý rằng những phương pháp này có cùng chữ ký như các phương pháp ContentResolver được đặt tên như nhau.

Việc bạn triển khai những phương pháp này nên xét tới các nội dung sau:

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

Triển khai phương pháp query()

Phương pháp ContentProvider.query() phải trả về một đối tượng Cursor, nếu không nó sẽ thất bại , đưa ra một lỗi Exception. Nếu bạn đang sử dụng một cơ sở dữ liệu SQLite làm kho lưu trữ dữ liệu của mình , bạn có thể chỉ cần trả về Cursor được trả về bởi một trong các phương pháp query() của lớp SQLiteDatabase. Nếu truy vấn không khớp với bất kỳ hàng nào, bạn nên trả về một thực thể Cursor có phương pháp getCount() trả về 0. Bạn chỉ nên trả về null nếu đã xảy ra một lỗi nội bộ trong tiến trình truy vấn.

Nếu bạn không đang sử dụng một cơ sở dữ liệu SQLite làm kho lưu trữ dữ liệu của mình, hãy sử dụng một trong các lớp con cụ thể của Cursor. Ví dụ, lớp MatrixCursor sẽ triển khai một con chạy trong đó mỗi hàng là một mảng của Object. Với lớp này, hãy sử dụng addRow() để thêm một hàng mới.

Nhớ rằng hệ thống Android phải có thể giao tiếp với Exception qua các ranh giới tiến trình. Android có thể làm vậy cho những trường hợp ngoại lệ sau, điều này có thể hữu ích trong xử lý lỗi truy vấn:

Triển khai phương pháp insert()

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

Phương pháp này sẽ trả về URI nội dung cho hàng mới. Để xây dựng điều này, hãy nối giá trị _ID của hàng mới (hay khóa chính khác) với URI nội dung của bảng bằng cách sử dụng withAppendedId().

Triển khai phương pháp delete()

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

Triển khai phương pháp update()

Phương pháp update() lấy cùng tham đối ContentValues được sử dụng bởi insert(), và cùng tham đối 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 những phương pháp này.

Triển khai phương pháp onCreate()

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

Ví dụ, nếu bạn đang sử dụng một cơ sở dữ liệu SQLite, bạn có thể tạo một đối tượng SQLiteOpenHelper mới trong ContentProvider.onCreate(), rồi tạo các bảng SQL lần đầu tiên khi bạn mở cơ sở dữ liệu. Để tạo điều kiện cho điều này, lần đầu tiên bạn gọi getWritableDatabase(), nó sẽ tự động gọi ra phương pháp SQLiteOpenHelper.onCreate().

Hai đoạn mã HTML sau minh họa tương tác giữa ContentProvider.onCreate()SQLiteOpenHelper.onCreate(). Đoạn mã HTML đầu tiên là triển khai ContentProvider.onCreate():

public class ExampleProvider extends ContentProvider

    /*
     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
     * in a following snippet.
     */
    private MainDatabaseHelper mOpenHelper;

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

    // Holds the database object
    private SQLiteDatabase db;

    public boolean onCreate() {

        /*
         * Creates a new helper object. This method always returns quickly.
         * Notice that the database itself isn't created or opened
         * until SQLiteOpenHelper.getWritableDatabase is called
         */
        mOpenHelper = new MainDatabaseHelper(
            getContext(),        // the application context
            DBNAME,              // the name of the database)
            null,                // uses the default SQLite cursor
            1                    // the version number
        );

        return true;
    }

    ...

    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which table to open, handle error-checking, and so forth

        ...

        /*
         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
         *
         */
        db = mOpenHelper.getWritableDatabase();
    }
}

Đoạn mã HTML tiếp theo là triển khai SQLiteOpenHelper.onCreate(), bao gồm một lớp trình trợ giúp:

...
// A string that defines the SQL statement for creating a table
private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
    "main " +                       // Table's name
    "(" +                           // The columns in the table
    " _ID INTEGER PRIMARY KEY, " +
    " WORD TEXT"
    " FREQUENCY INTEGER " +
    " LOCALE TEXT )";
...
/**
 * Helper class that actually creates and manages the provider's underlying data repository.
 */
protected static final class MainDatabaseHelper extends SQLiteOpenHelper {

    /*
     * Instantiates an open helper for the provider's SQLite data repository
     * Do not do database creation and upgrade here.
     */
    MainDatabaseHelper(Context context) {
        super(context, DBNAME, null, 1);
    }

    /*
     * Creates the data repository. This is called when the provider attempts to open the
     * repository and SQLite reports that it doesn't exist.
     */
    public void onCreate(SQLiteDatabase db) {

        // Creates the main table
        db.execSQL(SQL_CREATE_MAIN);
    }
}

Triển khai Kiểu MIME của Trình cung cấp Nội dung

Lớp ContentProvider có hai phương pháp để trả về các kiểu MIME:

getType()
Một trong các phương pháp được yêu cầu mà bạn phải triển khai cho bất kỳ trình cung cấp nào.
getStreamTypes()
Một phương pháp mà bạn được dự tính sẽ triển khai nếu trình cung cấp của bạn cung cấp tệp.

Kiểu MIME cho bảng

Phương pháp getType() trả về một String theo định dạng MIME mà mô tả kiểu dữ liệu được trả về bởi tham đối URI nội dung. Tham đối Uri có thể là một mẫu hình thay vì một URI cụ thể; trong trường hợp này, bạn nên trả về kiểu dữ liệu được liên kết với các URI nội dung mà khớp với mẫu hình đó.

Đối với các kiểu dữ liệu phổ biến như văn bản, HTML, hay JPEG, getType() sẽ trả về kiểu MIME tiêu chuẩn cho dữ liệu đó. Một danh sách đầy đủ về những kiểu tiêu chuẩn này có sẵn trên trang web IANA MIME Media Types .

Đối với các URI nội dung mà trỏ tới một hàng hoặc các hàng của bảng dữ liệu, getType() sẽ trả về một kiểu MIME theo định dạng MIME riêng cho nhà cung cấp của Android:

  • Bộ phận kiểu: vnd
  • Bộ phận kiểu con:
    • Nếu mẫu hình URI áp dụng cho một hàng đơn: android.cursor.item/
    • Nếu mẫu hình URI áp dụng cho nhiều hơn một hàng: android.cursor.dir/
  • Bộ phận riêng theo trình cung cấp: vnd.<name>.<type>

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

Ví dụ, nếu thẩm quyền của một trình cung cấp là com.example.app.provider, và nó làm hiện ra một bảng có tên table1 thì kiểu 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 đơn của table1, kiểu MIME là:

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

Kiểu MIME cho tệp

Nếu trình cung cấp của bạn cung cấp tệp, hãy triển khai getStreamTypes(). Phương pháp này sẽ trả về một mảng String của kiểu MIME đối với các tệp mà trình cung cấp của bạn có thể trả về cho một URI nội dung cho trước. Bạn nên lọc các kiểu MIME mà mình cung cấp bằng tham đối bộ lọc kiểu MIME, sao cho bạn chỉ trả về những kiểu MIME mà máy khách muốn xử lý.

Ví dụ, xét một trình cung cấp hình ảnh dưới dạng tệp có định dạng .jpg, .png.gif. Nếu một ứng dụng gọi ContentResolver.getStreamTypes() bằng xâu bộ lọc image/* ( mà là một "hình ảnh"), khi đó phương pháp ContentProvider.getStreamTypes() sẽ trả về mảng:

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

Nếu ứng dụng chỉ quan tâm đến các tệp .jpg, vậy nó có thể gọi ContentResolver.getStreamTypes() bằng xâu bộ lọc *\/jpeg, và ContentProvider.getStreamTypes() sẽ trả về:

{"image/jpeg"}

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

Triển khai một Lớp Hợp đồng

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

Lớp hợp đồng cũng giúp các nhà phát triển vì chúng thường có tên dễ nhớ cho các hằng số của mình, vì vậy các nhà phát triển ít có khả năng sử dụng các giá trị không đúng cho tên cột hay URI hơn. Do đó là một lớp, nó có thể chứa tài liệu Javadoc. Các môi trường phát triển tích hợp như Eclipse có thể tự động điền các tên hằng số từ lớp hợp đồng và hiển thị Javadoc cho các hằng số đó.

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

Lớp ContactsContract và các lớp lồng nhau của nó là các ví dụ về lớp hợp đồng.

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

Quyền và truy cập đối với tất cả khía cạnh trong hệ thống Android được mô tả chi tiết trong chủ đề Bảo mật và Quyền. Chủ đề Kho lưu trữ Dữ liệu cũng mô tả bảo mật và các quyền có hiệu lực cho nhiều loại kho lưu trữ khác nhau. Nói tóm lại, các điểm quan trọng là:

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

Nếu bạn muốn sử dụng các quyền của trình cung cấp nội dung để kiểm soát truy cập vào dữ liệu của mình, khi đó bạn nên 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" (ví dụ, trên một máy chủ từ xa), và bạn nên giữ các tệp và cơ sở dữ liệu riêng tư cho ứng dụng của mình.

Triển khai quyền

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

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

Danh sách sau liệt kê phạm vi các quyền của trình cung cấp, bắt đầu với các quyền áp dụng cho toàn bộ trình cung cấp rồi mới đến các quyền chi tiết hơn. Các quyền chi tiết hơn được ưu tiên so với các quyền có phạm vi rộng hơn:

Quyền đọc-ghi đơn lẻ ở cấp trình cung cấp
Một quyền kiểm soát cả quyền truy cập đọc và ghi cho toàn bộ trình cung cấp, được quy định bằng thuộc tính android:permission của phần tử <provider>.
Quyền đọc ghi tách riêng ở cấp độ trình cung cấp
Một quyền đọc và một quyền ghi cho toàn bộ trình cung cấp. Bạn chỉ định chúng bằng các thuộc tính android:readPermission android:writePermission của phần tử <provider>. Chúng được ưu tiên so với quyền được yêu cầu bởi android:permission.
Quyền ở cấp đường dẫn
Quyền đọc, ghi, hoặc đọc/ghi cho một URI nội dung trong trì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 một phần tử con <path-permission> của phần tử <provider>. Với mỗi một URI nội dung mà bạn chỉ định, bạn có thể chỉ định một quyền đọc/ghi, quyền đọc, hoặc quyền ghi, hoặc cả ba. Quyền đọc và quyền ghi được ưu tiên so với quyền đọc/ghi. Đồng thời, quyền ở cấp độ đường dẫn sẽ được ưu tiên so với quyền ở cấp độ trình cung cấp.
Quyền tạm thời
Là cấp độ quyền cho phép 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. Tính năng truy cập tạm thời làm giảm số quyền mà một ứng dụng phải yêu cầu trong bản kê khai của mình. Khi bạn dùng đến các quyền tạm thời, những ứng dụng duy nhất mà cần quyền "lâu dài" cho trình cung cấp của bạn là những ứng dụng liên tục truy cập tất cả dữ liệu của bạn.

Xét các quyền bạn cần để triển khai một trình cung cấp và ứng dụng e-mail khi bạn muốn cho phép một ứng dụng trình xem ảnh bên ngoài hiển thị các tài liệu đính kèm dạng ảnh từ trình cung cấp của bạn. Để cấp cho trình xem ảnh quyền truy cập cần thiết mà không cần yêu cầu quyền, hãy thiết lập các quyền tạm thời cho URI nội dung đối với ảnh. Thiết kế ứng dụng e-mail của bạn sao cho khi người dùng muốn hiển thị một ảnh, ứng dụng sẽ gửi một ý định chứa URI nội dung của ảnh và cờ cho phép tới trình xem ảnh. Trình xem ảnh khi đó có thể truy vấn trình cung cấp e-mail của bạn để truy xuất ảnh, ngay cả khi trình xem không có quyền đọc bình thường cho trình cung cấp của bạn.

Để sử dụng các quyền tạm thời, hoặc đặt thuộc tính android:grantUriPermissions của phần tử <provider> hoặc thêm một hoặc nhiều phần tử con <grant-uri-permission> vào phần tử <provider> của bạn. Nếu bạn sử dụng các quyền tạm thời, bạn phải gọi Context.revokeUriPermission() bất cứ khi nào bạn gỡ bỏ hỗ trợ cho một URI nội dung khỏi trình cung cấp của mình, và URI nội dung đó sẽ được liên kết với một quyền tạm thời.

Giá trị của thuộc tính sẽ xác định trình cung cấp của bạn được cho phép truy cập bao nhiêu. Nếu thuộc tính được đặt thành true, khi đó hệ thống sẽ cấp quyền tạm thời cho toàn bộ trình cung cấp của bạn, khống chế mọi quyền khác mà được yêu cầu bởi quyền ở cấp độ trình cung cấp hoặc cấp độ đường dẫn của bạn.

Nếu cờ này được đặt thành false, khi đó bạn phải thêm các phần tử con <grant-uri-permission> vào phần tử <provider> của mình. Mỗi phần tử con lại quy định URI nội dung hoặc các URI mà truy cập tạm thời được cấp cho.

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

Nếu thuộc tính android:grantUriPermissions không có mặt, giả sử rằng nó là false.

Phần tử <provider>

Như các thành phần ActivityService, một lớp con của ContentProvider phải được định nghĩa trong tệp bản kê khai cho ứng dụng của nó bằng cách sử dụng phần tử <provider>. Hệ thống Android nhận thông tin sau từ phần tử:

Thẩm quyền (android:authorities)
Các tên biểu tượng nhận biết toàn bộ trình cung cấp trong hệ thống. Thuộc tính 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 trình cung cấp ( android:name )
Lớp triển khai ContentProvider. Lớp này được mô tả chi tiết hơn trong phần Triển khai Lớp Trình cung cấp Nội dung.
Quyền
Những thuộc tính quy định quyền mà các ứng dụng khác phải có để truy cập dữ liệu của trình cung cấp:

Các quyền và thuộc tính tương ứng của chúng được mô tả chi tiết hơn trong 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
Những thuộc tính này xác định cách và thời điểm hệ thống Android khởi động trình cung cấp, các đặc tính tiến trình của trình cung cấp, và các thiết đặt về thời gian chạy:
  • android:enabled: Cờ cho phép hệ thống khởi động trình cung cấp.
  • android:exported: Cờ cho phép các ứng dụng sử dụng trình cung cấp này.
  • android:initOrder: Thứ tự mà trình cung cấp nên được khởi động, so với các trình cung cấp khác trong cùng tiến trình.
  • android:multiProcess: Cờ cho phép hệ thống khởi động trình cung cấp trong cùng tiến trình như máy khách gọi.
  • android:process: Tên của tiến trình mà trình cung cấp nên chạy trong đó.
  • android:syncable: Cờ cho biết rằng dữ liệu của trình cung cấp sẽ được đồng bộ với dữ liệu trên một máy chủ.

Các thuộc tính được lập tài liệu theo dõi đầy đủ trong chủ đề hướng dẫn nhà phát triển đối với phần tử <provider> .

Các thuộc tính thông tin
Một biểu tượng tùy chọn và nhãn cho trình cung cấp:
  • android:icon: Một tài nguyên có thể vẽ chứa một biểu tượng cho trình cung cấp. Biểu tượng xuất hiện bên cạnh nhãn của trình cung cấp trong danh sách ứng dụng trong Settings > Apps > All.
  • android:label: Một nhãn thông tin mô tả trình cung cấp hoặc dữ liệu của nó, hoặc cả hai. Nhãn xuất hiện trong danh sách ứng dụng trong Settings > Apps > All.

Các thuộc tính được lập tài liệu theo dõi đầy đủ trong chủ đề hướng dẫn nhà phát triển đối với phần tử <provider>.

Ý định và Truy cập Dữ liệu

Các ứng dụng có thể gián tiếp truy cập một trình cung cấp nội dung bằng một Intent. Ứng dụng không gọi bất kỳ phương pháp nào của ContentResolver hoặc ContentProvider. Thay vào đó, nó sẽ gửi một ý định để bắt đầu một hoạt động, đây thường là một bộ phận trong ứng dụng của chính trình cung cấp. Hoạt động đích phụ trách truy xuất và hiển thị dữ liệu trong UI của nó. Tùy vào hành động trong ý định, hoạt động đích cũng có thể nhắc người dùng thực hiện sửa đổi dữ liệu của trình cung cấp. Một ý định cũng có thể chứa dữ liệu "phụ thêm" mà hoạt động đích hiển thị trong UI; khi đó người dùng có tùy chọn thay đổi dữ liệu này trước khi sử dụng nó để sửa đổi dữ liệu trong trình cung cấp.

Bạn có thể muốn sử dụng truy cập ý định để giúp đảm bảo toàn vẹn dữ liệu. Trình cung cấp của bạn có thể phụ thuộc vào việc chèn, cập nhật và xóa dữ liệu theo lô-gic nghiệp vụ được quy định chặt chẽ. Trong trường hợp như vậ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 các nhà phát triển sử dụng truy cập ý định, hãy đảm bảo lập tài liệu theo dõi nó thật kỹ. Giải thích với họ tại sao truy cập ý định sử dụng UI ứng dụng của chính bạn lại tốt hơn là cố gắng sửa đổi dữ liệu bằng mã của họ.

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