Creating a Content Provider
英文原文:http://developer.android.com/guide/topics/providers/content-provider-creating.html
采集日期:2015-01-23
Content Provider 管理着數據庫的訪問工作。 依據 Manifest 中的定義, Provider 實現為 Android 應用中的一個或多個類。 其中一個類實現了 ContentProvider
的子類,作為連接 Provider 與其他應用程序的接口。 雖然 Content Provider 是用於向其他應用程序提供數據的,但在其所在的應用程序中,用戶當然也可以通過 Activity 查詢並修改 Provider 中的數據。
本文列出了建立一個 Content Provider 的基本步驟,並給出了涉及的 API。
准備工作
在建立 Provider 之前,請完成:
- 確定必要性。 創建 Content Provider 是為了實現以下目標:
- 需要向其他應用程序提供復雜的數據或文件
- 用戶需要把本應用中的數據復制給其他應用程序
- 需要利用(系統的)搜索機制提供自定義的搜索建議項
如果只是在應用程序內部使用, Provider 不一定要用到 SQLite 數據庫。
- 如果目的還不確定,請閱讀文章 Content Provider 基礎 以便對 Provider 進行更深入的了解。
接下來,按照以下步驟建立自己的 Provider:
- 設計數據的的底層存儲形式。Content Provider 可以用兩種方式提供數據:
- 文件數據
- 數據通常存放在文件中,比如圖片、音頻、視頻等。 文件存放在應用程序的私有空間中。 當其他應用程序發起訪問請求時,Provider 將給出相應的文件句柄。
- “結構化”(Structured)數據
- 數據通常存放在數據庫、數組或類似的數據結構中。 存儲格式類似於行、列組成的表格。 每行代表一個實體,比如一個人或者商品。 每列代表與實體相關的數據項,比如人的姓名或者商品的價格。 常用的存儲方式是放入 SQLite 數據庫中,但其實可以使用任何類型的持久性存儲方式。 如果要了解 Android 系統支持的存儲類型,請參閱 設計數據存儲形式一節。
- 設計自己的
ContentProvider
類,並實現必要的方法。 此類為訪問數據的途徑,更多信息請參閱 實現 ContentProvider 類 一節。 - 定義 Provider 的 authority 字符串、Content URI 及各數據列的名稱。 如果 Provider 應用需要處理 Intent,則還要定義 Intent Action、extra 數據及標志位。 並且,還要為訪問數據的應用定義權限。 建議將所有上述定義都設為常量,並定義在一個單獨的 Contract 類中,這樣只要把這個類公布給其他開發人員即可。 關於 Content URI 的詳細信息,請參閱 設計 Content URI 一節。關於 Intent 的詳細信息,請參閱 Intent 和 Data 訪問 一節。
- 填加其他的可選部分,比如示例數據、用於將 Provider 和雲端數據進行同步的
AbstractThreadedSyncAdapter
類。
設計數據存儲形式
Content Provider 是為數據提供結構化訪問途徑的接口。 在創建這個接口之前,首先必須確定數據的存儲形式。 數據可以用任何方式存儲,只要設計好必要的的讀寫接口即可。
以下列出了一些 Android 支持的數據存儲形式:
- Android 系統內置了一套 SQLite 數據庫 API,系統自帶的 Provider 就用它來存儲表格形式的數據。
SQLiteOpenHelper
是用於創建數據庫的工具類,SQLiteDatabase
是訪問數據庫的基礎類。請記住,並不是一定要用數據庫來存儲數據。 Provider 向外部展現數據的形式是與關系型數據庫類似的表格,但內部不一定非要這么去實現。
- 對於存儲文件數據, Android 也提供了一系列的文件操作 API。 要了解文件存儲的詳細信息,請參閱文章 數據存儲 。如果 Provider 需要支持多媒體數據,比如音頻和視頻文件,可以混合使用表和文件的形式來存儲數據。
- 如果要使用基於網絡的數據,請使用
java.net
和android.net
中的類。 還可以先將網絡端數據與本地數據庫進行同步,再以數據表或文件的形式提供出來。 Sync Adapter 示例 應用就給出了這種同步方式的例子。
數據設計因素
以下列出了一些設計 Provider 的數據結構時需要考慮的因素:
- 數據表通常應該包含“主鍵”, Provider 會自動維護該列,為每行數據標識一個唯一的數字值。 該值可用來與其他表中的數據行關聯(即用作“外鍵”)。 雖然主鍵的列名可以任意指定,但最好還是使用
BaseColumns._ID
,因為ListView
與 Provider 關聯時要求查詢結果中必須包含名為_ID
的列。 - 如果要提供位圖文件或其他大量的文件數據,請以文件的形式存放並由 Provider 間接地提供出來,而不是直接存放在數據表中。 這時,Provider 有必要讓使用方知道,需要通過
ContentResolver
的文件方法來訪問數據。 - 如果數據的大小不一,或者數據結構各異,則可以使用二進制大數據對象(BLOB,Binary Large OBject)類型來存放。 比如,可以用 BLOB 類型的字段來存儲 Google 協議緩沖區(protocol buffer) 和 JSON 結構的數據。
還可以用 BLOB 來實現庫結構無關(schema-independent)的表。 在這種表中,可以定義一個主鍵、一個 MIME 類型字段和多個 BLOB 之類的通用字段。 BLOB 字段中的數據含義由 MIME 類型字段來指明。 這樣,就可以在同一張表中存放多種不同類型的數據行了。 Contracts Provider 的 “data”表
ContactsContract.Data
就是庫結構無關的。
設計 Content URI
Content URI 是用於標識 Provider 數據的 URI。 Content URI 包含了整個 Provider 的名稱(authority)和某個表或文件的名稱(path)。 可選的 id 部分標明了表中的數據行索引。 ContentProvider
中所有的數據訪問方法都包含一個 Content URI 參數,指明了要訪問的表、數據行或文件。
文章 Content Provider 基礎 中介紹了 Content URI 的基礎知識。
設計 authority
Provider 通常都需要指定一個 authority ,用作 Android 內部名稱。 為了避免與其他 Provider 沖突,請使用 Internet 域名(反向)作為 Provider 的 authority 基礎。 因為這也是 Android 包(package)的命名建議,所以可在 Provider 所在包名的基礎上進行擴展來定義其 authority 。 比如,假設 Android 包名為 com.example.<appname>
, 則 Provider 的 authority 就可以定義為 com.example.<appname>.provider
。
定義 path 部分
開發人員創建 Content URi 的方式,通常是在 authority 后面添加指明了表名的 path 部分。 比如,假設已有兩張表 table1 和 table2, 則利用上述 authority 示例生成的 Content URI 即為: com.example.<appname>.provider/table1
和 com.example.<appname>.provider/table2
。 可以有多個 path 部分,每個 path 中並不一定包含表。
處理 Content URI ID
作為約定,通過在 Content URI 的末尾附帶該行數據的 ID,Provider 提供了對單行數據的訪問能力。 同樣是系統約定, Provider 會把 ID 在表的 _ID
字段中進行檢索,並在匹配的的數據行上執行所需的訪問請求。
這樣,應用程序可以按照統一的設計模式訪問 Provider 中的數據。 在向 Provider 提交查詢請求之后,應用程序就可以通過 CursorAdapter
在ListView
中顯示返回的 Cursor
了。 CursorAdapter
要求 Cursor
中必須存在一個名為_ID
的字段。
然后,用戶可以在界面中選擇需要查詢或修改的一行數據。 應用程序從 ListView
后台的 Cursor
中讀取相應的數據行,得到該行的 _ID
值,並將它添加到 Content URI 后面,再把訪問請求發送給 Provider 。 Provider 然后在用戶選中的記錄上執行查詢或修改操作。
Content URI 的匹配模式
為了便於根據傳入的 Content URI 選擇相應的 Action, Provider API 中提供了一個工具類 UriMatcher
,該類將 Content URI 映射為整數值。 這樣,就可以在 switch
語句中使用整數值來根據 Content URI 或符合匹配規則的 URI 執行所需的 Action。
Content URI 的匹配規則使用以下通配符來進行匹配:
-
*
: 匹配任意長度的任意合法字符。 -
#
: 匹配任意長度的數字字符。
舉個例子,假定有一個 authority 為 com.example.app.provider
的 Provider, 可以識別出以下 Content URI 對應的數據表:
-
content://com.example.app.provider/table1
:名為table1
的表。 -
content://com.example.app.provider/table2/dataset1
:名為dataset1
的表。 -
content://com.example.app.provider/table2/dataset2
:名為dataset2
的表。 -
content://com.example.app.provider/table3
:名為table3
的表。
Provider 還可以識別附帶記錄 ID 的 Content URI,比如 content://com.example.app.provider/table3/1
就表示 table3
中 ID 為 1
的記錄行。
以下都是合法的 Content URI 匹配規則:
-
content://com.example.app.provider/*
- 匹配 Provider 中的所有 Content URI。
-
content://com.example.app.provider/table2/*
-
匹配表
dataset1
和dataset2
的 Content URI, 但不會匹配table1
和table3
。 -
content://com.example.app.provider/table3/#
-
匹配表
table3
中的某一條記錄,比如content://com.example.app.provider/table3/6
表示 ID 為6
的記錄行。
以下代碼演示了如何使用 UriMatcher
中的方法。 這里分別演示了對全表和單條記錄 URI 的處理過程, content://<authority>/<path>
匹配全表 Content URI , content://<authority>/<path>/<id>
則表示單條記錄。
addURI()
方法將 authority 和 path 映射為一個整數值。 match()
方法將返回 URI 對應的整數值。 這里使用了 switch
語句來選擇查詢全表還是單條記錄:
1 public class ExampleProvider extends ContentProvider { 2 ... 3 // 創建 UriMatcher 對象。 4 private static final UriMatcher sUriMatcher; 5 ... 6 /* 7 * 在這里調用匹配全部 Content URI 的 addURI() 。 8 * 但本段代碼只匹配 table3。 9 */ 10 ... 11 /* 12 * 設置 table3 全表對應整數值 1。 13 * 注意這里的 path 沒有使用通配符。 14 */ 15 sUriMatcher.addURI("com.example.app.provider", "table3", 1); 16 17 /* 18 * 設置單條記錄對應數字 2。 19 * 這里使用了通配符 “#”。 20 * 所以“content://com.example.app.provider/table3/3”將符合規則,而“content://com.example.app.provider/table3”就不會匹配了。 21 */ 22 sUriMatcher.addURI("com.example.app.provider", "table3/#", 2); 23 ... 24 // 實現 ContentProvider.query() 25 public Cursor query( 26 Uri uri, 27 String[] projection, 28 String selection, 29 String[] selectionArgs, 30 String sortOrder) { 31 ... 32 /* 33 * 根據 URI 對應的整數值確定查詢表和排序方向。 34 * 這里只是給出了 table3 的有關語句。 35 */ 36 switch (sUriMatcher.match(uri)) { 37 38 39 // 如果 URI 對應的是 table3 全表 40 case 1: 41 42 if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; 43 break; 44 45 // 如果 URI 對應了單條記錄 46 case 2: 47 48 /* 49 * 既然 URI 對應單條記錄, 就應該給出 _ID 值。 50 * 讀取 URI 的 path 部分的最后一段,也即 _ID 值。 51 * 然后把它附加到查詢語句的 WHERE 條件中。 52 */ 53 selection = selection + "_ID = " uri.getLastPathSegment(); 54 break; 55 56 default: 57 ... 58 // 如果無法識別 URI,請在這里進行錯誤處理。 59 } 60 // 執行查詢操作 61 }
在 ContentUris
中,提供了一些操作 Content URI 的 id
部分的方法。在 Uri
和 Uri.Builder
類中,還提供了一些解析或新建 Uri
對象的方法。
實現 ContentProvider 類
ContentProvider
的實例對象負責處理其他應用程序的查詢請求,由此管理着對結構化數據的訪問。 各種形式的數據訪問最終都要調用 ContentResolver
,再由它調用 ContentProvider
實例對象的方法來完成。
必需實現的方法
抽象類 ContentProvider
定義了6個抽象方法,在其實例子類中必須實現這些方法。 除了 onCreate()
之外,其他所有方法都會被需要訪問 Content Provider 的客戶端應用調用。
-
query()
-
讀取 Provider 中的數據。參數中可指明表、數據行、需返回的字段及排序方向。 返回數據為一個
Cursor
對象。 -
insert()
- 在 Provider 中插入一條新記錄。 參數中指定了目標表和各字段的值。 返回新插入記錄的 Content URI。
-
update()
- 更新 Provider 中已有的記錄。 參數中指定了表、要更新的記錄、各字段值。 返回更新成功的記錄數。
-
delete()
- 刪除 Provider 中的記錄。 參數中指定了表和要刪除的記錄。 返回刪除成功的記錄數。
-
getType()
- 返回 Content URI 對應的 MIME 類型。 該方法將在 實現 Content Provider 的 MIME 類型一節中詳細介紹。
-
onCreate()
-
初始化 Provider 。Android 系統將在創建 Provider 之后立即調用該方法。 請注意,在
ContentResolver
對象發起訪問請求之前, Provider 是不會被創建的。
注意, ContentResolver
中存在與上述方法同名的對應方法。
上述方法的實現代碼應該滿足以下要求:
- 除
onCreate()
外,其他所有方法都有可能同時被多個線程調用,因此必須是線程安全的。 有關多線程的內容,請參閱文章 進程和線程。 - 請勿在
onCreate()
方法中執行耗時較長的任務。 請把初始化工作推遲到實際需要時執行。 在實現 onCreate() 方法一節中將會詳細介紹這部分內容。 - 雖然上述方法都是要求實現的,但在代碼中可以不進行任何操作,只要返回類型合適的結果即可。 比如,為了阻止其他應用在表中插入數據,可以忽略
insert()
調用,直接返回 0 即可。
實現 query() 方法
ContentProvider.query()
方法必須返回一個 Cursor
對象,在查詢失敗時將會拋出一個 Exception
。如果使用 SQLite 數據庫存儲數據,只要用 SQLiteDatabase
類的 query()
方法直接返回一個 Cursor
即可。 如果沒有找到符合條件的記錄,應該返回一個 Cursor
實例,其 getCount()
方法應該返回 0。 只有當查詢過程中發生內部錯誤時,才能返回 null
。
如果沒有用 SQLite 數據庫存儲數據,請使用 Cursor
的子類作為結果返回。 比如, MatrixCursor
類實現了一個游標,其中的每行數據是由 Object
對象組成的數組。 該類使用 addRow()
來添加新的數據行。
請記住, Android 系統有能力保證 Exception
的跨進程傳遞。 Android 可以有效傳遞以下這些與查詢出錯相關的異常:
IllegalArgumentException
(當 Provider 接收到非法的 Content URI 時,可以拋出該異常。)NullPointerException
實現 insert() 方法
insert()
方法將在目標表中添加一條新記錄,字段名稱在參數 ContentValues
中給出。如果 ContentValues
中未能給出字段名稱,可能需要在 Provider 代碼或是數據庫定義中給出缺省值。
此方法應該返回新記錄的 Content URI。利用 withAppendedId()
方法,將新記錄的 _ID
(或其他主鍵)附加在表的 Content URI 之后即可實現。
實現 delete() 方法
delete()
方法不一定要物理刪除記錄。 如果 Provider 用於 Sync Adapter,則應考慮將刪除記錄標記為“delete”,而不是真的刪除它。 Sync Adapter 可以感知這些刪除記錄,並先刪除服務器端數據,再在 Provider 中進行刪除。
實現 update() 方法
update()
方法的 ContentValues
參數與 insert()
相同, selection
和 selectionArgs
參數與 delete()
和 ContentProvider.query()
的相同。因此這些方法就可以實現代碼復用了。
實現 onCreate() 方法
Android 系統會在啟動 Provider 時調用 onCreate()
方法。在該方法中,只能執行一些能夠迅速完成的初始化工作,創建數據庫及加載數據的工作請延至確實收到查詢請求時再去執行。 如果在該方法中執行了耗時很長的任務, Provider 的啟動速度將會減緩。 這會嚴重滯緩 Provider 對其他應用的響應速度。
比如,假定使用了 SQLite 數據庫,可以在 ContentProvider.onCreate()
方法中創建一個 SQLiteOpenHelper
對象,然后在第一次打開數據庫時再創建 SQL 表。 為了方便起見,第一次調用 getWritableDatabase()
時,它會自動調用 SQLiteOpenHelper.onCreate()
方法。
以下兩段代碼演示了 ContentProvider.onCreate()
與 SQLiteOpenHelper.onCreate()
之間的交互過程。第一段代碼實現了 ContentProvider.onCreate()
:
1 public class ExampleProvider extends ContentProvider 2 3 /* 4 * 定義數據庫工具類句柄。 5 * MainDatabaseHelper 類在后面的代碼中定義。 6 */ 7 private MainDatabaseHelper mOpenHelper; 8 9 // 定義數據庫名稱 10 private static final String DBNAME = "mydb"; 11 12 // 數據庫對象 13 private SQLiteDatabase db; 14 15 public boolean onCreate() { 16 17 /* 18 * 新建一個工具類對象。 19 * 本方法應該盡快返回。 20 * 請注意數據庫本身將在調用 SQLiteOpenHelper.getWritableDatabase 時才會被創建並打開。 21 */ 22 mOpenHelper = new MainDatabaseHelper( 23 getContext(), // 應用程序上下文(Context) 24 DBNAME, // 數據庫名稱 25 null, // 使用默認 SQLite 游標 26 1 // 版本號 27 ); 28 29 return true; 30 } 31 32 ... 33 34 // 實現 Provider 的插入方法 35 public Cursor insert(Uri uri, ContentValues values) { 36 // 在這里插入代碼,選擇要打開的表、進行差錯處理等等。 37 38 ... 39 40 /* 41 * 獲取一個可寫入的數據庫。Gets a writeable database. This will trigger its creation if it doesn't already exist. 42 * 如果數據庫不存在,則會觸發創建過程。 43 */ 44 db = mOpenHelper.getWritableDatabase(); 45 } 46 }
以下代碼實現了 SQLiteOpenHelper.onCreate()
,其中包含了一個工具類:
1 ... 2 // 定義建表的 SQL 語句 3 private static final String SQL_CREATE_MAIN = "CREATE TABLE " + 4 "main " + // 表名 5 "(" + // 字段名 6 " _ID INTEGER PRIMARY KEY, " + 7 " WORD TEXT" 8 " FREQUENCY INTEGER " + 9 " LOCALE TEXT )"; 10 ... 11 /** 12 * 用於創建和管理底層數據的工具類 13 */ 14 protected static final class MainDatabaseHelper extends SQLiteOpenHelper { 15 16 /* 17 * 實例化工具類,用於操作 Provider 的 SQLite 數據庫 18 * 這里不要創建或升級數據庫。 19 */ 20 MainDatabaseHelper(Context context) { 21 super(context, DBNAME, null, 1); 22 } 23 24 /* 25 * 創建數據庫。 26 * 當 Provider 嘗試打開數據庫但 SQLite 卻報告數據庫不存在時,將會調用此方法。 27 */ 28 public void onCreate(SQLiteDatabase db) { 29 30 // 創建主表 31 db.execSQL(SQL_CREATE_MAIN); 32 } 33 }
實現 ContentProvider 的 MIME 類型
ContentProvider
類有兩個方法是用於返回 MIME 類型的:
-
getType()
- 所有 Provider 都必須實現該方法。
-
getStreamTypes()
- 如果 Provider 提供文件訪問的話,建議實現該方法。
表的 MIME 類型
getType()
方法 應返回 MIME 格式的 String
,指明了 Content URI 的數據類型。 Uri
參數不一定是准確的 URI 。如果 URI 帶有通配符,則應返回符合匹配條件的所有 URI 的數據類型。
對於常見的數據類型而言,比如文本、HTML、JPEG, getType()
應該返回標准的 MIME 類型。 所有的標准類型都在網站 IANA MIME 媒體類型 中列出了。
對於一條以上表記錄的 Content URI 而言, getType()
應該返回 Android 的廠商定義(vendor-specific) MIME 類型:
- Type 部分:
vnd
- Subtype 部分:
- 如果 URI 匹配單行記錄:
android.cursor.item/
- 如果 URI 匹配多行記錄:
android.cursor.dir/
- 如果 URI 匹配單行記錄:
- Provider-specific 部分:
vnd.<name>
.<type>
需要給出的部分是
<name>
和<type>
。<name>
值應該是全局唯一的,<type>
值應該是在 URI 匹配定義中保持唯一。 較好的建議是把公司名稱或應用程序包名稱的一部分作為<name>
值, 並把 URI 關聯的表名用作<type>
值。
比如,假定 Provider 的 authority 部分為 com.example.app.provider
, 表名為 table1
, 那么 table1
中多條記錄的 MIME 類型可以為:
vnd.android.cursor.dir/vnd.com.example.provider.table1
table1
中單行記錄的 MIME 類型可為:
vnd.android.cursor.item/vnd.com.example.provider.table1
文件的 MIME 類型
如果 Provider 提供文件訪問,請實現 getStreamTypes()
。該方法將返回 Provider 可提供文件的 MIME 類型組成的 String
數組,目標文件由 Content URI 給定。 請根據 MIME 類型過濾器參數對 MIME 類型進行過濾,以便只返回客戶端需要處理的那些 MIME 類型。
例如,假定某個 Provider 用於提供 .jpg
、.png
和 .gif
格式的照片文件。 如果另一個應用程序調用 ContentResolver.getStreamTypes()
時帶有過濾字符串 image/*
(圖片文件),那么 ContentProvider.getStreamTypes()
方法就應該返回數組:
{ "image/jpeg", "image/png", "image/gif"}
如果應用程序只會處理 .jpg
文件,那么可以在調用 ContentResolver.getStreamTypes()
時附帶過濾字符串 *\/jpeg
,這時 ContentProvider.getStreamTypes()
應該返回:
{"image/jpeg"}
如果 Provider 無法提供過濾字符串要求的 MIME 類型, getStreamTypes()
應該返回 null
。
實現合約(Contract)類
Contract 類是一種 public final
類,其中以常量的方式定義了 URI、字段名稱、MIME 類型及 Provider 用到的其他元數據(meta-data)。 該類在 Provider 和其他應用程序間建立起一種約定,這樣即使是 URI 、字段名稱等發生了變化,也能保證 Provider 可被正確訪問。
因為常量名稱通常更便於記憶, Contract 類可以幫助開發人員減少差錯,防止列名或 URI 被用錯。 由於這是一個類,所以可以包含 Javadoc 文檔。 類似 Eclipse 之類的集成開發環境可以自動完成 Contract 類中的常量名稱的填寫,並顯示常量的 Javadoc 注釋。
其他開發人員無法直接訪問 Contract 類的文件,但可以把 .jar
文件將它們靜態編譯到自己的應用中去。
ContactsContract
及其內部類就是 Contract 類的范例。
實現 Content Provider 權限
關於 Android 系統的權限控制,將在文章 安全和權限 中詳細介紹。 數據存儲 一文中也介紹了有關各種存儲形式的安全和權限知識。 簡而言之,主要包括以下幾點:
- 默認情況下,保存在內部存儲中的文件是應用程序及 Provider 的私有數據。
- 由應用程序及 Provider 創建的
SQLiteDatabase
數據庫是其私有數據。 - 默認情況下,保存在外部存儲中的文件是 公有(public) 和 全局可讀(world-readable) 的。 靠 Content Provider 無法對外部存儲中的文件進行訪問限制,因為其他應用可以通過別的 API 讀寫這些文件。
- 打開或創建文件及數據庫之類的方法會自動賦權,對內部存儲的讀寫權限都可能會開放給所有應用。 如果使用了文件或數據庫作為 Provider 的存儲方式,並給它賦予了“world-readable”或“world-writeable”權限, 那么 Manifest 文件中設置的 Provider 權限就不再會保護數據了。 內部存儲中的文件或數據庫的默認權限是“私有”的,用於 Provider 時也不應去改變它。
如果需要通過 Content Provider 來控制數據的訪問,就應該將數據保存在內部文件或 SQLite 數據庫中,或是保存到“雲端” (比如遠程服務器),並保證這些文件和數據庫是應用程序的私有數據。
實現權限
Provider 默認是沒有設置權限的,因此所有的應用程序都可以讀寫它,即便其底層數據是私有的也沒關系。 如果需要為 Provider 添加權限,只要修改 Manifest 文件中 <provider>
的屬性或子元素即可。 可以為整個 Provider 、某一張表、甚至某條記錄設置權限,也可以三者組合設置。
Provider 的權限是由 Manifest 文件中的一個或多個 <permission>
元素定義的。 為了保證權限名稱的唯一性,請使用 Java 風格的域名來定義 android:name
屬性。比如,可以把讀權限命名為 com.example.app.provider.permission.READ_PROVIDER
。
以下列出了 Provider 權限的作用范圍,首先是適用於整個 Provider 的權限,然后逐漸縮小。 權限的作用域越小,優先級就越高:
- 一個權限同時控制 Provider 級別的讀寫操作
-
通過
<provider>
元素的android:permission
屬性,可以用一個權限控制對整個 Provider 的讀取和寫入。 - 分別控制 Provider 級別的讀和寫操作
-
分開控制對整個 Provider 的讀取和寫入。 這通過指定
<provider>
元素的android:readPermission
和android:writePermission
屬性即可。 該權限優先於android:permission
的權限設置。 - Path 級別的權限
-
對 Provider 中的某個 Content URI 的讀、寫、讀與寫權限。 這通過為每個 URI 定義
<provider>
的<path-permission>
子元素來實現。 可分別對每個 Content URI 指定讀寫權限、讀取權限、寫入權限,或者三者都賦予。 讀取、寫入權限優先於讀寫權限。 並且, path 級別的權限優先於 Provider 級別的權限設置。 - 臨時權限
-
臨時賦予某個應用程序的權限,即使該應用程序無權訪問也沒關系。 這種臨時授權機制減少了應用程序必須在 Manifest 中申請的權限數量。 如果啟用了臨時授權,則只有要持續訪問所有 Provider 數據的應用程序才需擁有“永久”性訪問權限。
現在假定要實現一個 Email Provider 和 App, 需要用外部圖片瀏覽應用顯示保存在 Provider 中的圖片附件。 如果不需要圖片瀏覽應用申請限,就要讓它擁有必要的訪問能力,則可以對圖片的 Content URI 建立臨時權限。 當用戶需要顯示圖片時, Email App 會向圖片瀏覽應用發送一個包含了 Content URI 和權限標志位的 Intent。 然后圖片瀏覽應用會查詢 Email Provider 並讀取圖片,即使它沒有 Provider 的讀取權限也沒關系。
要啟用臨時授權機制,請設置
<provider>
的android:grantUriPermissions
屬性,或者在<provider>
元素下添加幾條<grant-uri-permission>
子元素。如果啟用了該機制,則在 Provider 的某個關聯了臨時權限的 Content URI 失效時,必須調用 code>Context.revokeUriPermission() 。這里的屬性值決定着 Provider 可被訪問的程度。 如果設為
true
,則系統會對整個 Provider 賦予臨時權限,並覆蓋其他所有 Provider 級別和 path 級別的權限設定。如果設為
false
,則必須在<provider>
元素下添加<grant-uri-permission>
子元素。每個子元素都定義了一條臨時授權。為了實現對應用程序的臨時賦權, Intent 必須包含
FLAG_GRANT_READ_URI_PERMISSION
或FLAG_GRANT_WRITE_URI_PERMISSION
標志位,兩者都帶也行。這是通過setFlags()
方法來實現的。如果未設置
android:grantUriPermissions
屬性,則被視為false
。
<provider> 元素
類似於 Activity
和 Service
組件, ContentProvider
的子類必須在 Manifest 文件中用 <provider>
元素進行聲明。 Android 系統將從此元素中讀取以下信息:
-
Authority (
android:authorities
) - 用於在系統中唯一標識整個 Provider 的名稱。 該屬性的詳細內容已在 設計 Content URI一節中進行了詳細介紹。
-
Provider 類名 (
android:name
) -
實現了
ContentProvider
的類。該類已在 Implementing the ContentProvider Class 一節中進行了詳細介紹。 - 權限
-
該屬性定義了其他應用程序訪問 Provider 數據時必須擁有的權限:
android:grantUriPermssions
:臨時權限標志位。android:permission
: 讀/寫整個 Provider 的權限。android:readPermission
: 讀取整個 Provider 的權限。android:writePermission
:寫入整個 Provider 的權限。
有關權限及對應屬性值的信息已在 實現 Content Provider 權限 一節中詳細介紹。
- 與啟動和控制相關的屬性
-
以下屬性決定了 Android 系統啟動 Provider 的時機和方式、Provider 的進程參數和其他運行時設置:
android:enabled
:允許系統啟動 Provider 的標志。android:exported
:允許其他應用程序使用該 Provider 的標志。android:initOrder
:Provider 在其進程中的啟動順序。android:multiProcess
:允許系統在調用方進程中啟動 Provider 的標志。android:process
:Provider 所在的進程名稱。 run.android:syncable
:標明 Provider 數據將於服務器進行同步的標志。
該屬性在開發指南中的
<provider>
元素文檔中給出了完整的介紹。 - 資訊類屬性
-
代表 Provider 的圖標和文本標簽(可選項)。
android:icon
:包含了 Provider 圖標的 Drawable 資源。 在設置 > 應用程序 > 所有應用程序的列表中, Provider 的圖標將顯示在文本標簽的旁邊。android:label
:描述 Provider 或其內部數據的說明性文字。 顯示在設置 > 應用程序 > 所有應用程序的列表中。
該屬性在開發指南中的
<provider>
元素文檔中給出了完整的介紹。
Intent 和數據訪問
應用程序可以通過 Intent
間接訪問到 Content Provider, 它不需要調用 ContentResolver
或 ContentProvider
的任何方法,而是發送一個 Intent 啟動某個 Activity 即可。 這個 Activity 往往是 Provider 所在應用的一部分,它負責完成數據讀取工作,並顯示在自己的界面中。 根據 Intent 中的 Action,此 Activity 也可以讓用戶完成修改數據的操作。 在 Intent 中還可以包含“extras”數據,目標 Activity 可在其界面中顯示這些附加數據,然后用戶就有機會修改這些數據並保存到 Provider 中去。
利用 Intent 有助於保證數據的完整性。 因為 Provider 可以用精確的業務規則來控制數據的插入、修改和刪除操作。 如果允許其他應用程序直接修改 Provider 中的數據,則可能會帶來非法數據。 為了讓開發人員通過 Intent 訪問數據,請務必給出完整的文檔。 並且要讓他們明白,通過 Intent 訪問時,使用 Provider 應用的界面,比用自己的代碼修改數據更為合適。
對於修改 Provider 數據的 Intent 的處理過程,與其他 Intent 沒有什么不同。 關於使用 Intent 的詳細信息,請閱讀文章 Intent 和 Intent 過濾器.