ContentProvider使用总结

  近日来学习ContentProvider相关的知识,做了一个demo,想和网友分享下。

  首先说一点相关的知识:

  一:作用

  ContentProvider是不同应用程序共享数据的接口,跟共享数据的别的方法相比,ContentProvider更好地提供了数据共享接口的统一性。CP(ContentProvider的简称)通过一张或者多张表的形式向外部应用程序提供数据(就像关系型数据库中看见的表那样)。

  二:Content URIS

  URI(Uniform Resource Identifier)统一资源标示符,能表示provider中的数据。由三部分组成,分别是scheme,authority,path.它的scheme,Android系统规定为"content://",authority唯一表示了要访问的CP名字,而path表示了要访问的CP中的数据。在使用ContentResolver对象访问CP时,需要一个Uri参数。构造Uri时可能会用到三个类:

  Uri.Builder类可用来通过String构建Uri

  ContentUris.withAppendedId()可在Uri后面加一个id

  UriMatcher:在CP中这个类可帮你从接受到Uri中选择出要执行的ACTION

  三:MIME type

  MIME,多用途互联网邮件扩展,是一种互联网标准。它的作用是:服务器会通过MIME值来告诉客户端所传送的多媒体数据的类型。CP也是一种C/S架构,所以需要使用到这个值。它的格式为:type/subtype,比如text/html,代表所传输的文本为html的格式。在Android中MIME可提供两类值:统一的MIME数据类型和定制的MIME类型字符串。在CP中前者在传输文件时会用到,而后者更常用,发送结构化的数据,譬如SQLiteDatabase时会使用定制的MIME类型字符串。在android中后者有自己的规定:

  如果返回单行数据,type被设置为"vnd.android.cursor.item"

  如果返回多行数据,type被设置为"vnd.android.cursor.dir"

  而subtype部分由CP类自己设置。

  接下来进入正题,说代码环节,这篇代码是通过结构化数据来阐述CP的。

  一:客户端

  在客户端,是通过一个ContentResolver对象作为client和CP交互的。ContentResolver对象可调用CP中的同名方法,可提供“增删改查”的功能。

  Step1:在客户端的manifest文件申请要访问的CP的权限(该权限在CP端已向系统注册,下文会讲)

1 <uses-permission android:name="com.example.cpserver.permission" />
View Code

  Step1.在“增删改查”之前需要判断将要请求的Uri的有效性,代码如下:

 1 //判断所要使用的Provider是否有效
 2     private boolean checkValidProvider(Uri uri)
 3     { 
 4         ContentProviderClient client = getContentResolver().acquireContentProviderClient(uri);
 5         if(client == null)
 6         {
 7             System.out.println("provider is invalid!");
 8             return false;
 9         }
10         else
11         {
12             client.release();
13             return true;
14         }
15     }
View Code

  Step3:"增删改查"功能,这些方法都是SQL中DDL语言的一个封装,具体每个方法的返回值,参数意义不展开讲了,否则很长,可留言询问。

 1 private int insert()
 2     {
 3         if(!checkValidProvider(Contract.CONTENT_URI))
 4             return -1;
 5         ContentValues values = new ContentValues();
 6         values.put(Contract.COLUMN_NAME_1, "小卫的春天");
 7         values.put(Contract.COLUMN_NAME_2, "翟卫华");
 8         values.put(Contract.COLUMN_NAME_3, "100");
 9         Uri uri = getContentResolver().insert(Contract.CONTENT_URI, values);
10         String lastPath = uri.getLastPathSegment();
11         if(TextUtils.isEmpty(lastPath)) 
12         {
13             System.out.println("insert failure!");
14         } 
15         else 
16         {
17             System.out.println("insert success! the id is " + lastPath);
18         }
19         return Integer.parseInt(lastPath);
20     }
21     
22     //删除所有行
23     private int delete()
24     {
25         if(!checkValidProvider(Contract.CONTENT_URI))
26             return -1;
27         int count = getContentResolver().delete(Contract.CONTENT_URI, null, null);
28         return count;
29     }
30     
31     //将所有行的数据进行一个修改
32     private int update()
33     {
34         if(!checkValidProvider(Contract.CONTENT_URI))
35             return -1;
36         ContentValues values = new ContentValues();
37         values.put(Contract.COLUMN_NAME_1,"小宝的春天");
38         values.put(Contract.COLUMN_NAME_2, "翟小宝");
39         values.put(Contract.COLUMN_NAME_3, "200");
40         int count = getContentResolver().update(Contract.CONTENT_URI, values, null, null);
41         if(count == 0)
42         {
43             System.out.println("update failed!");
44         }
45         return count;
46     }
47     
48     //row:要查找的行号, "-1"代表查找多行
49     private void query(int row)
50     {
51         if(!checkValidProvider(Contract.CONTENT_URI))
52             return;
53         Cursor cursor = null;
54         if(row != -1)
55         {
56             
57         }
58         else
59         {
60             cursor = getContentResolver().query(Contract.CONTENT_URI,
61                     new String[]{Contract.COLUMN_NAME_1,Contract.COLUMN_NAME_2,Contract.COLUMN_NAME_3},null, null, null);
62         }
63         if(cursor == null)
64             System.out.println("query failure!");
65         else
66         {
67             String strDisplay = getDataFromCursor(cursor);    
68             tvDisplay.setText(strDisplay);
69             cursor.close();
70         }
71     }
View Code

  二:CP端

  在CP端要通过继承ContentProvider来实现。CP是Android的四大组件之一,比较重要,而且系统本身就能提供很多的ContentProvider供开发者使用,比如可通过CP请求道所有的图片,音频,视频等数据。在Android系统中CP默认是可被别的任何应用程序请求到的,如果你不设置权限加以限制的话。所以第一步应该设置一个permission字段,并把它添加到provider标签里面去。

 1 <permission 
 2     android:name="com.example.cpserver.permission"
 3     android:label="Example Data"
 4     android:protectionLevel="signature" />
 5 
 6 <provider
 7             android:name="provider.TestProvider"
 8             android:authorities="com.example.cpserver.provider"
 9             android:permission="com.example.cpserver.permission"
