由于之前主要做手机游戏相关的开发,所以contentprovider了解的不多,今天就来学习一下。
1. 首先来了解一下contentprovider是什么?它的作用是什么?
contentprovider是android的四大组件之一,可见它在android中的作用非同小可。它主要的作用是:实现各个应用程序之间的(跨应用)数据共享,比如联系人应用中就使用了contentprovider,你在自己的应用中可以读取和修改联系人的数据,不过需要获得相应的权限。其实它也只是一个中间人,真正的数据源是文件或者sqlite等。
2. 那contentprovider是怎么实现数据共享的呢?
下面先来了解一下这几个东东:
(1) uri
uri:统一资源标识符,代表要操作的数据,可以用来标识每个contentprovider,这样你就可以通过指定的uri找到想要的contentprovider,从中获取或修改数据。在android中uri的格式如下图所示(图片来自于网络):
主要分三个部分: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<string, string> snotesprojectionmap;</code>
<code> </code><code>snotesprojectionmap = </code><code>new</code>
<code>hashmap<string, string>();</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 > </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><</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>/></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的输出如下:
因为现在数据库和表还不存在,所以首先会创建表,返回的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输入如下,正是刚才插入的值。
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输出:
刚才插入的值已被更新了。
(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输出:
再次查询时,已经没有了数据。
ok,到此为止,应该对contentprovider的作用和创建一个自定义的contentprovider基本了解了吧。