天天看點

android ContentProvider使用詳解

由于之前主要做手機遊戲相關的開發,是以contentprovider了解的不多,今天就來學習一下。

1. 首先來了解一下contentprovider是什麼?它的作用是什麼?

contentprovider是android的四大元件之一,可見它在android中的作用非同小可。它主要的作用是:實作各個應用程式之間的(跨應用)資料共享,比如聯系人應用中就使用了contentprovider,你在自己的應用中可以讀取和修改聯系人的資料,不過需要獲得相應的權限。其實它也隻是一個中間人,真正的資料源是檔案或者sqlite等。

2. 那contentprovider是怎麼實作資料共享的呢?

下面先來了解一下這幾個東東:

(1) uri

uri:統一資源辨別符,代表要操作的資料,可以用來辨別每個contentprovider,這樣你就可以通過指定的uri找到想要的contentprovider,從中擷取或修改資料。在android中uri的格式如下圖所示(圖檔來自于網絡):

android ContentProvider使用詳解

主要分三個部分:scheme, authority and path。scheme表示上圖中的content://,authority表示b部分,path表示c和d部分。

a部分:表示是一個android内容uri,說明由contentprovider控制資料,該部分是固定形式,不可更改的。

b部分:是uri的授權部分,是唯一辨別符,用來定位contentprovider。格式一般是自定義contentprovider類的完全限定名稱,注冊時需要用到,如:com.alexzhou.provider.noteprovider

c部分和d部分:是每個contentprovider内部的路徑部分,c和d部分稱為路徑片段,c部分指向一個對象集合,一般用表的名字,如:/notes表示一個筆記集合;d部分指向特定的記錄,如:/notes/1表示id為1的筆記,如果沒有指定d部分,則傳回全部記錄。

在開發中通常使用常量來定義uri,如下面的content_uri:

1

2

<code>public</code>

<code>static</code> <code>final</code> <code>string authority = </code><code>"com.alexzhou.provider.noteprovider"</code><code>;</code>

<code>static</code> <code>final</code> <code>uri content_uri = uri.parse(</code><code>"content://"</code>

<code>+ authority + </code><code>"/notes"</code><code>);</code>

(2) mime

mime是指定某個擴充名的檔案用一種應用程式來打開,就像你用浏覽器檢視pdf格式的檔案,浏覽器會選擇合适的應用來打開一樣。android中的工作方式跟http類似,contentprovider會根據uri來傳回mime類型,contentprovider會傳回一個包含兩部分的字元串。mime類型一般包含兩部分,如:

text/html

text/css

text/xml

application/pdf

等,分為類型和子類型,android遵循類似的約定來定義mime類型,每個内容類型的android mime類型有兩種形式:多條記錄(集合)和單條記錄。

多條記錄

vnd.android.cursor.dir/vnd.alexzhou.note

單條記錄

vnd.android.cursor.item/vnd.alexzhou.note

vnd表示這些類型和子類型具有非标準的、供應商特定的形式。android中類型已經固定好了,不能更改,隻能差別是集合還是單條具體記錄,子類型vnd.之後的内容可以按照格式随便填寫。在使用intent時,會用到mime這玩意,根據mimetype打開符合條件的活動。

3. 接下來通過一個簡單的demo,來學習怎麼建立自定義的contentprovider,這裡資料源選用sqlite,最常用的也是這個。

(1) 建立一個類notecontentprovider,繼承contentprovider,需要實作下面5個方法:

query

insert

update

delete

gettype

這些方法是eclipse自動生成的,暫時先不動他們。

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

<code>/**</code>

<code>author:alexzhou</code>

<code>email :[email protected]</code>

<code>date  :2013-2-25</code>

<code> </code><code>**/</code>

<code>class</code> <code>notecontentprovider </code><code>extends</code>

