上文简要介绍了Android应用程序组件Content Provider在应用程序间共享数据的原理,但是没有进一步研究它的实现。本文将实现两个应用程序,其中一个以Content Provider的形式来提供数据访问入口,另一个通过这个Content Provider来访问这些数据。本文的例子不仅可以为下文分析Content Provider的实现原理准备好使用情景,还可以学习到它的一个未公开接口。 本文中的应用程序是按照上一篇文章 Android应用程序组件Content Provider简要介绍和学习计划 中提到的一般应用程序架构方法来设计的。本文包含两个应用程序,其中,第一个应用程序命名为ArticlesProvider,它使用了SQLite数据库来维护一个文章信息列表,同时,它定义了访问这个文章信息列表的URI,这样,我们就可以通过一个Content Provider组件来向第三方应用程序提供访问这个文章信息列表的接口;第二个应用程序命名为Article,它提供了管理保存在ArticlesProvider应用程序中的文章信息的界面入口,在这个应用程序中,用户可以添加、删除和修改这些文章信息。接下来我们就分别介绍这两个应用程序的实现。 1. ArticlesProvider应用程序的实现 首先是参照 在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务 一文,在packages/experimental目录下建立工程文件目录ArticlesProvider。在继续介绍这个应用程序的实现之前,我们先介绍一下这个应用程序用来保存文章信息的数据库的设计。 我们知道,在Android系统中,内置了一款轻型的数据库SQLite。SQLite是专门为嵌入式产品而设计的,它具有占用资源低的特点,而且是开源的,非常适合在Android平台中使用,关于SQLite的更多信息可以访问官方网站 http://www.sqlite.org ArticlesProvider应用程序就是使用SQLite来作为数据库保存文章信息的,数据库文件命名为Articles.db,它里面只有一张表ArticlesTable,表的结构如下所示: ------------------------------------------------------------- |--_id--|-- _title--|--_abstrat--|--_url--| ------------------------------------------------------------- | | | | | 它由四个字段表示,第一个字段_id表示文章的ID,类型为自动递增的integer,它作为表的key值;第二个字段_title表示文章的题目,类型为text;第三个字段_abstract表示文章的摘要,类型为text;第四个字段_url表示文章的URL,类型为text。注意,当我们打算将数据库表的某一列的数据作为一个数据行的ID时,就约定它的列名为_id。这是因为我们经常需要从数据库中获取一批数据,这些数据以Cursor的形式返回,对这些返回来的数据我们一般用一个ListView来显示,而这个ListView需要一个数据适配器Adapter来作为数据源,这时候就我们就可以以这个Cursor来构造一个Adapter。有些Adapter,例如android.widget.CursorAdapter,它们在实现自己的getItemId成员函数来获取指定数据行的ID时,就必须要从这个Cursor中相应的行里面取出列名为_id的字段的内容出来作为这个数据行的ID返回给调用者。当然,我们不在数据库表中定义这个_id列名也是可以的,不过这样从数据库中查询数据后得到的Cursor适合性就变差了,因此,建议我们在设计数据库表时,尽量设置其中一个列名字_id,并且保证这一列的内容是在数据库表中是唯一的。 下面我们就开始介绍这个应用程序的实现了。这个应用程序只有两个源文件,分别是Articles.java和ArticlesProvider,都是放在shy.luo.providers.articles这个package下面。在Articles.java文件里面,主要是定义了一些常量,例如用来访问文章信息数据的URI、MIME(Multipurpose Internet Mail Extensions)类型以及格式等,这些常量是第三方应用程序访问这些文章信息数据时要使用到的,因此,我们把它定义在一个单独的文件中,稍后我们会介绍如果把这个Articles.java文件打包成一个jar文件,然后第三方应用程序就可以引用这个常量了,这样也避免了直接把这个源代码文件暴露给第三方应用程序。 源文件Articles.java位于src/shy/luo/providers/articles目录下,它的内容如下所示:
        
  1. packageshy.luo.providers.articles;
  2. importandroid.net.Uri;
  3. publicclassArticles{
  4. /*DataField*/
  5. publicstaticfinalStringID="_id";
  6. publicstaticfinalStringTITLE="_title";
  7. publicstaticfinalStringABSTRACT="_abstract";
  8. publicstaticfinalStringURL="_url";
  9. /*Defaultsortorder*/
  10. publicstaticfinalStringDEFAULT_SORT_ORDER="_idasc";
  11. /*CallMethod*/
  12. publicstaticfinalStringMETHOD_GET_ITEM_COUNT="METHOD_GET_ITEM_COUNT";
  13. publicstaticfinalStringKEY_ITEM_COUNT="KEY_ITEM_COUNT";
  14. /*Authority*/
  15. publicstaticfinalStringAUTHORITY="shy.luo.providers.articles";
  16. /*MatchCode*/
  17. publicstaticfinalintITEM=1;
  18. publicstaticfinalintITEM_ID=2;
  19. publicstaticfinalintITEM_POS=3;
  20. /*MIME*/
  21. publicstaticfinalStringCONTENT_TYPE="vnd.android.cursor.dir/vnd.shy.luo.article";
  22. publicstaticfinalStringCONTENT_ITEM_TYPE="vnd.android.cursor.item/vnd.shy.luo.article";
  23. /*ContentURI*/
  24. publicstaticfinalUriCONTENT_URI=Uri.parse("content://"+AUTHORITY+"/item");
  25. publicstaticfinalUriCONTENT_POS_URI=Uri.parse("content://"+AUTHORITY+"/pos");
  26. }
