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ặcCursorLoader
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:
-
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.
-
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. - 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.
-
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.net
vàandroid.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ộtListView
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, table1 và
table2, 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/table1
và
com.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ọidataset1
-
content://com.example.app.provider/table2/dataset2
: một bảng được gọidataset2
-
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
dataset1
vàdataset2
, nhưng không khớp với URI nội dung chotable1
hoặctable3
. -
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ởi6
.
Đ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 Uri
và
Uri.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:
-
IllegalArgumentException
. Bạn có thể chọn gửi thông tin này nếu nhà cung cấp của bạn sẽ nhận được URI nội dung không hợp lệ. -
NullPointerException
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ố selection
và selectionArgs
được sử dụng bởi
delete()
và
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()
và
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/
-
Nếu mẫu URI dành cho một hàng duy nhất:
-
Phần dành riêng cho nhà cung cấp:
vnd.<name>
.<type>
Bạn cung cấp
<name>
và<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 *\/jpeg
và
getStreamTypes()
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 khai và có 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ínhandroid: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ệnContext.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ứcsetFlags()
.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 Activity
và Service
,
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:
-
android:grantUriPermissions
: cờ quyền tạm thời -
android:permission
: quyền đọc/ghi duy nhất trên toàn nhà cung cấp -
android:readPermission
: quyền đọc trên toàn nhà cung cấp -
android:writePermission
: quyền ghi trên toàn 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.