page.title=Tạo một Trình cung cấp Nội dung @jd:body
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 {@link android.content.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 một trình cung cấp, hãy làm việc sau:
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.
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:
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:
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.
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:
_ID
.
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" {@link android.provider.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ơ đồ.
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 {@link android.content.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.
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
.
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 table1 và
table2, 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/table1
và
com.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.
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ả {@link android.database.Cursor}
trong một {@link android.widget.ListView} bằng cách sử dụng {@link android.widget.CursorAdapter}.
Định nghĩa {@link android.widget.CursorAdapter} yêu cầu một trong các cột trong
{@link android.database.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ừ {@link android.database.Cursor} làm nền cho
{@link android.widget.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.
Để 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 {@link android.content.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/*
content://com.example.app.provider/table2/*
:
dataset1
và dataset2
, 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 {@link android.content.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 {@link android.content.UriMatcher#addURI(String, String, int) 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 {@link android.content.UriMatcher#match(Uri)
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, {@link android.content.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 {@link android.net.Uri} và
{@link android.net.Uri.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
{@link android.net.Uri} hiện có và xây dựng các đối tượng mới.
Thực thể {@link android.content.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 {@link android.content.ContentResolver}, sau đó nó gọi ra một phương pháp cụ thể của {@link android.content.ContentProvider} để lấy quyền truy cập.
Lớp tóm tắt {@link android.content.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ừ {@link android.content.ContentProvider#onCreate() 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:
Để ý rằng những phương pháp này có cùng chữ ký như các phương pháp {@link android.content.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:
Phương pháp
{@link android.content.ContentProvider#query(Uri, String[], String, String[], String)
ContentProvider.query()} phải trả về một đối tượng {@link android.database.Cursor}, nếu không nó sẽ thất bại
, đưa ra một lỗi {@link java.lang.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ề {@link android.database.Cursor} được trả về bởi một trong các phương pháp
query()
của lớp {@link android.database.sqlite.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ể {@link android.database.Cursor}
có phương pháp {@link android.database.Cursor#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 {@link android.database.Cursor}. Ví dụ, lớp {@link android.database.MatrixCursor} sẽ triển khai một con chạy trong đó mỗi hàng là một mảng của {@link java.lang.Object}. Với lớp này, hãy sử dụng {@link android.database.MatrixCursor#addRow(Object[]) 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 {@link java.lang.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:
Phương pháp {@link android.content.ContentProvider#insert(Uri, ContentValues) 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 {@link android.content.ContentValues} . Nếu tên cột không nằm trong tham đối {@link android.content.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
{@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()}.
Phương pháp {@link android.content.ContentProvider#delete(Uri, String, String[]) 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.
Phương pháp {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[])
update()} lấy cùng tham đối {@link android.content.ContentValues} được sử dụng bởi
{@link android.content.ContentProvider#insert(Uri, ContentValues) insert()}, và
cùng tham đối selection
và selectionArgs
được sử dụng bởi
{@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} và
{@link android.content.ContentProvider#query(Uri, String[], String, String[], String)
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.
Hệ thống Android sẽ gọi {@link android.content.ContentProvider#onCreate() 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 {@link android.content.ContentProvider#onCreate() 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 {@link android.database.sqlite.SQLiteOpenHelper} mới trong {@link android.content.ContentProvider#onCreate() 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 {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase getWritableDatabase()}, nó sẽ tự động gọi ra phương pháp {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) SQLiteOpenHelper.onCreate()}.
Hai đoạn mã HTML sau minh họa tương tác giữa {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} và {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) SQLiteOpenHelper.onCreate()}. Đoạn mã HTML đầu tiên là triển khai {@link android.content.ContentProvider#onCreate() 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 {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) 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); } }
Lớp {@link android.content.ContentProvider} có hai phương pháp để trả về các kiểu MIME:
Phương pháp {@link android.content.ContentProvider#getType(Uri) getType()} trả về một {@link java.lang.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 {@link android.net.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, {@link android.content.ContentProvider#getType(Uri) 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, {@link android.content.ContentProvider#getType(Uri) getType()} sẽ trả về một kiểu MIME theo định dạng MIME riêng cho nhà cung cấp của Android:
vnd
android.cursor.item/
android.cursor.dir/
vnd.<name>
.<type>
Bạn cung cấp <name>
và <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
Nếu trình cung cấp của bạn cung cấp tệp, hãy triển khai {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}. Phương pháp này sẽ trả về một mảng {@link java.lang.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
và .gif
.
Nếu một ứng dụng gọi {@link android.content.ContentResolver#getStreamTypes(Uri, String)
ContentResolver.getStreamTypes()} bằng xâu bộ lọc image/*
(
mà là một "hình ảnh"),
khi đó phương pháp {@link android.content.ContentProvider#getStreamTypes(Uri, String)
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
{@link android.content.ContentResolver#getStreamTypes(Uri, String)
ContentResolver.getStreamTypes()} bằng xâu bộ lọc *\/jpeg
, và
{@link android.content.ContentProvider#getStreamTypes(Uri, String)
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,
{@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}
sẽ trả về null
.
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 {@link android.provider.ContactsContract} và các lớp lồng nhau của nó là các ví dụ về lớp hợp đồng.
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à:
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.
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:
android:permission
của phần tử
<provider>
.
android:readPermission
và
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
.
<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.
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
{@link android.content.Context#revokeUriPermission(Uri, int)
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ờ {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} hoặc cờ {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION}, hoặc cả hai. Những quyền này được đặt bằng phương pháp {@link android.content.Intent#setFlags(int) setFlags()}.
Nếu thuộc tính
android:grantUriPermissions
không có mặt, giả sử rằng nó là
false
.
Như các thành phần {@link android.app.Activity} và {@link android.app.Service},
một lớp con của {@link android.content.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ử:
android:name
)
android:grantUriPermssions
: Cờ quyền tạm thời.
android:permission
: Quyền đọc/ghi đơn lẻ đối với toàn bộ trình cung cấp.
android:readPermission
: Quyền đọc đối với toàn bộ trình cung cấp.
android:writePermission
: Quyền ghi đối với toàn bộ 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.
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>
.
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>
.
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 {@link android.content.Intent}. Ứng dụng không gọi bất kỳ phương pháp nào của {@link android.content.ContentResolver} hoặc {@link android.content.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.