ID、TITLE、ABSTRACT和URL四个常量前面已经解释过了,它是我们用来保存文章信息的数据表的四个列名;DEFAULT_SORT_ORDER常量是调用ContentProvider接口的query函数来查询数据时用的,它表示对查询结果按照_id列的值从小到大排列;METHOD_GET_ITEM_COUNT和KEY_ITEM_COUNT两个常量是调用ContentProvider接口的一个未公开函数call来查询数据时用的,它类似于微软COM中的IDispatch接口的Invoke函数,使用这个call函数时,传入参数METHOD_GET_ITEM_COUNT表示我们要调用我们自定义的ContentProvider子类中的getItemCount函数来获取数据库中的文章信息条目的数量,结果放在一个Bundle中以KEY_ITEM_COUNT为关键字的域中。

剩下的常量都是跟数据URI相关的,这个需要详细解释一下。URI的全称是Universal Resource Identifier,即通用资源标志符,通过它用来唯一标志某个资源在网络中的位置,它的结构和我们常见的HTTP形式URL是一样的,其实我们可以把常见的HTTP形式的URL看成是URI结构的一个实例,URI是在更高一个层次上的抽象。在Android系统中,它也定义了自己的用来定痊某个特定的Content Provider的URI结构,它通常由四个组件来组成,如下所示: [content://][shy.luo.providers.articles][/item][/123] |------A------|-----------------B-------------------|---C---|---D--| A组件称为Scheme,它固定为content://,表示它后面的路径所表示的资源是由Content Provider来提供的。 B组件称为Authority,它唯一地标识了一个特定的Content Provider,因此,这部分内容一般使用Content Provider所在的package来命名,使得它是唯一的。 C组件称为资源路径,它表示所请求的资源的类型,这部分内容是可选的。如果我们自己所实现的Content Provider只提供一种类型的资源访问,那么这部分内部就可以忽略;如果我们自己实现的Content Provider同时提供了多种类型的资源访问,那么这部分内容就不可以忽略了。例如,我们有两种电脑资源可以提供给用户访问,一种是笔记本电脑,一种是平板电脑,我们就把分别它们定义为notebook和pad;如果我们想进一步按照系统类型来进一步细分这两种电脑资源,对笔记本电脑来说,一种是安装了windows系统的,一种是安装了linux系统的,我们就分别把它们定义为notebook/windows和notebook/linux;对平板电脑来说,一种是安装了ios系统的,一种是安装了android系统的,我们就分别把它们定义为pad/ios和pad/android。 D组件称为资源ID,它表示所请求的是一个特定的资源,它通常是一个数字,对应前面我们所介绍的数据库表中的_id字段的内容,它唯一地标志了某一种资源下的一个特定的实例。继续以前面的电脑资源为例,如果我们请求的是编号为123的装了android系统的平板电脑,我们就把它定义为pad/android/123。当忽略这部分内容时,它有可能是表示请求某一种资源下的所有实例,取决于我们的URI匹配规则,后面我们将会进一步解释如何设置URI匹配规则。 回到上面的Articles.java源文件中,我们定义了两个URI,分别用COTENT_URI和CONTENT_POS_URI两个常量来表示,它们的Authority组件均指定为shy.luo.providers.articles。其中,COTENT_URI常量表示的URI表示是通过ID来访问文章信息的,而CONTENT_POS_URI常量表示的URI表示是通过位置来访问文章信息的。例如,content://shy.luo.providers.articles/item表示访问所有的文章信息条目;content://shy.luo.providers.articles/item/123表示只访问ID值为123的文章信息条目;content://shy.luo.providers.articles/pos/1表示访问数据库表中的第1条文章信息条目,这条文章信息条目的ID值不一定为1。通过常量CONTENT_POS_URI来访问文章信息条目时,必须要指定位置,这也是我们设置的URI匹配规则来指定的,后面我们将会看到。 此外,我们还需要定义与URI对应的资源的MIME类型。每个MIME类型由两部分组成,前面是数据的大类别,后面定义具体的种类。在Content Provider中,URI所对应的资源的MIME类型的大类别根据同时访问的资源的数量分为两种,对于访问单个资源的URI,它的大类别就为vnd.android.cursor.item,而对于同时访问多个资源的URI,它的大类别就为vnd.android.cursor.dir。Content Provider的URI所对应的资源的MIME类型的具体类别就需要由Content Provider的提供者来设置了,它的格式一般为vnd.[company name].[resource type]的形式。例如,在我们的例子中,CONTENT_TYPE和COTENT_ITEM_TYPE两个常量分别定义了两种MIME类型,它们的大类别分别为vnd.android.cursor.dir和vnd.android.cursor.item,而具体类别均为vdn.shy.luo.article,其中shy.luo就是表示公司名了,而article表示资源的类型为文章。这两个MIME类型常量主要是在实现ContentProvider的getType函数时用到的,后面我们将会看到。 最后,ITEM、ITEM_ID和POS_ID三个常量分别定了三个URI匹配规则的匹配码。如果URI的形式为content://shy.luo.providers.articles/item,则匹配规则返回的匹配码为ITEM;如果URI的形式为content://shy.luo.providers.articles/item/#,其中#表示任意一个数字,则匹配规则返回的匹配码为ITEM_ID;如果URI的形式为#也是表示任意一个数字,则匹配规则返回的匹配码为ITEM_POS。这三个常量的用法我们在后面也将会看到。 这样,Articles.java文件的内容就介绍完了。下面我们再接着介绍位于src/shy/luo/providers/articles目录下的ArticlesProvider.java文件,它的内容如下所示:
        
  1. importjava.util.HashMap;
  2. importandroid.content.ContentValues;
  3. importandroid.content.Context;
  4. importandroid.content.UriMatcher;
  5. importandroid.content.ContentProvider;
  6. importandroid.content.ContentUris;
  7. importandroid.content.ContentResolver;
  8. importandroid.database.Cursor;
  9. importandroid.database.sqlite.SQLiteDatabase;
  10. importandroid.database.sqlite.SQLiteDatabase.CursorFactory;
  11. importandroid.database.sqlite.SQLiteException;
  12. importandroid.database.sqlite.SQLiteOpenHelper;
  13. importandroid.database.sqlite.SQLiteQueryBuilder;
  14. importandroid.net.Uri;
  15. importandroid.os.Bundle;
  16. importandroid.text.TextUtils;
  17. importandroid.util.Log;
  18. publicclassArticlesProviderextendsContentProvider{
  19. privatestaticfinalStringLOG_TAG="shy.luo.providers.articles.ArticlesProvider";
  20. privatestaticfinalStringDB_NAME="Articles.db";
  21. privatestaticfinalStringDB_TABLE="ArticlesTable";
  22. privatestaticfinalintDB_VERSION=1;
  23. privatestaticfinalStringDB_CREATE="createtable"+DB_TABLE+
  24. "("+Articles.ID+"integerprimarykeyautoincrement,"+
  25. Articles.TITLE+"textnotnull,"+
  26. Articles.ABSTRACT+"textnotnull,"+
  27. Articles.URL+"textnotnull);";
  28. privatestaticfinalUriMatcheruriMatcher;
  29. static{
  30. uriMatcher=newUriMatcher(UriMatcher.NO_MATCH);
  31. uriMatcher.addURI(Articles.AUTHORITY,"item",Articles.ITEM);
  32. uriMatcher.addURI(Articles.AUTHORITY,"item/#",Articles.ITEM_ID);
  33. uriMatcher.addURI(Articles.AUTHORITY,"pos/#",Articles.ITEM_POS);
  34. }
  35. privatestaticfinalHashMap<String,String>articleProjectionMap;
  36. static{
  37. articleProjectionMap=newHashMap<String,String>();
  38. articleProjectionMap.put(Articles.ID,Articles.ID);
  39. articleProjectionMap.put(Articles.TITLE,Articles.TITLE);
  40. articleProjectionMap.put(Articles.ABSTRACT,Articles.ABSTRACT);
  41. articleProjectionMap.put(Articles.URL,Articles.URL);
  42. }
  43. privateDBHelperdbHelper=null;
  44. privateContentResolverresolver=null;
  45. @Override
  46. publicbooleanonCreate(){
  47. Contextcontext=getContext();
  48. resolver=context.getContentResolver();
  49. dbHelper=newDBHelper(context,DB_NAME,null,DB_VERSION);
  50. Log.i(LOG_TAG,"ArticlesProviderCreate");
  51. returntrue;
  52. }
  53. @Override
  54. publicStringgetType(Uriuri){
  55. switch(uriMatcher.match(uri)){
  56. caseArticles.ITEM:
  57. returnArticles.CONTENT_TYPE;
  58. caseArticles.ITEM_ID:
  59. caseArticles.ITEM_POS:
  60. returnArticles.CONTENT_ITEM_TYPE;
  61. default:
  62. thrownewIllegalArgumentException("ErrorUri:"+uri);
  63. }
  64. }
  65. @Override
  66. publicUriinsert(Uriuri,ContentValuesvalues){
  67. if(uriMatcher.match(uri)!=Articles.ITEM){
  68. thrownewIllegalArgumentException("ErrorUri:"+uri);
  69. }
  70. SQLiteDatabasedb=dbHelper.getWritableDatabase();
  71. longid=db.insert(DB_TABLE,Articles.ID,values);
  72. if(id<0){
  73. thrownewSQLiteException("Unabletoinsert"+values+"for"+uri);
  74. }
  75. UrinewUri=ContentUris.withAppendedId(uri,id);
  76. resolver.notifyChange(newUri,null);
  77. returnnewUri;
  78. }
  79. @Override
  80. publicintupdate(Uriuri,ContentValuesvalues,Stringselection,String[]selectionArgs){
  81. SQLiteDatabasedb=dbHelper.getWritableDatabase();
  82. intcount=0;
  83. switch(uriMatcher.match(uri)){
  84. caseArticles.ITEM:{
  85. count=db.update(DB_TABLE,values,selection,selectionArgs);
  86. break;
  87. }
  88. caseArticles.ITEM_ID:{
  89. Stringid=uri.getPathSegments().get(1);
  90. count=db.update(DB_TABLE,values,Articles.ID+"="+id
  91. +(!TextUtils.isEmpty(selection)?"and("+selection+')':""),selectionArgs);
  92. break;
  93. }
  94. default:
  95. thrownewIllegalArgumentException("ErrorUri:"+uri);
  96. }
  97. resolver.notifyChange(uri,null);
  98. returncount;
  99. }
  100. @Override
  101. publicintdelete(Uriuri,Stringselection,String[]selectionArgs){
  102. SQLiteDatabasedb=dbHelper.getWritableDatabase();
  103. intcount=0;
  104. switch(uriMatcher.match(uri)){
  105. caseArticles.ITEM:{
  106. count=db.delete(DB_TABLE,selection,selectionArgs);
  107. break;
  108. }
  109. caseArticles.ITEM_ID:{
  110. Stringid=uri.getPathSegments().get(1);
  111. count=db.delete(DB_TABLE,Articles.ID+"="+id
  112. +(!TextUtils.isEmpty(selection)?"and("+selection+')':""),selectionArgs);
  113. break;
  114. }
  115. default:
  116. thrownewIllegalArgumentException("ErrorUri:"+uri);
  117. }
  118. resolver.notifyChange(uri,null);
  119. returncount;
  120. }
  121. @Override
  122. publicCursorquery(Uriuri,String[]projection,Stringselection,String[]selectionArgs,StringsortOrder){
  123. Log.i(LOG_TAG,"ArticlesProvider.query:"+uri);
  124. SQLiteDatabasedb=dbHelper.getReadableDatabase();
  125. SQLiteQueryBuildersqlBuilder=newSQLiteQueryBuilder();
  126. Stringlimit=null;
  127. switch(uriMatcher.match(uri)){
  128. caseArticles.ITEM:{
  129. sqlBuilder.setTables(DB_TABLE);
  130. sqlBuilder.setProjectionMap(articleProjectionMap);
  131. break;
  132. }
  133. caseArticles.ITEM_ID:{
  134. Stringid=uri.getPathSegments().get(1);
  135. sqlBuilder.setTables(DB_TABLE);
  136. sqlBuilder.setProjectionMap(articleProjectionMap);
  137. sqlBuilder.appendWhere(Articles.ID+"="+id);
  138. break;
  139. }
  140. caseArticles.ITEM_POS:{
  141. Stringpos=uri.getPathSegments().get(1);
  142. sqlBuilder.setTables(DB_TABLE);
  143. sqlBuilder.setProjectionMap(articleProjectionMap);
  144. limit=pos+",1";
  145. break;
  146. }
  147. default:
  148. thrownewIllegalArgumentException("ErrorUri:"+uri);
  149. }
  150. Cursorcursor=sqlBuilder.query(db,projection,selection,selectionArgs,null,null,TextUtils.isEmpty(sortOrder)?Articles.DEFAULT_SORT_ORDER:sortOrder,limit);
  151. cursor.setNotificationUri(resolver,uri);
  152. returncursor;
  153. }
  154. @Override
  155. publicBundlecall(Stringmethod,Stringrequest,Bundleargs){
  156. Log.i(LOG_TAG,"ArticlesProvider.call:"+method);
  157. if(method.equals(Articles.METHOD_GET_ITEM_COUNT)){
  158. returngetItemCount();
  159. }
  160. thrownewIllegalArgumentException("Errormethodcall:"+method);
  161. }
  162. privateBundlegetItemCount(){
  163. Log.i(LOG_TAG,"ArticlesProvider.getItemCount");
  164. SQLiteDatabasedb=dbHelper.getReadableDatabase();
  165. Cursorcursor=db.rawQuery("selectcount(*)from"+DB_TABLE,null);
  166. intcount=0;
  167. if(cursor.moveToFirst()){
  168. count=cursor.getInt(0);
  169. }
  170. Bundlebundle=newBundle();
  171. bundle.putInt(Articles.KEY_ITEM_COUNT,count);
  172. cursor.close();
  173. db.close();
  174. returnbundle;
  175. }
  176. privatestaticclassDBHelperextendsSQLiteOpenHelper{
  177. publicDBHelper(Contextcontext,Stringname,CursorFactoryfactory,intversion){
  178. super(context,name,factory,version);
  179. }
  180. @Override
  181. publicvoidonCreate(SQLiteDatabasedb){
  182. db.execSQL(DB_CREATE);
  183. }
  184. @Override
  185. publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion){
  186. db.execSQL("DROPTABLEIFEXISTS"+DB_TABLE);
  187. onCreate(db);
  188. }
  189. }
  190. }

