近日来学习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" />
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 }
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 }
二: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" />
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 }
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 }
我认为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