解读ContentResolver和ContentProvider

转自:http://cthhqu.blog.51cto.com/7598297/1281217html

 

1. ContentProvider的概述
ContentProvider:
(Official Definition)Content providers manage access to a structured set of data. They encapsulate the data, and provide mechanisms for defining data security. Content providers are the standard interface that connects data in one process with code running in another process.
       由官方的定义咱们能够得知它是一个管理访问结构化数据的机制。咱们系统中有些数据很重要,不能让人随便访问,可是由于比较有价值,因此不少应用程序须要用到它,这是就可经过ContentProvider这个机制,压缩数据,提供安全定义、访问数据的机制。该机制提供一个借口,使得应用程序能从该进程访问另一个应用程序的数据。
       不只是系统重要的数据,若是咱们开发过程当中有数据也是比较重要,可是须要提供给多个程序访问,这个时候也能够用到ContentProvider机制,把咱们的数据的增删改查分装在ContentProvider的增删改查中,并增长相应的安全机制,使得用户可咱们规定的安全机制下访问咱们的数据。
 ContentProvider还有一个重要的特色就是它是可使得某些数据能够被跨进程访问,通常咱们的数据库是不可跨进程被访问,由于数据库通常的数据是属于某个应用程序的,若是其余程序能够随意访问其数据库,这是很危险的,可是若是该应用程序的数据想分享给其余应用程序,那么就能够经过创建一个ContentProvider,规定一些安全机制,屏蔽一些比较重要的数据被访问,或是规定访问权限,好比只可读不可写等,使其余应用程序在有限制的前提下访问咱们的数据。这样作就会起到对数据进行保护同时又能使得有用的数据能被分享的做用。
2. ContentResolver和ContentProvider的使用方法
   2.1 ContentResolver的使用方法:
       首先,咱们得了解如何经过ContentResolver来对系统或咱们自定义的ContentProvider进行交互,一般,咱们经常使用到的是访问系统的数据,常见的有通信录、日历、短信、多媒体等,下面以经过ContentResolver获取系统的通信录为例子,简单演示是如何使用ContentResolver的:
使用方法概括:
一、从当前Activity获取系统的ContentResolver;
二、使用ContentProvider的insert、delete、update、query方法对ContentProvider的内容进行增删改查;
三、若是是使用query是的到一个Cursor的结果集,经过该结果集能够得到咱们查询的结果。
 
源代码示例:
1
2
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
48
49
50
51
package cth.android.contentprovide;
 
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ContentResolver;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.util.Log;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
 
 
/**
* @author CTH
*  
*该类是演示利用ContentProvider,获取手机的联系人信息。
*使用ContentProvide的步骤:
*一、从当前Activity获取系统的ContentResolver;
*二、使用ContentProvider的insert、delete、update、query方法对ContentProvider的内容进行增删改查;
*三、若是是使用query是的到一个Cursor的结果集,经过该结果集能够得到咱们查询的结果。
*
*/
public class MainActivity  extends Activity {
 
private ListView contactsList;
 
@SuppressLint ( "InlinedApi" )
protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);  //不使用inflate XML文件方法,而是使用动态生成控件。
 
contactsList =  new ListView(MainActivity. this );  
 
ContentResolver cr = getContentResolver();   //获取ContentResolver
Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI,  null ,
null null null );    //查询系统的联系人信息
Log.i( "cth" , cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME) +  "" );
@SuppressWarnings ( "deprecation" )
ListAdapter la =  new SimpleCursorAdapter(MainActivity. this ,
android.R.layout.simple_list_item_1, cursor,
new String[] {ContactsContract.Contacts.DISPLAY_NAME_PRIMARY },
new int [] { android.R.id.text1 });    //创建列表适配器,把cursor关联进来
 
 
contactsList.setAdapter(la);         //把ListVIew与适配器绑定
 
setContentView(contactsList);        //动态生成ListView
}
}
2.2 经常使用的系统URI
       访问系统提供的其余数据方法基本相似,只是传入的URI有所区别,这些URI都是API里面提供的,经过系统规定的不一样URI可访问系统不一样的数据。系统经常使用的URI以下:

联系人的URI:
ContactsContract.Contacts.CONTENT_URI 管理联系人的Uri
ContactsContract.CommonDataKinds.Phone.CONTENT_URI 管理联系人的电话的Uri
ContactsContract.CommonDataKinds.Email.CONTENT_URI 管理联系人的Email的Uri
(注:Contacts有两个表,分别是rawContact和Data,rawContact记录了用户的id和name,java

其中id栏名称 为:ContactsContract.Contacts._ID,name名称栏为ContactContract.Contracts.DISPLAY_NAME,android

电话信息表的外键id为 ContactsContract.CommonDataKinds.Phone.CONTACT_ID,sql

电话号码栏名称为:ContactsContract.CommonDataKinds.Phone.NUMBER。数据库

data表中Email地址栏名称为:ContactsContract.CommonDataKinds.Email.DATA
其外键栏为:ContactsContract.CommonDataKinds.Email.CONTACT_ID)api


多媒体的ContentProvider的Uri以下:
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI  存储在sd卡上的音频文件
MediaStore.Audio.Media.INTERNAL_CONTENT_URI  存储在手机内部存储器上的音频文件安全

MediaStore.Audio.Images.EXTERNAL_CONTENT_URI SD卡上的图片文件内容
MediaStore.Audio.Images.INTERNAL_CONTENT_URI 手机内部存储器上的图片
MediaStore.Audio.Video.EXTERNAL_CONTENT_URI SD卡上的视频
MediaStore.Audio.Video.INTERNAL_CONTENT_URI  手机内部存储器上的视频app