我们在实现自己的Content Provider时,必须继承于ContentProvider类,并且实现以下六个函数: -- onCreate(),用来执行一些初始化的工作。 -- query(Uri, String[], String, String[], String),用来返回数据给调用者。 -- insert(Uri, ContentValues),用来插入新的数据。 -- update(Uri, ContentValues, String, String[]),用来更新已有的数据。 -- delete(Uri, String, String[]),用来删除数据。 -- getType(Uri),用来返回数据的MIME类型。
这些函数的实现都比较简单,这里我们就不详细介绍了,主要解释五个要点。

更多相关文章

  1. Android(安卓)Volley 库通过网络获取 JSON 数据
  2. 音频采集(AudioRecorder)
  3. windwos安装Android(安卓)NDK(Native Development Kit)
  4. 【Android】ORM框架greenDao 3
  5. Android之浅谈Android中的MVP
  6. 感受LiveData 与 ViewModel结合之美
  7. Android编程心得-Service数据绑定初步
  8. Android强大的数据库开源框架LitePal
  9. Android(安卓)进程间通信的几种实现方式

随机推荐

  1. 基本空间划分
  2. Android之从网络中获取数据并返回客户端
  3. This text field does not specify an in
  4. unity工程接入Android(安卓)sdk后真机测
  5. android中用XMPP Asmack获取用户朋友的pr
  6. android 拼音字母搜索联系人以及实现多选
  7. Android(安卓)SDK Manager:Failed to fetc
  8. Android(安卓)开发小技巧1: Button 点击区
  9. Android进行设备管理(针对企业开发)
  10. 浅谈android的selector背景选择器