10             android:exported="true"
11             android:enabled="true" />
View Code

  Step1:实现一个SQLiteOpenHelper作为Provider的数据存储库

 1 package db;
 2 
 3 import com.example.cpserver.Contract;
 4 
 5 import android.content.Context;
 6 import android.database.sqlite.SQLiteDatabase;
 7 import android.database.sqlite.SQLiteOpenHelper;
 8 /*
 9  * 实现一个SQLiteOpenHelper作为Provider的数据存储库
10  */
11 public final class MainDatabaseHelper extends SQLiteOpenHelper {
12 
13     //创建一张表的SQL语句
14     private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
15     Contract.TABLE_NAME + "(" +                           
16     " _ID INTEGER PRIMARY KEY, " +
17     Contract.COLUMN_NAME_1 + " TEXT," +
18     Contract.COLUMN_NAME_2 + " TEXT," +
19     Contract.COLUMN_NAME_3 + " INTEGER )";
20     
21     public MainDatabaseHelper(Context context) 
22     {
23         super(context, Contract.DB_NAME, null, 1);
24     }
25 
26     /*
27      *当Provider设法打开数据存储库,并且数据库不存在时该方法被调用
28      */
29     @Override
30     public void onCreate(SQLiteDatabase db) {
31         // Creates the main table
32         db.execSQL(SQL_CREATE_MAIN);
33     }
34 
35     @Override
36     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
37         // TODO Auto-generated method stub
38         
39     }
40 }
View Code

  Step2:实现ContentProvider的子类,并且扩展抽象方法,代码注释很详细。

  1 package provider;
  2 
  3 import com.example.cpserver.Contract;
  4 import db.MainDatabaseHelper;
  5 import android.content.ContentProvider;
  6 import android.content.ContentUris;
  7 import android.content.ContentValues;
  8 import android.content.UriMatcher;
  9 import android.database.Cursor;
 10 import android.database.SQLException;
 11 import android.database.sqlite.SQLiteDatabase;
 12 import android.net.Uri;
 13 import android.text.TextUtils;
 14 
 15 /*
 16  * 1.除了onCreate方法,别的方法都可能会被多线程调用,所以这些方法要设计成线程安全的
 17  * 2.在onCreate中避免耗时操作
 18  * 3.虽然以下方法都要被继承,但不必重写每个方法,除了getType
 19  */
 20 public class TestProvider extends ContentProvider 
 21 {
 22     //创建一个UriMatcher对象,该对象帮你从接受的URI中选择出要执行的动作
 23     private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 24     private MainDatabaseHelper mOpenHelper;
 25     private SQLiteDatabase db = null;
 26     
 27     //调用addURI方法添加provider可以识别的所有URI类型
 28     static
 29     {
 30         sUriMatcher.addURI(Contract.authority, Contract.TABLE_NAME, 1);
 31         sUriMatcher.addURI(Contract.authority, Contract.TABLE_NAME+"/#", 2);
 32     }
 33     /*
 34      * 该方法作用:初始化该Provider
 35      * 注意:直到一个ContentResolver对象访问时,该方法才被调用
 36      * 这个方法里不应做耗时的操作,因为这样可能延缓Provider对别的应用程序的相应
 37      */
 38     @Override
 39     public boolean onCreate() {
 40         mOpenHelper = new MainDatabaseHelper(getContext());
 41         return true;
 42     }
 43 
 44     @Override
 45     public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) 
 46     {
 47         int type = sUriMatcher.match(uri);
 48         switch (type)
 49         {
 50         //多行请求的URI
 51         case 1:
 52             if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
 53             break;
 54         case 2:
 55             //单行请求的URI
 56             selection = selection + "_ID = " + uri.getLastPathSegment();
 57             break;
 58         default:
 59            //如果URI不匹配,做一些错误提示,返回null或者抛出异常
 60             throw new IllegalArgumentException("Unknown URI " + uri);
 61         }
 62         if(db == null)
 63             db = mOpenHelper.getWritableDatabase();
 64         Cursor cursor = db.query(Contract.TABLE_NAME, projection, selection, selectionArgs, null, null,sortOrder);
 65         return cursor;
 66     }
 67 
 68     //返回content URI相应的MIME 类型
 69     @Override
 70     public String getType(Uri uri) 
 71     {
 72         int type = sUriMatcher.match(uri);
 73         switch (type) 
 74         {
 75         case 1:
 76             return Contract.CONTENT_TYPE;
 77         case 2:
 78             return Contract.CONTENT_ITEM_TYPE;
 79         default:
 80             throw new IllegalArgumentException("Unknown URI " + uri);
 81         }
 82     }
 83 
 84     @Override
 85     public Uri insert(Uri uri, ContentValues initialValues) 
 86     {
 87         int type = sUriMatcher.match(uri);
 88         if(type != 1)
 89             throw new IllegalArgumentException("Unknown URI " + uri);
 90         
 91         //创建一个可写数据库,将调用MainDatabaseHelper的onCreate方法,如果数据库还不存在的话
 92         if(db == null)
 93             db = mOpenHelper.getWritableDatabase();
 94         
 95         //确保所有的域都被设置
 96         ContentValues values;
 97         if (initialValues != null) 
 98             values = new ContentValues(initialValues);
 99         else 
100             values = new ContentValues();
101         if (values.containsKey(Contract.COLUMN_NAME_1) == false) {
102             values.put(Contract.COLUMN_NAME_1, "");
103         }
104         if (values.containsKey(Contract.COLUMN_NAME_2) == false) {
105             values.put(Contract.COLUMN_NAME_2, "");
106         }
107         if (values.containsKey(Contract.COLUMN_NAME_3) == false) {
108             values.put(Contract.COLUMN_NAME_3, "");
109         }
110         
111         long rowId = db.insert(Contract.TABLE_NAME,null, values);
112         if(rowId > 0)
113         {
114             Uri noteUri = ContentUris.withAppendedId(Contract.CONTENT_URI, rowId);
115             getContext().getContentResolver().notifyChange(noteUri, null);
116             return noteUri;
117         }
118         throw new SQLException("Failed to insert row into " + uri);
119     }
120 
121     @Override
122     public int delete(Uri uri, String selection, String[] selectionArgs) 
123     {
124         if(db == null)
125             db = mOpenHelper.getWritableDatabase();
126         int type = sUriMatcher.match(uri);
127         int count;
128         switch (type)
129         {
130             case 1:
131                 count = db.delete(Contract.TABLE_NAME, selection, selectionArgs);
132                 break;
133             case 2:
134                 String noteId = uri.getLastPathSegment();
135                 count = db.delete(Contract.TABLE_NAME, "_ID" + "=" + noteId +
136                         (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs);
137                 break;
138             default:
139                 throw new IllegalArgumentException("Unknown URI " + uri);
140         }
141         getContext().getContentResolver().notifyChange(uri, null);
142         return count;
143     }
144 
145     @Override
146     public int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) 
147     {
148         if(db == null)
149             db = mOpenHelper.getWritableDatabase();
150         int type = sUriMatcher.match(uri);
151         int count;
152         switch (type)
153         {
154             case 1:
155                 count = db.update(Contract.TABLE_NAME, values, selection, selectionArgs);
156                 break;
157             case 2:
158                 String noteId = uri.getLastPathSegment();
159                 count = db.update(Contract.TABLE_NAME, values,"_ID" + "=" + noteId +
160                         (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs);
161                 break;
162             default:
163                 throw new IllegalArgumentException("Unknown URI " + uri);
164         }
165         getContext().getContentResolver().notifyChange(uri, null);
166         return count;
167     }
168 
169     //如果Provider提供file数据,要用这个方法返回MIME类型
170     @Override
171     public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
172         // TODO Auto-generated method stub
173         return super.getStreamTypes(uri, mimeTypeFilter);
174     }
175 
176 }
View Code

 

  我认为CP之所以不那么容易理解,是因为它涉及到的东西较多,还涉及到计算机网络中的的URI,MIME等概念,确实不是那么容易,作为码农的我们只能去啃了,无别的办法。总结一下,CP机制中主要涉及到这么几点知识:permission权限,uri,MIME,使用UriMatcher来匹配uri的类型,还要掌握一些基本的SQL语言等。关于使用CP来实现File分享,又是很长的一个篇幅,等得空再研究吧。我的项目用百度云做个下载链接吧,有需求的朋友可拿去用。欢迎留言交流。

http://pan.baidu.com/s/1gdkPia3

 

  Author:Andy Zhai

  2014-02-11    20:44:00

  

  

posted @ 2014-02-11 20:49  Andy Zhai  阅读(1530)  评论(0编辑  收藏  举报