(注:图片的显示名栏:Media.DISPLAY_NAME,图片的详细描述栏为:Media.DESCRIPTION  图片的保存位置:Media.DATAide

短信URI: Content://sms函数

发送箱中的短信URI: Content://sms/outbox

2.3 ContentProvider的使用方法:
       那么咱们是如何自定义ContentProvider来对其余程序提供访问咱们数据的接口的呢?
 
       通常咱们若是有数据想被跨进程共享,就能够定义个ContentProvider,并在该ContentProvider定义访问该数据的方法,这些方法只容许外界传入一个URI和其余附加信息,该URI用于定位想操做数据的对象,而附加信息通常就是操做对象里具体数据的条件(例如SQL里面的where语句或者是SharedPreferences的键值对)。这样,就能作到对外界屏蔽了访问数据的方法,单纯开发URI和附加信息的接口,使得其余应用程序是在咱们规定的机制下访问数据。这样既能作到跨进程数据共享,又能消除访问不一样类型数据时访问方法的差别性,用户可不用管访问的数据类型是数据库或是其余类型而单纯用URI和附加信息定位操做数据对象,同时有提升了安全性。
2.3.1 URI简介:
       安卓中系统和用户自定义ContentProvider不止一个,那么当咱们想访问某个ContentProvider时,咱们是怎么定位到的呢?由上文可知咱们是经过URI定位到咱们具体想访问的数据,这时咱们得先了解用于定位ContentProvider的URI的构成以及各部分的意义。官网API文档是这样介绍的:
Content URIs have the syntax

content://authority/path/id

content:
The scheme portion of the URI. This is always set to  ContentResolver.SCHEME_CONTENT (value content://).  
authority
A string that identifies the entire content provider. All the content URIs for the provider start with this string.
To guarantee a unique authority, providers should consider using an authority that is the same as the provider class' package identifier.  
path
Zero or more segments, separated by a forward slash ( /), that identify some subset of the provider's data.  
Most providers use the path part to identify individual tables. Individual segments in the path are often called "directories"  
although they do not refer to file directories. The right-most segment in a path is often called a "twig"  
id
A unique numeric identifier for a single row in the subset of data identified by the preceding path part.  
Most providers recognize content URIs that contain an id part and give them special handling.
A table that contains a column named  _ID often expects the id part to be a particular value for that column.

       由上述咱们能够获得该URI分为四部分:

第一部分:“content://”是系统规定的;

第二部分:authority是一个标志整个内容提供者的字符串,也就是该内容提供者的标识符,至关于咱们每一个人都有的姓名;

第三部分:是内容提供者提供数据的某个子集,由于内容提供者有时会提供多个数据,咱们具体是要访问那个子集,须要把具体路径标出来;

第四部分:是一个标志数据子集中具体某一行数据的数字,通常咱们数据库都会有ID这一项,经过该字段可直接定位到表中的某一行。若是不知道或用不到该项可不填。

       Android里面提供了两个类对URI进行操做,一个是UriMatcher,该类专用于在ContentProvider中创建匹配器,从安卓的API文档咱们得知该匹配器用于在ContentProvider类的最开始创建匹配器并添加匹配规则,在后面的方法覆盖过程,须要用到该类的match方法进行匹配,返回匹配到的标志位。

void addURI(String authority, String path, int code)   //  三个参数,第一个是受权信息,第二个是路径,第三个就是当匹配该规则是返回的标志位
Add a URI to match, and the code to return when this URI is matched.

       另一个是ContentUri类,该类仅有的三个方法都是静态类,它是一个提供对URI和ID进行操做的工具类。经常使用的主要是parseId:用于取得URI中的Id,其实内部实现方法就是取得Uri PATH部分后面的值;withAppendedId用于把ID拼接到一个Uri的后面。

Public Methods
static Uri.Builder appendId(Uri.Builder builder, long id)
Appends the given ID to the end of the path.
                   
static long parseId(Uri contentUri)
Converts the last path segment to a long.
                   
static Uri withAppendedId(Uri contentUri, long id)
Appends the given ID to the end of the path.
                   

 

    2.3.2 ContentProvider的使用方法:

       了解了什么是URI就能够自定义一个ContentProvider,对外提供访问咱们数据的接口了。下面以ContentProvider最常封装的数据类型——数据库为例子进行说明。

       整个自定义ContentProvider的过程分为两大步,第一步固然是创建数据源,第二部则是创建访问数据源的内容提供者,这里以SQL数据库为例:

第一步:首先咱们须要先建一个SQLiteOpenHelper的子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
 
public class StuDbHelper  extends SQLiteOpenHelper {
 
private static String DbName =  "student.db" ;
 
public StuDbHelper(Context context, int version) {
super (context, DbName,  null , version);
}
 
@Override
public void onCreate(SQLiteDatabase db) {
String sql =  "create table student (id integer primary key,name varchar(20),age integer)" //在数据库中建立一张表
db.execSQL(sql);
}
 
@Override
public void onUpgrade(SQLiteDatabase db,  int oldVersion,  int newVersion) {
 
}
}

第二步:有了数据源,咱们接下来就得创建访问数据源的ContentProvider,创建ContentProvider的步骤一般分为:创建匹配器并添加匹配规则,重写各类操做数据的方法。添加匹配规则必需在全部方法和构造函数的执行前执行,由于有时候咱们须要在匹配成功后才创建数据库的SQLiteOpenHelper。因此这里必须使用静态代码块添加匹配规则。而重写访问数据的方法主要有增删改查四种,重写的步骤基本遵循: 对Uri进行匹配 --> 获取读或写数据库对象  -->  根据匹配结果利用switch语句判断该执行哪一种操做  -->  返回结果。一下是ContentProvider的源码示例:

 

1
2
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package cth.android.contentprovider;
 
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
 
public class StuDbCP  extends ContentProvider {
 
private static final UriMatcher URI_MATCHER =  new UriMatcher(UriMatcher.NO_MATCH);   //建立该内容提供者的匹配器
private static final String AUTHORITY =  "cth.android.contentprovider.StuDbCP" ;    //定义受权信息,用于获取该内容提供者的标识
private static final String PATH =  "student" ;                        //路径,表示访问内容提供者的具体路径,通常用代表表示访问该数据库的具体哪张表
private static final int STU =  1 ;     //设定标志位,STU表示单条信息
private static final int STUS =  2 ;    //STUS表示多条信息
static {
URI_MATCHER.addURI(AUTHORITY, PATH +  "/#" , STU);    //给匹配器加入匹配规则
URI_MATCHER.addURI(AUTHORITY, PATH, STUS);
}
 
private StuDbHelper stuDbHepler =  null ;
private SQLiteDatabase stuDb =  null ;
@Override
public boolean onCreate() {
boolean flag =  false ;
stuDbHepler =  new StuDbHelper(getContext(), 1 );   //建立SQLiteOpenHelper对象,版本为1
if (stuDb !=  null ) flag =  true ;
return flag;
}
 
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
int flag = URI_MATCHER.match(uri);     //匹配传入的Uri
Cursor selectResult =  null ;
SQLiteDatabase db = stuDbHepler.getReadableDatabase();
String whereClause =  null ;
switch (flag) {
case STU:
whereClause =  "id = " + ContentUris.parseId(uri);   //若是匹配第一种方式表示后面跟了id,因此要先提取id
if (selection !=  null && selection.equals( "" )) {
whereClause +=  " and " + selection;    //把id加到where条件子句(下面几种方法此步骤做用同理)
}
selectResult = db.query( "student" , projection, whereClause, selectionArgs,  null null null );
break ;
case STUS:
selectResult = db.query( "student" , projection,selection,selectionArgs,  null null null );
}
 
return selectResult;
}
 
/*返回uri的路径扩展部分的信息,通常单个条目返回字段为vnd.android.cursor.item/对应的PATH 而多个条目的以vnd.android.cursor.dir/对应的PATH。*/
@Override
public String getType(Uri uri) {  
 
int flag = URI_MATCHER.match(uri);
switch (flag) {
case STU:
return "vnd.android.cursor.item/student" ;
 
case STUS:
return "vnd.android.cursor.dir/students" ;
}
return null ;
}
 
@Override
public Uri insert(Uri uri, ContentValues values) {
int flag = URI_MATCHER.match(uri);
Uri resultUri =  null ;
switch (flag) {
case STUS :
stuDb = stuDbHepler.getWritableDatabase();    //获取写数据库
long id = stuDb.insert(PATH,  null , values);    //插入数据
resultUri = ContentUris.withAppendedId(uri, id);   //创建插入的数据的URI
break ;
}
 
return resultUri;
}
 
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int flag = URI_MATCHER.match(uri);
String whereClause =  null ;
int rowCount = - 1 ;
SQLiteDatabase db = stuDbHepler.getWritableDatabase();
switch (flag) {
case STU:
whereClause =  "id = " + ContentUris.parseId(uri);
if (selection !=  null && selection.equals( "" )) {
whereClause +=  " and " + selection ;
}
rowCount = db.delete( "student" , whereClause, selectionArgs);
break ;
 
case STUS:
rowCount = db.delete( "student" , selection, selectionArgs);
 
 
default break ;
}
return rowCount;
}
 
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int flag = URI_MATCHER.match(uri);
int count = - 1 ;
SQLiteDatabase db = stuDbHepler.getWritableDatabase();
String whereClause =  null ;
switch (flag) {
case STU:
whereClause =  "id = " + ContentUris.parseId(uri);
if (selection !=  null && selection.equals( "" )) {
whereClause +=  " and " + selection ;
}
count = db.update( "student" , values, whereClause, selectionArgs);
break ;
case STUS:
count = db.update( "student" , values, selection, selectionArgs);
break ;
}
return count;
}
 
}

 

       最后,ContentProvider也是安卓的四大组件之一,因此咱们要在Manifest文件的application标签中对其进行注册。这样,一个内容提供者就创建好了。咱们能够另外创建一个测试工程,对其进行跨进程访问,不过须要注意的是咱们对其进行访问前必须肯定该ContentProvider是存在的,因此必须先运行ContentProvider的工程,而后就可使用测试工程对其进行访问了。这里须要注意的是,当咱们把ContentProvider进程关闭后,咱们仍是能够对其数据进行访问的。一下是测试工程的代码:

 

 