<code>contentprovider {</code>

<code>    </code><code>@override</code>

<code>    </code><code>public</code>

<code>boolean</code> <code>oncreate() {</code>

<code>        </code><code>// todo auto-generated method stub</code>

<code>        </code><code>return</code>

<code>false</code><code>;</code>

<code>    </code><code>}</code>

<code>cursor query(uri uri, string[] projection, string selection,</code>

<code>            </code><code>string[] selectionargs, string sortorder) {</code>

<code>null</code><code>;</code>

<code>string gettype(uri uri) {</code>

<code>uri insert(uri uri, contentvalues values) {</code>

<code>int</code> <code>delete(uri uri, string selection, string[] selectionargs) {</code>

<code>0</code><code>;</code>

<code>int</code> <code>update(uri uri, contentvalues values, string selection,</code>

<code>            </code><code>string[] selectionargs) {</code>

<code>}</code>

(2)先來設計一個資料庫,用來存儲筆記資訊,主要包含_id,title,content,create_date四個字段。建立noteprovidermetadata類,封裝uri和資料庫、表、字段相關資訊,源碼如下:

<code>class</code> <code>noteprovidermetadata {</code>

<code>static</code> <code>final</code> <code>string database_name = </code><code>"note.db"</code><code>;</code>

<code>static</code> <code>final</code> <code>int</code> <code>database_version = </code><code>1</code><code>;</code>

<code>    </code><code>/**</code>

<code>     </code><code>*</code>

<code>     </code><code>*資料庫中表相關的中繼資料</code>

<code>     </code><code>*/</code>

<code>static</code> <code>final</code> <code>class</code> <code>notetablemetadata </code><code>implements</code>

<code>basecolumns {</code>

<code>        </code><code>public</code>

<code>static</code> <code>final</code> <code>string table_name = </code><code>"notes"</code><code>;</code>

<code>static</code> <code>final</code> <code>string content_type = </code><code>"vnd.android.cursor.dir/vnd.alexzhou.note"</code><code>;</code>

<code>static</code> <code>final</code> <code>string content_item_type = </code><code>"vnd.android.cursor.item/vnd.alexzhou.note"</code><code>;</code>

<code>static</code> <code>final</code> <code>string note_title = </code><code>"title"</code><code>;</code>

<code>static</code> <code>final</code> <code>string note_content = </code><code>"content"</code><code>;</code>

<code>static</code> <code>final</code> <code>string create_date = </code><code>"create_date"</code><code>;</code>

<code>static</code> <code>final</code> <code>string default_orderby = </code><code>"create_date desc"</code><code>;</code>

<code>static</code> <code>final</code> <code>string sql_create_table = </code><code>"create table "</code> <code>+ table_name + </code><code>" ("</code>

<code>                                        </code><code>+ _id + </code><code>" integer primary key,"</code>

<code>                                        </code><code>+ note_title + </code><code>" varchar(50),"</code>

<code>                                        </code><code>+ note_content + </code><code>" text,"</code>

<code>                                        </code><code>+ create_date + </code><code>" integer"</code>

<code>                                        </code><code>+ </code><code>");"</code>

<code>;</code>

authority代表授權,該字元串和在android描述檔案androidmanifest.xml中注冊該contentprovider時的android:authorities值一樣,notetablemetadata繼承basecolumns,後者提供了标準的_id字段,表示行id。

(3) contentprovider是根據uri來擷取資料的,那它怎麼區分不同的uri呢,因為無論是擷取筆記清單還是擷取一條筆記都是調用query方法,現在來實作這個功能。需要用到類urimatcher,該類可以幫助我們識别uri類型,下面看實作源碼:

<code>private</code>

<code>static</code> <code>final</code> <code>urimatcher surimatcher;</code>

<code>static</code> <code>final</code> <code>int</code> <code>collection_indicator = </code><code>1</code><code>;</code>

<code>static</code> <code>final</code> <code>int</code> <code>single_indicator = </code><code>2</code><code>;</code>

<code>static</code>

<code>{</code>

<code>    </code><code>surimatcher = </code><code>new</code>

<code>urimatcher(urimatcher.no_match);</code>

<code>    </code><code>surimatcher.adduri(noteprovidermetadata.authority, </code><code>"notes"</code><code>, collection_indicator);</code>

<code>    </code><code>surimatcher.adduri(noteprovidermetadata.authority, </code><code>"notes/#"</code><code>, single_indicator);</code>

這段代碼是notecontentprovider類中的,urimatcher的工作原理:首先需要在urimatcher中注冊uri模式,每一個模式跟一個唯一的編号關聯,注冊之後,在使用中就可以根據uri得到對應的編号,當模式不比對時,urimatcher将傳回一個no_match常量,這樣就可以區分了。

(4) 還需為查詢設定一個投影映射,主要是将抽象字段映射到資料庫中真實的字段名稱,因為這些字段有時是不同的名稱,既抽象字段的值可以不跟資料庫中的字段名稱一樣。這裡使用hashmap來完成,key是抽象字段名稱,value對應資料庫中的字段名稱,不過這裡我把兩者的值設定是一樣的,在notecontentprovider.java中添加如下代碼。

<code>static</code> <code>hashmap&lt;string, string&gt; snotesprojectionmap;</code>

<code>    </code><code>snotesprojectionmap = </code><code>new</code>

<code>hashmap&lt;string, string&gt;();</code>

<code>    </code><code>snotesprojectionmap.put(noteprovidermetadata.notetablemetadata._id, noteprovidermetadata.notetablemetadata._id);</code>

<code>    </code><code>snotesprojectionmap.put(noteprovidermetadata.notetablemetadata.note_content, noteprovidermetadata.notetablemetadata.note_content);</code>

<code>    </code><code>snotesprojectionmap.put(noteprovidermetadata.notetablemetadata.note_title, noteprovidermetadata.notetablemetadata.note_title);</code>

<code>    </code><code>snotesprojectionmap.put(noteprovidermetadata.notetablemetadata.create_date, noteprovidermetadata.notetablemetadata.create_date);</code>

(5) 在notecontentprovider.java中建立一個内部類databasehelper,繼承自sqliteopenhelper,完成資料庫表的建立、更新,這樣可以通過它獲得資料庫對象,相關代碼如下。

<code>databasehelper mdbhelper;</code>

<code>static</code> <code>class</code> <code>databasehelper </code><code>extends</code>

<code>sqliteopenhelper {</code>

<code>databasehelper(context context) {</code>

<code>        </code><code>super</code><code>(context, noteprovidermetadata.database_name, </code><code>null</code><code>, noteprovidermetadata.database_version);</code>

<code>void</code> <code>oncreate(sqlitedatabase db) {</code>

<code>        </code><code>log.e(</code><code>"databasehelper"</code><code>, </code><code>"create table: "</code> <code>+ noteprovidermetadata.notetablemetadata.sql_create_table);</code>

<code>        </code><code>db.execsql(noteprovidermetadata.notetablemetadata.sql_create_table);</code>

<code>void</code> <code>onupgrade(sqlitedatabase db, </code><code>int</code>

<code>oldversion, </code><code>int</code>

<code>newversion) {</code>

<code>        </code><code>db.execsql(</code><code>"drop table if exists "</code> <code>+ noteprovidermetadata.notetablemetadata.table_name);</code>

<code>        </code><code>oncreate(db);</code>

(6) 現在來分别實作第一步中未實作的5個方法,先來實作query方法,這裡借助sqlitequerybuilder來為查詢設定投影映射以及設定相關查詢條件,看源碼實作:

<code>        </code><code>string[] selectionargs, string sortorder) {</code>

<code>    </code><code>sqlitequerybuilder querybuilder = </code><code>new</code>

<code>sqlitequerybuilder();</code>

<code>    </code><code>switch</code><code>(surimatcher.match(uri)) {</code>

<code>    </code><code>case</code>

<code>collection_indicator:</code>

<code>        </code><code>// 設定查詢的表</code>

<code>        </code><code>querybuilder.settables(noteprovidermetadata.notetablemetadata.table_name);</code>

<code>        </code><code>// 設定投影映射</code>

<code>        </code><code>querybuilder.setprojectionmap(snotesprojectionmap);</code>

<code>        </code><code>break</code><code>;</code>

<code>single_indicator:</code>

<code>        </code><code>querybuilder.appendwhere(noteprovidermetadata.notetablemetadata._id + </code><code>"="</code>

<code>+ uri.getpathsegments().get(</code><code>1</code><code>));</code>

<code>    </code><code>default</code><code>:</code>

<code>        </code><code>throw</code>

<code>new</code> <code>illegalargumentexception(</code><code>"unknow uri: "</code> <code>+ uri);</code>

<code>    </code><code>string orderby;</code>

<code>    </code><code>if</code><code>(textutils.isempty(sortorder))</code>

<code>    </code><code>{</code>

<code>        </code><code>orderby = noteprovidermetadata.notetablemetadata.default_orderby;</code>

<code>    </code><code>} </code><code>else</code>

<code>        </code><code>orderby = sortorder;</code>

<code>    </code><code>sqlitedatabase db = mdbhelper.getreadabledatabase();</code>

<code>    </code><code>cursor cursor = querybuilder.query(db, projection, selection, selectionargs, </code><code>null</code><code>, </code><code>null</code><code>, orderby);</code>

<code>    </code><code>return</code>

<code>cursor;</code>

傳回的是一個cursor對象,它是一個行集合,包含0和多個記錄,類似于jdbc中的resultset,可以前後移動遊标,得到每行每列中的資料。注意的是,使用它需要調用movetofirst(),因為遊标預設是在第一行之前。

(7)實作insert方法,實作把記錄插入到基礎資料庫中,然後傳回新建立的記錄的uri。

<code>uri insert(uri uri, contentvalues values) {       </code>

<code>    </code><code>if</code>

<code>(surimatcher.match(uri) != collection_indicator) {</code>

<code>new</code> <code>illegalargumentexception(</code><code>"unknown uri "</code> <code>+ uri);</code>

<code>    </code><code>sqlitedatabase db = mdbhelper.getwritabledatabase();</code>

<code>    </code><code>long</code>

<code>rowid = db.insert(noteprovidermetadata.notetablemetadata.table_name, </code><code>null</code><code>, values);</code>

<code>    </code><code>if</code><code>(rowid &gt; </code><code>0</code><code>) {</code>

<code>        </code><code>uri returi = contenturis.withappendedid(noteprovidermetadata.notetablemetadata.content_uri, rowid);</code>

<code>returi;</code>

(8) 實作update方法,根據傳入的列值和where字句來更新記錄,傳回更新的記錄數,看源碼:

<code>@override</code>

<code>        </code><code>string[] selectionargs) {</code>

<code>    </code><code>int</code>

<code>count = -</code><code>1</code><code>;</code>

<code>        </code><code>count = db.update(noteprovidermetadata.notetablemetadata.table_name, values, </code><code>null</code><code>, </code><code>null</code><code>);</code>

<code>        </code><code>string rowid = uri.getpathsegments().get(</code><code>1</code><code>);</code>

<code>        </code><code>count = db.update(noteprovidermetadata.notetablemetadata.table_name, values, noteprovidermetadata.notetablemetadata._id + </code><code>"="</code>

<code>+ rowid, </code><code>null</code><code>);</code>

<code>new</code> <code>illegalargumentexception(</code><code>"unknow uri : "</code> <code>+ uri);</code>

<code>    </code><code>this</code><code>.getcontext().getcontentresolver().notifychange(uri, </code><code>null</code><code>);</code>

<code>count;</code>

notifychange函數是在更新資料時,通知其他監聽對象。

(9)實作delete方法,該方法傳回删除的記錄數。

<code>        </code><code>count = db.delete(noteprovidermetadata.notetablemetadata.table_name, selection, selectionargs);</code>

<code>        </code><code>count = db.delete(noteprovidermetadata.notetablemetadata.table_name, noteprovidermetadata.notetablemetadata._id + </code><code>"="</code>

<code>new</code> <code>illegalargumentexception(</code><code>"unknow uri :"</code> <code>+ uri);</code>

<code>    </code><code>// 更新資料時,通知其他contentobserver</code>

(10) 實作gettype方法,根據uri傳回mime類型,這裡主要用來區分uri是擷取集合還是單條記錄,這個方法在這裡暫時沒啥用處,在使用intent時有用。

<code>        </code><code>case</code>

<code>            </code><code>return</code>

<code>noteprovidermetadata.notetablemetadata.content_type;</code>

<code>noteprovidermetadata.notetablemetadata.content_item_type;</code>

<code>        </code><code>default</code><code>:</code>

<code>            </code><code>throw</code>

(11) 在androidmanifest.xml中注冊該contentprovider,這樣系統才找得到,當然你也可以設定相關的權限,這裡就不設定了。

<code>&lt;</code><code>provider</code>

<code>android:name</code><code>=</code><code>"notecontentprovider"</code>

<code>android:authorities</code><code>=</code><code>"com.alexzhou.provider.noteprovider"</code><code>/&gt;</code>

到現在為止,自定義contentprovider的全部代碼已經完成,下面建立一個簡單的應用來測試一下。

主要測試insert、update、delete、query這四個函數。

1. 現在資料庫中沒有資料,是以先測試insert,插入一條資料。主要代碼如下:

<code>void</code> <code>insert() {</code>

<code>     </code><code>contentvalues values = </code><code>new</code>

<code>contentvalues();</code>

<code>     </code><code>values.put(</code><code>"title"</code><code>, </code><code>"hello"</code><code>);</code>

<code>     </code><code>values.put(</code><code>"content"</code><code>, </code><code>"my name is alex zhou"</code><code>);</code>

<code>     </code><code>long</code>

<code>time = system.currenttimemillis();</code>

<code>     </code><code>values.put(</code><code>"create_date"</code><code>, time);</code>

<code>     </code><code>uri uri = </code><code>this</code><code>.getcontentresolver().insert(content_uri, values);</code>

<code>     </code><code>log.e(</code><code>"test "</code><code>, uri.tostring());</code>

這裡需要獲得content_uri值,其他的應用可以通過contentresolver接口通路contentprovider提供的資料。logcat的輸出如下:

android ContentProvider使用詳解

因為現在資料庫和表還不存在,是以首先會建立表,傳回的uri的值是第一條記錄的uri。

2. 測試query方法

<code>void</code> <code>query() {</code>

<code>    </code><code>cursor cursor = </code><code>this</code><code>.getcontentresolver().query(content_uri, </code><code>null</code><code>, </code><code>null</code><code>, </code><code>null</code><code>, </code><code>null</code><code>);</code>

<code>    </code><code>log.e(</code><code>"test "</code><code>, </code><code>"count="</code>

<code>+ cursor.getcount());</code>

<code>    </code><code>cursor.movetofirst();</code>

<code>    </code><code>while</code><code>(!cursor.isafterlast()) {</code>

<code>        </code><code>string title = cursor.getstring(cursor.getcolumnindex(</code><code>"title"</code><code>));</code>

<code>        </code><code>string content = cursor.getstring(cursor.getcolumnindex(</code><code>"content"</code><code>));</code>

<code>        </code><code>long</code>

<code>createdate = cursor.getlong(cursor.getcolumnindex(</code><code>"create_date"</code><code>));</code>

<code>        </code><code>log.e(</code><code>"test "</code><code>, </code><code>"title: "</code> <code>+ title);</code>

<code>        </code><code>log.e(</code><code>"test "</code><code>, </code><code>"content: "</code> <code>+ content);</code>

<code>        </code><code>log.e(</code><code>"test "</code><code>, </code><code>"date: "</code> <code>+ createdate);</code>

<code>        </code><code>cursor.movetonext();</code>

<code>    </code><code>cursor.close();</code>

logcat輸入如下,正是剛才插入的值。

android ContentProvider使用詳解

3. 測試update方法

<code>void</code> <code>update() {</code>

<code>    </code><code>contentvalues values = </code><code>new</code>

<code>    </code><code>values.put(</code><code>"content"</code><code>, </code><code>"my name is alex zhou !!!!!!!!!!!!!!!!!!!!!!!!!!"</code><code>);</code>

<code>count = </code><code>this</code><code>.getcontentresolver().update(content_uri, values, </code><code>"_id=1"</code><code>, </code><code>null</code><code>);</code>

<code>    </code><code>log.e(</code><code>"test "</code><code>, </code><code>"count="</code><code>+count);</code>

<code>    </code><code>query();</code>

logcat輸出:

android ContentProvider使用詳解

剛才插入的值已被更新了。

(4) 測試delete方法

<code>void</code> <code>delete() {</code>

<code>count = </code><code>this</code><code>.getcontentresolver().delete(content_uri, </code><code>"_id=1"</code><code>, </code><code>null</code><code>);</code>

看logcat輸出:

android ContentProvider使用詳解

再次查詢時,已經沒有了資料。

ok,到此為止,應該對contentprovider的作用和建立一個自定義的contentprovider基本了解了吧。