`
soboer
  • 浏览: 1310366 次
文章分类
社区版块
存档分类
最新评论

一个ContentProvider的典型结构分析

 
阅读更多

最近查看了Android Sdk中提供的NoteList示例代码,里面有一个ContentProvider的示例蛮经典的,所以进行下结构分析,让印象更深刻些,免得以后把较为复杂的ContentProvider用法弄忘了。^-^。

转载请注明出处:http://blog.csdn.net/nieweilin

一、字段部分

代码:

1 publicclassNotePadProviderextendsContentProvider{
2
3 privatestaticfinalStringTAG="NotePadProvider";
4
5 privatestaticfinalStringDATABASE_NAME="note_pad.db";
6 privatestaticfinalintDATABASE_VERSION=2;
7 privatestaticfinalStringNOTES_TABLE_NAME="notes";
8
9 privatestaticHashMapString,String>sNotesProjectionMap;
10 privatestaticHashMapString,String>sLiveFolderProjectionMap;
11
12 privatestaticfinalintNOTES=1;
13 privatestaticfinalintNOTE_ID=2;
14 privatestaticfinalintLIVE_FOLDER_NOTES=3;
15
16 privatestaticfinalUriMatchersUriMatcher;
3,TAG,用于在android程序运行时在Log中显示NotePadProvider这个字符串,方便程序的调试用
5,DATABASE_NAME,表征你要使用的数据库名字,这里是note_pad.db,也就是说会在应用程序的数据文件夹里面生成一个名字为note_pad.db的Sqlite数据库文件。
6,DATABASE_VERSION,表征你的数据库的版本情况。具体为什么是2不是很明白。
7,NOTES_TABLE_NAME,数据库中的一个表的表名。这里这个表的名字是notes。
8,这个map在以后SQLiteQueryBuilder的setProjectionMap方法中会用到,起到的作用就是将数据库中表的名字进行一个映射。刚开始我不是很懂这一部分的意义,后来看sdk文档中有这样一句话:
The projection map maps from column names that the caller passes into query to database column names. This is useful for renaming columns as well as disambiguating column names when doing joins. For example you could map "name" to "people.name". If a projection map is set it must contain all column names the user may request, even if the key and value are the same.
这个map是对用户查询操作传入的column名和数据表中存在的column名进行了一个映射,这样一来,有些数据库中抽象的表的column名就可以通过这个projection map实现重新命名,以达到方便开发者做一些类似于数据库表联接(join)操作的目的。一般情况下数据库中的表的column名就已经够清晰了,所以你可以不用再在projection map里面做一些column重命名的操作了——也就是说,你可以将这个map对象中的key和value都设为同样的值(接下来的代码会有体现)。
10,这个map和刚才那个同样是一个projection map,不同的是它针对的是Notepad的Live Folder。至于什么是Live Folder,请参看Android sdk文档中的Resources中的Live Folders一文。
12,13,14是三个常量,它们用于等一下会出现的UriMatcher类的addUri()方法。实际上它们是UriMatcher的match()方法的返回值,用于标记Uri的类型。从名字就可以知道,NOTES标记所有的记载(NOTE)所对应的URI返回值,NOTE_ID对应单个NOTE的URI返回值,LIVE_FOLDER_NOTES代表的是Live Folder中的NOTE对应的URI的返回值。它们三个标记了NoteList应用中基本的几种URI,从而在UriMatcher执行match()方法时,便会返回这些相应的int值。
16,ContentProvider封装的一个UriMatcher实例。该类用于将特定类型的Uri和一些int值进行映射式绑定,然后以后通过该类判断Uri类型时,返回这个绑定的int值,从而可以由此对Uri的类型判断提供更为有效的手段。
二、静态初始化语句块部分
代码:
1 static{
2 sUriMatcher=newUriMatcher(UriMatcher.NO_MATCH);
3 sUriMatcher.addURI(NotePad.AUTHORITY,"notes",NOTES);
4 sUriMatcher.addURI(NotePad.AUTHORITY,"notes/#",NOTE_ID);
5 sUriMatcher.addURI(NotePad.AUTHORITY,"live_folders/notes",LIVE_FOLDER_NOTES);
6
7 sNotesProjectionMap=newHashMapString,String>();
8 sNotesProjectionMap.put(Notes._ID,Notes._ID);
9 sNotesProjectionMap.put(Notes.TITLE,Notes.TITLE);
10 sNotesProjectionMap.put(Notes.NOTE,Notes.NOTE);
11 sNotesProjectionMap.put(Notes.CREATED_DATE,Notes.CREATED_DATE);
12 sNotesProjectionMap.put(Notes.MODIFIED_DATE,Notes.MODIFIED_DATE);
13
14 //SupportforLiveFolders.
15 sLiveFolderProjectionMap=newHashMapString,String>();
16 sLiveFolderProjectionMap.put(LiveFolders._ID,Notes._ID+"AS"+
17 LiveFolders._ID);
18 sLiveFolderProjectionMap.put(LiveFolders.NAME,Notes.TITLE+"AS"+
19 LiveFolders.NAME);
20 //AddmorecolumnshereformorerobustLiveFolders.
21 }
static语句模块经常被用来初始化类里面的字段部分。这里我们的sUriMatcher、sNotesProjectionMap、sLiveFolderProjectionMap都是一次初始化,以后基本上都无需改变的,所以我们就放到这里来进行静态static语句块的初始化了。
2-5进行sUriMatcher的初始化工作。需要注意的是NotePad.AUTHORITY是本ContentProvider所对应的Uri(这个Uri是整个Android中唯一标识的,由开发人员在AndroidManifest.xml中定义的)。后面类似notes,notes/#,live_folders/notes等便是在这个Uri上添加的部分,用于对应于全部note,单个note以及live folder中的note。在这里初始化了之后,以后调用sUriMatcher的match()方法时,就会返回对应的NOTESNOTE_IDLIVE_FOLDER_NOTES等int值,从而表示该Uri对应的种类。
7-12对sNotesProjectionMap进行了初始化,这里没有对column进行rename。而15-19行则对sLiveFolderProjectionMap初始化时进行了rename。

三,一个SQLiteOpenHelper

代码:

1 /**
2 *Thisclasshelpsopen,create,andupgradethedatabasefile.
3 */
4 privatestaticclassDatabaseHelperextendsSQLiteOpenHelper{
5
6 DatabaseHelper(Contextcontext){
7 super(context,DATABASE_NAME,null,DATABASE_VERSION);
8 }
9
10 @Override
11 publicvoidonCreate(SQLiteDatabasedb){
12 db.execSQL("CREATETABLE"+NOTES_TABLE_NAME+"("
13 +Notes._ID+"INTEGERPRIMARYKEY,"
14 +Notes.TITLE+"TEXT,"
15 +Notes.NOTE+"TEXT,"
16 +Notes.CREATED_DATE+"INTEGER,"
17 +Notes.MODIFIED_DATE+"INTEGER"
18 +");");
19 }
20
21 @Override
22 publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion){
23 Log.w(TAG,"Upgradingdatabasefromversion"+oldVersion+"to"
24 +newVersion+",whichwilldestroyallolddata");
25 db.execSQL("DROPTABLEIFEXISTSnotes");
26 onCreate(db);
27 }
28 }
29
30 privateDatabaseHelpermOpenHelper;
这里实现了一个继承于SQLiteOpenHelper的DatabaseHelper内部类。这个类被封装在这里是因为这个ContentProvider本身是和数据库进行打交道的,所以需要一个SQLiteOpenHelper来处理相关关系。具体该类怎么用自己Google吧。

四,方法部分

代码:

1 @Override
2 publicbooleanonCreate(){
3 mOpenHelper=newDatabaseHelper(getContext());
4 returntrue;
5 }
该方法用于对ContentProvider中的资源进行初始化,可以知道的是,这些处理是不可能在static语句块中实现的,因为会有一些要求,例如这里我们要传入一个Context实例给DataBaseHelper构造器,这在static语句块中是实现不了的。
1 @Override
2 publicCursorquery(Uriuri,String[]projection,Stringselection,String[]selectionArgs,
3 StringsortOrder){
4 SQLiteQueryBuilderqb=newSQLiteQueryBuilder();
5 qb.setTables(NOTES_TABLE_NAME);
6
7 switch(sUriMatcher.match(uri)){
8 caseNOTES:
9 qb.setProjectionMap(sNotesProjectionMap);
10 break;
11
12 caseNOTE_ID:
13 qb.setProjectionMap(sNotesProjectionMap);
14 qb.appendWhere(Notes._ID+"="+uri.getPathSegments().get(1));
15 break;
16
17 caseLIVE_FOLDER_NOTES:
18 qb.setProjectionMap(sLiveFolderProjectionMap);
19 break;
20
21 default:
22 thrownewIllegalArgumentException("UnknownURI"+uri);
23 }
24
25 //Ifnosortorderisspecifiedusethedefault
26 StringorderBy;
27 if(TextUtils.isEmpty(sortOrder)){
28 orderBy=NotePad.Notes.DEFAULT_SORT_ORDER;
29 }else{
30 orderBy=sortOrder;
31 }
32
33 //Getthedatabaseandrunthequery
34 SQLiteDatabasedb=mOpenHelper.getReadableDatabase();
35 Cursorc=qb.query(db,projection,selection,selectionArgs,null,null,orderBy);
36
37 //Tellthecursorwhaturitowatch,soitknowswhenitssourcedatachanges
38 c.setNotificationUri(getContext().getContentResolver(),uri);
39 returnc;
40 }
这个方法是我们的ContentProvider必须实现的,最重要的方法之一。当我们通过ContentProvider进行查询操作时,这个方法就会被回调,而且返回一个保存了查询结果的Cursor对象。
对于参数:
uri,查询时传入的uri,它代表了我们要查找的数据源。
projection,查询时传入的一个字符串数组,通过它来表明我们需要数据表中的哪些row。当查询结束后就会返回包含这些row的结果表。
selectionArgs,功能不清楚,猜想为表明结果表中各个row的排列顺序。
sortOrder,表示返回的结果集的排列顺序的语句。
方法体内:
4,声明了一个SQLiteQueryBuilder对象,该对象利用传入的参数生成一个完整的sqlite查询语句,还可以完成查询工作并返回结果集(Cursor)。
5,为该SQLiteQueryBuilder对象设置了它查询工作将要针对的表。
7-23通过一个switch语句,完成了通过uri种类的辨识来生成不同的SQLiteQueryBuilder对象的工作。而这里的uri辨识便用到了我们刚开始声明的UriMatcher对象。其中14行为SQLiteQueryBuilder设置了一个where查询条件。9,13,18则分别为不同的uri设置了不同的projection map。
27-31设置了查询返回结果的顺序。TextUtils.isEmpty(sortOrder)这一句判断传入的sortOrder是否为空字符串(即"")或者null。如果是的话就将orderBy设置为一个定义好的默认的顺序。否则用传入的顺序。
34,通过我们之前定义的SQLiteDatabase类来获得一个可读不可写的数据库对象。
35,通过我们刚刚构造的SQLiteQueryBuilder来进行查询操作,并且返回一个我们即将return的Cursor对象。
38,让Cursor来监视它所属的那个uri。
39,返回Cursor c,让外界通过它来获得查询结果。
1 @Override
2 publicStringgetType(Uriuri){
3 switch(sUriMatcher.match(uri)){
4 caseNOTES:
5 caseLIVE_FOLDER_NOTES:
6 returnNotes.CONTENT_TYPE;
7
8 caseNOTE_ID:
9 returnNotes.CONTENT_ITEM_TYPE;
10
11 default:
12 thrownewIllegalArgumentException("UnknownURI"+uri);
13 }
14 }
这个方法用于返回相关uri对应的MIME类型。这个类型基本上是自己定义的,而且是会用在Activity的启动判别中去的。需要注意的是,这里又用到了我们之前定义的uri判别工具——sUriMatcher
1 @Override
2 publicUriinsert(Uriuri,ContentValuesinitialValues){
3 //Validatetherequesteduri
4 if(sUriMatcher.match(uri)!=NOTES){
5 thrownewIllegalArgumentException("UnknownURI"+uri);
6 }
7
8 ContentValuesvalues;
9 if(initialValues!=null){
10 values=newContentValues(initialValues);
11 }else{
12 values=newContentValues();
13 }
14
15 Longnow=Long.valueOf(System.currentTimeMillis());
16
17 //Makesurethatthefieldsareallset
18 if(values.containsKey(NotePad.Notes.CREATED_DATE)==false){
19 values.put(NotePad.Notes.CREATED_DATE,now);
20 }
21
22 if(values.containsKey(NotePad.Notes.MODIFIED_DATE)==false){
23 values.put(NotePad.Notes.MODIFIED_DATE,now);
24 }
25
26 if(values.containsKey(NotePad.Notes.TITLE)==false){
27 Resourcesr=Resources.getSystem();
28 values.put(NotePad.Notes.TITLE,r.getString(android.R.string.untitled));
29 }
30
31 if(values.containsKey(NotePad.Notes.NOTE)==false){
32 values.put(NotePad.Notes.NOTE,"");
33 }
34
35 SQLiteDatabasedb=mOpenHelper.getWritableDatabase();
36 longrowId=db.insert(NOTES_TABLE_NAME,Notes.NOTE,values);
37 if(rowId>0){
38 UrinoteUri=ContentUris.withAppendedId(NotePad.Notes.CONTENT_URI,rowId);
39 getContext().getContentResolver().notifyChange(noteUri,null);
40 returnnoteUri;
41 }
42
43 thrownewSQLException("Failedtoinsertrowinto"+uri);
44 }
这个方法用于我们的ContentProvider进行insert操作。参数uri为我们即将进行insert操作的数据源。initialValues则是我们传入的要insert到数据库中去的信息。该方法返回我们insert进去的条目所对应的uri。
4-6进行异常判断,如果传入的uri不是我们想要的则抛出异常。
8,声明了一个本地的ContentValues,用于接下来的initialValues处理。
9-13,如果initialValues为null,则本地生成一个ContentValues对象,否则使用initialValues
15,通过方法调用获得了一个时间信息,将它保存到Long对象now里面去。now作为时间特性将会保存到数据库中的CREATED_DATEMODIFIED_DATE项目中去。
18-33,通过containsKey方法来判断我们的values里面是否包含了某一项条目,有则无动作,没有则通过本地获得信息并且放到values里面去。
35,获得一个可读可写的数据库对象。
36,执行insert操作,并且把insert方法返回的我们insert进去的项的_id保存在rowId中。
37,通过rowId来生成一个即将返回的uri。
1 @Override
2 publicintdelete(Uriuri,Stringwhere,String[]whereArgs){
3 SQLiteDatabasedb=mOpenHelper.getWritableDatabase();
4 intcount;
5 switch(sUriMatcher.match(uri)){
6 caseNOTES:
7 count=db.delete(NOTES_TABLE_NAME,where,whereArgs);
8 break;
9
10 caseNOTE_ID:
11 StringnoteId=uri.getPathSegments().get(1);
12 count=db.delete(NOTES_TABLE_NAME,Notes._ID+"="+noteId
13 +(!TextUtils.isEmpty(where)?"AND("+where+')':""),whereArgs);
14 break;
15
16 default:
17 thrownewIllegalArgumentException("UnknownURI"+uri);
18 }
19
20 getContext().getContentResolver().notifyChange(uri,null);
21 returncount;
22 }
23
24 @Override
25 publicintupdate(Uriuri,ContentValuesvalues,Stringwhere,String[]whereArgs){
26 SQLiteDatabasedb=mOpenHelper.getWritableDatabase();
27 intcount;
28 switch(sUriMatcher.match(uri)){
29 caseNOTES:
30 count=db.update(NOTES_TABLE_NAME,values,where,whereArgs);
31 break;
32
33 caseNOTE_ID:
34 StringnoteId=uri.getPathSegments().get(1);
35 count=db.update(NOTES_TABLE_NAME,values,Notes._ID+"="+noteId
36 +(!TextUtils.isEmpty(where)?"AND("+where+')':""),whereArgs);
37 break;
38
39 default:
40 thrownewIllegalArgumentException("UnknownURI"+uri);
41 }
42
43 getContext().getContentResolver().notifyChange(uri,null);
44 returncount;
45 }
46
47
48 }
delete方法和update方法则针对的是数据库中的删除条目和更新条目的操作看懂之前的代码后这里没有难度,所以就不解释了。
四,小结
在我的理解看来,ContentProvider是对各种数据源进行统一包装并外露的工具——不只是我们这里所用到的典型的数据库式的数据源,其他如文件等数据源也可以通过ContentProvider来包装并实现对其他有需求的消费者的数据供应。虽然对于初学者来说ContentProvider是较为难以理解的,但当你用熟后,你便定会感叹于Android系统的细节精妙性!
本文撰于2010-1-27晚10点华中科技大学IDream工作室,正值小年,屋外爆竹声声。明天回家,看爸妈。
分享到:
评论

相关推荐

    新版Android开发教程.rar

    也有分析认为,谷歌并不想做一个简单的手机终端制造商或者软件平台开发商,而意在一统传统互联网和 移 动互联网。----------------------------------- Android 编程基础 4 Android Android Android Android 手机新...

    Android典型技术模块开发详解

    2.2.1 程序结构 2.2.2 代码分析 2.3 权限permission 2.4 LogCat日志调试 2.5 示例练习 2.5.1 登录界面 2.5.2 事件处理 2.6 本章小结 第二篇 Android开发关键组件 第3章 Activity(活动) 3.1 什么是任务 3.2 ...

    android开发实例大全_王东华

    实例057: 使用ContentProvider开发一个 手机日记本 177 实例058: 使用文件保存数据 188 实例059: 将网上的图片保存到SD卡并在 手机中显示出来 190 实例060: 保存联系人信息 194 第5章 电话和短信实例集锦 197 ...

    android开发入门与实战(下)

    第5章 千里之行始于足下——第一个应用HelloWorld 5.1 HelloWorld应用分析 5.1.1 新建一个Android工程 5.1.2 填写工程的信息 5.1.3 编程实现 5.1.4 运行项目 5.2 调试项目 5.2.1 设置断点 5.2.2 Debug项目 5.2.3 ...

    Google.Android开发入门与实战

    书中以讲述实战实例为导向,用一个个典型应用生动地引领读者进行项目开发实践。作为一本既及时、又翔实、理论实践相结合的教程,《Google Android开发入门与实战》一书很值得入门者阅读。  值得一提的是,书中的...

    android开发入门与实战(上)

    第5章 千里之行始于足下——第一个应用HelloWorld 5.1 HelloWorld应用分析 5.1.1 新建一个Android工程 5.1.2 填写工程的信息 5.1.3 编程实现 5.1.4 运行项目 5.2 调试项目 5.2.1 设置断点 5.2.2 Debug项目 5.2.3 ...

    《Google Android开发入门与实战》.pdf

    第5章 千里之行 始于足下——第一个应用helloworld 52 5.1 helloworld应用分析 52 5.1.1 新建一个android工程 52 5.1.2 填写工程的信息 52 5.1.3 编程实现 53 5.1.4 运行项目 54 5.2 调试项目 ...

    Google Android开发入门与实战的代码

    第5章 千里之行 始于足下——第一个应用HelloWorld 52 5.1 HelloWorld应用分析 52 5.1.1 新建一个Android工程 52 5.1.2 填写工程的信息 52 5.1.3 编程实现 53 5.1.4 运行项目 54 5.2 调试项目 ...

    2010年谢彦的android笔记

    3.2.3 操作调试数据库与ContentProvider详解 53 3.2.4 电源管理 56 3.2.5 开发桌面小程序AppWidget 58 3.2.6 代码中运行二进制程序或脚本 60 3.2.7 Android自带的md5校验 61 3.2.8 将数据打进apk包 63 3.2.9 如何...

    android笔记.rar

    3.2.3 操作调试数据库与ContentProvider详解... ..53 3.2.4 电源管理... .56 3.2.5 开发桌面小程序AppWidget ... ...58 3.2.6 代码中运行二进制程序或脚本 ... .60 3.2.7 Android自带的md5 校验 ... ..61 3.2.8 将...

Global site tag (gtag.js) - Google Analytics