1
2
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.test.AndroidTestCase;
import android.util.Log;
 
/*
* 新建一个工程,创建一个测试类来测试是否可以跨进程访问数据。分别实现增删改查四种方法。
* */
 
public class TestCP  extends AndroidTestCase {
 
public void insertData() {
ContentResolver cr = getContext().getContentResolver();
ContentValues values =  new ContentValues();
values.put( "id" 3 );
values.put( "name" "Jiky" );
values.put( "age" 18 );
Uri resultUri = cr.insert(uri, values);
if (resultUri !=  null ) {
Log.i( "cth" ,resultUri.toString());
else {
Log.e( "cth" , "插入失败" );
}
}
 
 
public void deleteData() {
ContentResolver cr = getContext().getContentResolver();
String where =  "id = ?" ;
int deleteRowNum = cr.delete(uri, where,  new String[] { "123" });
Log.i( "cth" , "deleteRowNum is " + deleteRowNum );
}
 
public void updateData() {
ContentResolver cr = getContext().getContentResolver();
Uri uri = Uri.parse( "content://cth.android.contentprovider.StuDbCP/student/" ); //直接把要修改的id加在PATH后,也可按一下方式。
ContentValues values =  new ContentValues();
values.put( "name" , "Mike" );
values.put( "age" 11 );
int rowId = cr.update(uri, values,  "id = ?" new String[]{ "1" });
if (rowId ==  0 ) {
Log.e( "cth" , "找不到匹配项。" );
else {
Log.i( "cth" , "rowId = " + rowId);
}
 
}
 
public void selectData() {
ContentResolver cr = getContext().getContentResolver();
 
Cursor cursor = cr.query(uri, new String[]{ "name" , "id" },  null null null );
 
while (cursor.moveToNext()) {
Log.i( "cth" ,cursor.getString(cursor.getColumnIndex( "id" )) +  " count = " + cursor.getCount());
}
 
}
}
相关文章
相关标签/搜索