Content Provider管理中央存儲庫的數據的訪問, 你在Android程序中實現一個或者多個Provider, 連同清單文件中的元素. 其中一個類實現了ContentProvider子類, 這個子類是你的Provider和其餘程序之間的接口.儘管Content Provider意味着讓數據對其餘程序可見, 固然, 你也能夠在你的程序裏, 讓用戶查詢和修改由Provider管理的數據.java
這個主題的其餘部分是建立Content Provider的基本步驟以及一些API的使用.android
在你開始建立一個Provider以前, 作以下:數據庫
1.肯定你須要一個Content Provider. 若是你須要提供一個或多個如下特徵, 虛擬須要建立Content Provider:設計模式
你但願向其餘程序提供複雜的數據或者文件數組
你但願用戶從你的程序複製複雜數據到其餘程序瀏覽器
你但願提供用搜索引擎框架提供自定義搜索提示安全
:若是用途徹底只在你的程序內部, 你就不須要Provider去用SQLite數據庫.服務器
2.若是你尚未準備好, 閱讀 Content Provider Basics(Content Provider基礎)學習更多關於Provider的知識網絡
接下來, 遵循如下步驟來構建你的Provider:數據結構
Design the raw storage for your data. A content provider offers data in two ways:
文件數據
數據一般裝入文件, 諸如圖片, 音頻, 或者視頻. 將文件存儲在你的私人空間. 做爲其餘程序請求你的文件的響應, 你的Provider提供一個文件的handle.
"格式化"數據
數據一般放入數據庫, 數組, 或者相似結構. 將數據存儲在一個表格,匹配表的行和列。一行表示一個實體, 如一我的或者一個清單條目. 一列表示實體的一些數據, 如人的名字, 或者條目的價格. 儲存這種數據的常見方式是用SQLite 數據庫, 但你可使用任何類型的持久性存儲.
2.定義ContentProvider類的具體實現及其所需的方法。這個類是你的數據和Android系統的其他部分之間的接口。對於這個類的詳細信息,請參閱 Implementing the ContentProvider Class 的部分。
3.定義Provider的受權字符串,其內容的URI,和列名。若是你想Provider的程序處理的Intent,也要定義Intent行動,額外的數據,和標誌。還能夠定義您將須要的應用程序要訪問數據的權限。你應該考慮將全部這些值做爲一個單獨的contract類中的常量定義; 以後,你將這個類公開給其餘開發者。有關URI的更多信息,請參閱部分設計內容的URI。欲瞭解更多有關意圖的信息,請參閱 Intents and Data Access 。
4.添加其餘可選件,如樣本數據或AbstractThreadedSyncAdapter的實現, 能實現Provider和雲端數據的同步
Content Provider是用結構化格式保存的數據接口。 在你建立接口以前, 你必須肯定如何存儲數據。你能夠將數據儲存成任何形式, 而後設計接口在必要時讀寫數據。
這是一些Android中的一些數據存儲技術:
* Android系統提供SQLite數據庫的API, 用於Android本身的Provider存儲面向表結構的數據。SQLiteOpenHelper類是用來訪問數據庫的基類。
記住, 你沒必要用一個數據庫去實現你的存儲庫。 一個Provider外在表現爲一組表格, 就像關係型數據庫, 可是這個並非Provider內部實現的必要部分。
對於存儲文件數據, Android有多樣的面向文件的APIs, 要學習更多關於文件存儲的信息, 參閱Data Storage(數據存儲)主題。 若是你設計的Provider 提供媒體相關的數據, 如音頻和視頻, 你可讓Provider結合表數據結構數據和文件數據。
對於網絡型數據, 用java.net和android.net的類。 你也能夠將網絡數據同步到本地存儲, 如數據庫中, 而後提供表數據或者文件。 Sample Sync Adapter示例程序展現這種類型的同步。
這裏有一些建議供你設計Provider數據結構:
Table數據常有一個主鍵列, Provider爲每行提供一個惟一的數值。你可使用這個值來聯接一行到其餘表中的相關行(使用它做爲一個「外鍵」)。儘管你可使用這個列的任何名字, 使用BaseColumns_ID是個好的選擇,由於聯接到Provider查詢一個ListView的結果, 須要一個檢索列有name_ID。
若是你想提供的位圖圖像或其餘很是大的文件型數據塊, 將數據存儲在文件, 而後由它間接地提供,而不是直接存儲在一個表。若是你這樣作,你須要告訴你的Provider的用戶,他們須要使用一個ContentResolver文件的方法來訪問數據。
使用二進制大對象(BLOB)數據類型存儲數據,不一樣大小有不一樣的數據結構。例如,你可使用一個BLOB列存儲protocol buffer(協議緩衝區)或JSON結構。
您還可使用一個BLOB實現一個單一模式表。在這種類型的表中,你定義一個主鍵列,一個MIME類型的列,以及一個或多個普通的BLOB列。在「MIME類型」列中的值表示在BLOB列中的數據的含義。這使您能夠在同一表存儲不一樣類型的行。 Contacts Provider 的「數據」表ContactsContract.Data架構的獨立表的一個例子。就是schema-independent table(單一模式表)的一個實例。
Content URI是在Provider中標識數據的URI。 Content URIs包含整個Provider的符號名稱(它的簽名)以及表或者文件的名稱(路徑)。選配的id部分指向表中的獨立的行。 每一個訪問ContentProvider的方法的數據以一個Content URI做爲一個參數;這容許你指定表, 行以及文件的訪問。
Provider一般有個單獨的身份認證, 做爲其Android內部名稱。 爲了不與其餘Provider衝突, 你應該用完整的互聯網域名(反轉)做爲你Provider認證的基礎。 由於這一建議也適用Android的包名, 您能夠定義provider身份認證做爲包名的擴展。例如, 若是你的Android包名是com.example., 你應該設置Provider的身份認證爲com.example..provider.
開發者一般在身份認證後附加指向單獨表的路徑的方式建立Content URIs。 例如, 若是你有兩個表, table1和table2, 你結合前面的例子的認證生成Content URIs com.example..provider/table1 and com.example..provider/table2。路徑不侷限於單一的segment, 沒有必要產生每一個級別的路徑表。
按照慣例, 經過接收一個帶有行ID在URI末尾的content URI, providers提供訪問一個表中單獨的行。按照慣例,providers匹配ID值與表的ID列,並執行對行的訪問請求的匹配。
本慣例有助於爲應用程序訪問Provider提供通用的設計模式。應用程序針對Provider作一個查詢, 而後用 CursorAdapter在 ListView中顯示結果Cursor。 CursorAdapter的定義須要 Cursor中的一列做爲_ID.
而後用戶選擇UI中顯示的一行來查看或者修改數據。應用程序從由ListView支持的Cursor獲取對應的行。爲這行取得_ID值, 將之添加到content URI, 發送訪問請求到Provider。
爲了幫你選擇引入的content URI採起的動做, Provider API引入了個方便的類UriMatcher。將content URI模型映射到數值。您可使用整數值在一個switch語句選擇所需的行動爲內容的URI或URI相匹配的特定模式。
Content URI模型用通配符匹配Content URIs:
* 匹配字符串中有效字符的長度
* 匹配字符串中數字字符的長度
涉及便攜Content URI處理的例子, 考慮一個認證爲com.example.app.provider的Provider, 它識別下面指向表的Content URIs:
content://com.example.app.provider/table1: A table called table1.
content://com.example.app.provider/table2/dataset1: A table called dataset1.
content://com.example.app.provider/table2/dataset2: A table called dataset2.
content://com.example.app.provider/table3: A table called table3.
這個Provider也識別這樣的Content URIs,將行號加在它們後面, 例如:
content://com.example.app.provider/table3/1 表示table3的第一行
如下的Content URI模型也適用:
content://com.example.app.provider/*
* 匹配Provider中的任意Content URI
content://com.example.app.provider/* /table2/* :
* * 匹配表中dataset1和dataset2的Content URI, 可是不匹配table1或table3的Content URI。
content://com.example.app.provider/table3/#:匹配table3中單一行的Content URI, 如:content://com.example.app.provider/table3/6表示第6行。
下面的代碼段顯示UriMatcher中的方法如何使用。 這段代碼處理整個table的URIs, 不一樣與單一行的URIs, 經過使用Content URI模型content:///用於表, content:////對於單一行。
addURI()方法映射一個authority和路徑到一個整數。方法android.content.UriMatcher#match(Uri) match()} 返回URI的整數值。用switch語句選擇在查詢整個表或者查詢一條記錄。
1 |
public class ExampleProvider extends ContentProvider {... |
另外一個類,ContentUris, 提供了個便利的方法,用於處理Content URIs的id部分。類Uri和Uri.Builder包含便利的方法, 解析存在的Uri對象和建立新的。
ContentProvider實例管理經過從其餘應用程序請求的處理的方式訪問一個結構化的數據集。全部形式的訪問ContentResolver最終調用,而後調用一個具體的方法來訪問的ContentProvider。
ContentProvider的抽象類定義了六個抽象方法,您必須實現的做爲你的本身的具體子類的一部分。除了onCreate(), 其餘的這些方法調用一個客戶程序嘗試訪問你的ContentProvider
query()
* 從您的Provider檢索數據。使用參數選擇表去查詢,返回行和列,按順序排列結果。返回數據做爲Cursor對象。
insert()
* 插入一個新行到您的Provider。使用參數來選擇目標表及得到的列值使用。爲新插入的行返回一個Content URI。
update()
* 在你的Provider更新現有的行。使用參數選擇表和行更新並獲得更新的列值。返回更新的行數。
delete()
* 從你的Provider刪除行。使用參數選擇要刪除的表和行。返回刪除的行數。
getType()
* 返回Content URI對應的IME類型。這個方法在Implementing Content Provider MIME Types部分作了更詳細的闡述。
onCreate()
* 初始化您的provider。Android系統一建立你的Provider就調用這個方法。注意直到ContentResolver對象試圖訪問時,Provider才建立。
注意,這些方法與ContentResolver中一樣命名方法有相同的簽名。
這些方法的實現應該遵循如下原則:
除了onCreate(), 全部的這些方法可能被多個線程同時調用,因此他們必須是線程安全的。學習更多關於多線程, 查看Processes and Threads專題
避免在onCreate()作耗時的操做。延遲初始化任務,直到他們真正須要。部 Implementing the onCreate() method章節更細緻的討論了這個問題。
儘管您必須實現這些方法,除了返回預期的數據類型,你的代碼能夠不作任何事。例如,您可能想要阻止其餘應用程序將數據插入某些表。要作到這一點,您能夠忽略該insert()調用和返回0。
ContentProvider.query()方法必須返回一個Cursor對象,或者若是它失敗了,拋出一個異常。若是您使用的是一個SQLite數據庫做爲數據存儲庫,您能夠簡單地返回一個 SQLiteDatabase 遊標query()方法的類SQLiteDatabase。若是查詢不匹配任何行,您應該返回一個遊標的實例源()方法返回0。若是在查詢過程發生了一個內部錯誤, 你只須要返回null。
若是您沒有使用SQLite數據庫做爲數據存儲,使用其中一個Cursor的具體的子類。例如,MatrixCursor類實現一個cursor中的每一行當成一個數組對象。這個類,使用addRow()來添加一個新行。
記住,Android系統必須可以聯繫各個進程之間的異常。Android能夠爲下列異常進行有效的查詢異常處理:
IllegalArgumentException(若是你的provider接收一個無效的content URI, 能夠選擇拋出這個異常)
NullPointerException
insert()方法添加一個新行到相應的表,使用在ContentValues的??參數值。若是列名不在ContentValues??參數中,你可能想在您的provider代碼,或在您的數據庫架構提供了它的默認值。
此方法應該返回新行的content URI。要構造這個,使用withAppendedId()追加新行的_id(或其餘主鍵)值到表的content URI。
delete()方法實際並無從您的數據存儲中刪除的行。若是您使用的是同步適配器到provider。你應該考慮用一個「delete」標記標記一個被刪除的行, 而不是徹底的刪除。同步適配器能夠檢查要被刪除的行,從provider刪除以前,將他們從服務器中刪除。
update()方法須要同insert()方法相同的ContentValues??的參數,和deleted()和ContentProvider.query()用過相同的selection 和 selectionArgs參數。這可能讓你複用這些方法之間的代碼。
啓動Provider時, Android系統調用的onCreate()。您應該在方法中只執行快速初始化任務,並推遲建立數據庫和數據加載,直到provider實際收到的數據的請求。若是你在onCreate()作冗長的任務,你將減緩您provider的啓動。從而這會減慢從provider到其餘應用程序的響應。
例如,若是您使用的是SQLite數據庫,你能夠在ContentProvider.onCreate()中建立一個新SQLiteOpenHelper對象(),而後首次打開數據庫時建立SQL表。爲了推進這項工做,你第一次調用getWritableDatabase(),它會自動調用SQLiteOpenHelper.onCreate()方法。
如下兩個片斷展現ContentProvider.onCreate()和SQLiteOpenHelper.onCreate()之間的相互做用。第一個片斷是ContentProvider.onCreate()的實現:
1 |
public class ExampleProvider extends ContentProvider |
下一個片斷是SQLiteOpenHelper.onCreate()的實現,包含一個輔助類:
1 |
...// A string that defines the SQL statement for creating a tableprivate static final String SQL_CREATE_MAIN = "CREATE TABLE " + |
ContentProvider類有兩個方法返回的MIME類型:
getType()
* 你必須在全部的provider中實現的方法之一。
getStreamTypes()
* 若是您的provider提供文件, 你會但願實現。
getType()方法返回一個MIME格式的String 描述內容的URI參數返回的數據類型。開放的參數能夠是一個模式,而不是一個特定的URI;在這種狀況下,你應該返回類型與內容的URI模式相匹配的相關數據。
對於常見的數據類型,如文本,HTML或JPEG的, getType()應返回的數據的標準MIME類型。這些標準類型的完整列表能夠在IANA MIME Media Types網站上得到.
對於content URI, 指向表中的數據一行或多行, getType()應該返回一個Android的廠商特定的MIME格式的MIME類型:
類型部分:vnd
子類型部分:
若是URI模型是一個單一的行:android.cursor.item/
若是URI模式是多行:android.cursor.dir/
Provider指定部分: vnd..
您提供和。 的值應該是全球惟一的,對於對應的URI模型,值應該惟一。貴公司的名稱或您的應用程序的Android包名稱的某些部分是的一個不錯的選擇。的一個不錯的選擇是與URI相關聯的表的標識的字符串。
例如,若是一個Provider的認證是com.example.app.provider,並將一個表名爲table1,爲table1中的多個行的MIME類型是:
1 |
vnd.android.cursor.dir/vnd.com.example.provider.table1 |
對於單一行的table1,MIME類型是:
1 |
vnd.android.cursor.item/vnd.com.example.provider.table1 |
若是您的provider提供文件,實現getStreamTypes()。該方法返回一個MIME類型的文件,這個文件是你的provider能夠返回一個給定的content URI的String數組。你應該過濾你提供的MIME類型過濾器參數,這樣你只返回那些客戶端要處理的MIME類型。
例如,考慮provider提供的照片圖像, 如:jpg, PNG,gif格式的文件。若是應用程序經過過濾器的字符串image/ * (這是一個「圖片」)調用ContentResolver.getStreamTypes(),而後的ContentProvider.getStreamTypes()方法返回數組:
1 |
{ "image/jpeg", "image/png", "image/gif"} |
若是應用程序僅在jpg文件感興趣,那麼它能夠調用過濾字符串* / JPEG ContentResolver.getStreamTypes(),ContentProvider.getStreamTypes()應返回:
1 |
{"image/jpeg"} |
若是您的provider沒有提供任何過濾字符串要求的MIME類型,getStreamTypes()應該返回null。
Contract類是一個屬於provider的public final類,它包含常量定義的URI,列名,MIME類型,和其餘元數據。這個類創建的供應商和其餘應用程序之間的contract,以確保即便有改變URI的實際值,列名,等等, provider也能夠正確訪問,。
Contract類能夠幫助開發人員,由於它一般有助記符名稱的常量,因此provider不太可能使用不正確的值的列名或URIs。由於它是一個類,它能夠包含的Javadoc文檔。集成開發環境,如Eclipse能夠自動完成從Contract類和顯示常數的Javadoc的常量名。
provider不能從您的應用程序訪問的Contract類的類文件,但他們能夠從您提供的.jar文件靜態編譯到他們的應用程序,。
ContactsContract類和嵌套類Contract類的例子。
Android系統的全部方面的權限和訪問中在主題Security and Permissions進行了詳細介紹。主題Data Storage還介紹了不一樣類型的存儲安全和有效的權限。總之,要點以下:
默認狀況下,在存儲設備的內部存儲的數據文件對您的應用程序和Provider是私有的。
您所建立的SQLiteDatabase數據庫對您的應用程序和Provider是私有的。
默認狀況下,你保存到外部存儲的數據文件是公開的。你不能使用一個Content Provider,以限制訪問外部存儲中的文件,由於其餘應用程序可使用其餘API讀寫它們。
打開或建立你內部儲存設備上的文件或SQLite數據庫, 調用這個方法能給其餘程序讀或者寫他們的方法。若是你使用內部文件或者數據庫做爲你Provider的存儲器,給它「可讀」或「可寫」訪問。在manifest爲您的Provider設置的訪問權限將不能保護您的數據。內部存儲器訪問文件和數據庫的訪問權限是「private」,你的Provider的存儲器不該該改變.
若是您要使用的content provider的權限,以控制對數據的訪問,那麼你應該將數據存儲在內部文件,SQLite數據庫,或「雲」(例如,在遠程服務器上)的數據,你應該保持文件和數據庫對您的應用程序私有。
全部的應用程序能夠讀取或寫入您的Provider,即便底層數據是私人的,由於默認狀況下,你的Provider沒有的權限集。要改變這種情況,在您的manifest文件中設置權限,用元素的屬性或者子屬性。你能夠爲如下狀況設置權限, 適用整個provider, 或者特定的表, 或者單一的記錄, 或者前面3項。
你用一個或多個mainifest文件中的元素定義你Provider的屬性。爲了使您的Provider有特有的限定名,使用Java風格的做用域爲android:name屬性。例如,指定讀權限的com.example.app.provider.permission.READ_PROVIDER.
如下列表說明Provider的權限範圍,最開始的適用於整個Provider,而後變得更加細化。更加細化的權限覆蓋大範圍的權限:
單一讀寫Provider級別權限
* 一個權限控制讀寫訪問整個Provider,指定了android:權限屬性的元素。
分開的讀, 寫Provider級別權限
* 讀權限和寫權限爲整個Provider。您能夠在元素中指定Android:readPermission和androidwritePermission屬性。他們覆蓋了android:permission的權限。
路徑級別權限
* 讀,寫,或讀/寫權限做用與您Provider中的Content URI。你指定你想控制每一個URI與「的元素子元素。對於每一個您指定的Content URI,你能夠指定一個讀/寫權限,讀權限,寫權限,或全部三個。讀取和寫入權限覆蓋讀/寫權限。此外,路徑級別的權限覆蓋Provider級別的權限。
臨時權限
權限級別授予臨時訪問的應用程序,即便應用程序沒有一般所需的權限。臨時訪問功能減小了應用程序要求在其清單的權限的數量。當你打開臨時權限,只有須要Provider的「永久性」的權限,不斷訪問你的全部數據。
考慮實施時,你要容許外部圖像瀏覽器應用程序,以顯示您提供的照片附件的電子郵件服務提供商和應用,你須要的權限。爲了讓圖像瀏覽器,沒有必要設立臨時的權限爲內容的照片的URI須要的權限,訪問。設計您的電子郵件應用程序,這樣,當用戶要顯示一張照片,應用程序發送一個含有照片的內容URI和權限標誌的圖像瀏覽器的意圖。圖像瀏覽器就能夠查詢您的電子郵件提供商檢索照片,即便觀衆不正常讀取爲您提供的權限。
要打開的臨時權限,或者設置了元素的android:grantUriPermissions屬性,或將一個或多個子元素添加到您的元素。若是您使用的臨時權限,你必須調用Context.revokeUriPermission()每當你從Provider刪除Content URI支持,Content URI擁有一個臨時的權限。
您的Provider有多少是由訪問該屬性的值決定。若是該屬性設置爲true,則系統將給予臨時許可給您的整個Provider,覆蓋您的Provider級別或路徑級別的權限.
若是這個標誌設置爲false,那麼您必須添加子元素到你的元素。每一個子元素指定Content URI或URI臨時受權訪問。
要指定臨時訪問權限給應用程序,意圖必須包含的的FLAG_GRANT_READ_URI_PERMISSION或FLAG_GRANT_WRITE_URI_PERMISSION標誌,或二者兼而有之。這些用setFlags()方法設置。
若是 android:grantUriPermissions屬性沒有顯示,假定爲false.
像Activity和Service組件同樣,ContentProvider的子類必須定義在其應用的manifest文件中,使用的元素。 Android系統獲得元素的如下信息:
認證(android:authorities)
* 符號名稱,標識系統內的整個provider。這個屬性在章節 Designing Content URIs中作了更詳細的描述。
Provider類的名字(android:name)
* 實現ContentProvider的類。這個類在章節 Implementing the ContentProvider Class中作了更詳細的描述。
權限
指定權限的屬性,其餘應用程序必須爲了訪問Provider的數據:
* android:grantUriPermssions: 臨時權限標誌。
* android:permission: 單一的provider範圍讀/寫權限
* android:readPermission:Provider範圍讀權限
android:writePermission:Provider範圍寫權限
權限及其相應的屬性在Implementing Content Provider Permissions章節中作了更詳細的描述.
啓動和控制屬性
* 這些屬性決定如何以及什麼時候啓動Android系統Provider,該Provider的過程特性,以及其餘運行時設置:
* android:enabled:標誌容許系統開啓Provider.
* android:exported:標誌容許其餘程序使用這個Provider
* android:initOrder:Provider應該開始的順序, 涉及相同進程中的其餘Provider.
* android:multiProcess:標誌容許系統做爲調用客戶端開啓同一個進程中的Provider.
* android:process:Provider應該運行的進程的名字.
* android:syncable:標誌代表Provider的數據是同服務器同步的.
這個屬性是徹底記錄在dev指南主題的元素。
信息屬性
provider的可選icon和label
* android:icon:一個繪圖資源包含Provider的圖標。icon顯示應用程序列表中的下一個provider標籤在Setting > Apps > All.
android:label:描述provider或它們數據或二者的信息標籤.這個標籤的列表中顯示的應用程序中 Settings > Apps > All.
這個屬性在dev指南主題的元素中有完整的記錄.
應用程序能夠經過Intent間接的訪問ContentProvider。應用程序不調用任何ContentResolver或ContentProvider的方法。相反,它發送一個啓動活動的意圖,這每每是Provider的本身的應用程序的一部分。目標Activity是負責在UI上檢索和顯示的數據。根據Activity的Intent,目標Activity也可能提示用戶修改Provider數據。目標活動在UI中顯示的意圖,也可能包含「額外」的數據,而後在改變Provider的數據以前, 用戶能夠有選擇性的改變數據.
你可能想使用意圖訪問確保數據的完整性。你的Provider可能會依賴於數據的插入,更新和刪除按照嚴格定義的業務邏輯。若是是這樣的狀況下,容許其餘應用程序直接修改您的數據可能會致使無效的數據。若是你想要開發者使用意圖訪問,確保徹底的記錄它。向他們解釋爲什麼使用本身的應用程序的UI的意圖存取比用他們的代碼修改數據好。
處理傳入的意圖修改您Provider數據的意圖,處理其餘的意圖沒有什麼不一樣。你能夠了解更多有關使用Intent的主題 Intents and Intent Filters。