解读ContentResolver和ContentProvider

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

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的:

使用方法归纳:

1、从当前Activity获取系统的ContentResolver;

2、使用ContentProvider的insert、delete、update、query方法对ContentProvider的内容进行增删改查;

3、如果是使用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

packagecth.android.contentprovide;

importandroid.annotation.SuppressLint;

importandroid.app.Activity;

importandroid.content.ContentResolver;

importandroid.database.Cursor;

importandroid.os.Bundle;

importandroid.provider.ContactsContract;

importandroid.util.Log;

importandroid.widget.ListAdapter;

importandroid.widget.ListView;

importandroid.widget.SimpleCursorAdapter;

/**

* @author CTH

*  

*该类是演示利用ContentProvider,获取手机的联系人信息。

*使用ContentProvide的步骤:

*1、从当前Activity获取系统的ContentResolver;

*2、使用ContentProvider的insert、delete、update、query方法对ContentProvider的内容进行增删改查;

*3、如果是使用query是的到一个Cursor的结果集,通过该结果集可以获得我们查询的结果。

*

*/

publicclassMainActivity extendsActivity {

privateListView contactsList;

@SuppressLint("InlinedApi")

protectedvoidonCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

//setContentView(R.layout.activity_main);  //不使用inflate XML文件方法,而是使用动态生成控件。

contactsList = newListView(MainActivity.this);  

ContentResolver cr = getContentResolver();  //获取ContentResolver

Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null,

nullnullnull);   //查询系统的联系人信息

Log.i("cth", cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME) + "");

@SuppressWarnings("deprecation")

ListAdapter la = newSimpleCursorAdapter(MainActivity.this,

android.R.layout.simple_list_item_1, cursor,

newString[] {ContactsContract.Contacts.DISPLAY_NAME_PRIMARY },

newint[] { 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,

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

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

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

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

多媒体的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  手机内部存储器上的视频

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

短信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 (valuecontent://).  
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

importandroid.content.Context;

importandroid.database.sqlite.SQLiteDatabase;

importandroid.database.sqlite.SQLiteOpenHelper;

publicclassStuDbHelper extendsSQLiteOpenHelper {

privatestaticString DbName = "student.db";

publicStuDbHelper(Context context,intversion) {

super(context, DbName, null, version);

}

@Override

publicvoidonCreate(SQLiteDatabase db) {

String sql = "create table student (id integer primary key,name varchar(20),age integer)"//在数据库中创建一张表

db.execSQL(sql);

}

@Override

publicvoidonUpgrade(SQLiteDatabase db, intoldVersion, intnewVersion) {

}

}

第二步:有了数据源,我们接下来就得建立访问数据源的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

packagecth.android.contentprovider;

importandroid.content.ContentProvider;

importandroid.content.ContentUris;

importandroid.content.ContentValues;

importandroid.content.UriMatcher;

importandroid.database.Cursor;

importandroid.database.sqlite.SQLiteDatabase;

importandroid.net.Uri;

publicclassStuDbCP extendsContentProvider {

privatestaticfinalUriMatcher URI_MATCHER = newUriMatcher(UriMatcher.NO_MATCH);  //创建该内容提供者的匹配器

privatestaticfinalString AUTHORITY = "cth.android.contentprovider.StuDbCP";   //定义授权信息,用于获取该内容提供者的标识

privatestaticfinalString PATH = "student";                       //路径,表示访问内容提供者的具体路径,一般用表明表示访问该数据库的具体哪张表

privatestaticfinalintSTU = 1;    //设定标志位,STU表示单条信息

privatestaticfinalintSTUS = 2;   //STUS表示多条信息

static{

URI_MATCHER.addURI(AUTHORITY, PATH + "/#", STU);   //给匹配器加入匹配规则

URI_MATCHER.addURI(AUTHORITY, PATH, STUS);

}

privateStuDbHelper stuDbHepler = null;

privateSQLiteDatabase stuDb = null;

@Override

publicbooleanonCreate() {

booleanflag = false;

stuDbHepler = newStuDbHelper(getContext(),1);  //创建SQLiteOpenHelper对象,版本为1

if(stuDb != null) flag = true;

returnflag;

}

@Override

publicCursor query(Uri uri, String[] projection, String selection,

String[] selectionArgs, String sortOrder) {

intflag = URI_MATCHER.match(uri);    //匹配传入的Uri

Cursor selectResult = null;

SQLiteDatabase db = stuDbHepler.getReadableDatabase();

String whereClause = null;

switch(flag) {

caseSTU:

whereClause = "id = "+ ContentUris.parseId(uri);  //如果匹配第一种方式表示后面跟了id,所以要先提取id

if(selection != null&& selection.equals("")) {

whereClause += " and "+ selection;   //把id加到where条件子句(下面几种方法此步骤作用同理)

}

selectResult = db.query("student", projection, whereClause, selectionArgs, nullnullnull);

break;

caseSTUS:

selectResult = db.query("student", projection,selection,selectionArgs, nullnullnull);

}

returnselectResult;

}

/*返回uri的路径扩展部分的信息,一般单个条目返回字段为vnd.android.cursor.item/对应的PATH 而多个条目的以vnd.android.cursor.dir/对应的PATH。*/

@Override

publicString getType(Uri uri) {  

intflag = URI_MATCHER.match(uri);

switch(flag) {

caseSTU:

return"vnd.android.cursor.item/student";

caseSTUS:

return"vnd.android.cursor.dir/students";

}

returnnull;

}

@Override

publicUri insert(Uri uri, ContentValues values) {

intflag = URI_MATCHER.match(uri);

Uri resultUri = null;

switch(flag) {

caseSTUS :

stuDb = stuDbHepler.getWritableDatabase();   //获取写数据库

longid = stuDb.insert(PATH, null, values);   //插入数据

resultUri = ContentUris.withAppendedId(uri, id);  //建立插入的数据的URI

break;

}

returnresultUri;

}

@Override

publicintdelete(Uri uri, String selection, String[] selectionArgs) {

intflag = URI_MATCHER.match(uri);

String whereClause = null;

introwCount = -1;

SQLiteDatabase db = stuDbHepler.getWritableDatabase();

switch(flag) {

caseSTU:

whereClause = "id = "+ ContentUris.parseId(uri);

if(selection != null&& selection.equals("")) {

whereClause += " and "+ selection ;

}

rowCount = db.delete("student", whereClause, selectionArgs);

break;

caseSTUS:

rowCount = db.delete("student", selection, selectionArgs);

defaultbreak;

}

returnrowCount;

}

@Override

publicintupdate(Uri uri, ContentValues values, String selection,

String[] selectionArgs) {

intflag = URI_MATCHER.match(uri);

intcount = -1;

SQLiteDatabase db = stuDbHepler.getWritableDatabase();

String whereClause = null;

switch(flag) {

caseSTU:

whereClause = "id = "+ ContentUris.parseId(uri);

if(selection != null&& selection.equals("")) {

whereClause += " and "+ selection ;

}

count = db.update("student", values, whereClause, selectionArgs);

break;

caseSTUS:

count = db.update("student", values, selection, selectionArgs);

break;

}

returncount;

}

}

最后,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

importandroid.content.ContentResolver;

importandroid.content.ContentValues;

importandroid.database.Cursor;

importandroid.net.Uri;

importandroid.test.AndroidTestCase;

importandroid.util.Log;

/*

* 新建一个工程,建立一个测试类来测试是否能够跨进程访问数据。分别实现增删改查四种方法。

* */

publicclassTestCP extendsAndroidTestCase {

publicvoidinsertData() {

ContentResolver cr = getContext().getContentResolver();

Uri uri = Uri.parse("content://cth.android.contentprovider.StuDbCP/student");

ContentValues values = newContentValues();

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","插入失败");

}

}

publicvoiddeleteData() {

ContentResolver cr = getContext().getContentResolver();

Uri uri = Uri.parse("content://cth.android.contentprovider.StuDbCP/student");

String where = "id = ?";

intdeleteRowNum = cr.delete(uri, where, newString[] {"123"});

Log.i("cth","deleteRowNum is "+ deleteRowNum );

}

publicvoidupdateData() {

ContentResolver cr = getContext().getContentResolver();

Uri uri = Uri.parse("content://cth.android.contentprovider.StuDbCP/student/"); //直接把要修改的id加在PATH后,也可按一下方式。

ContentValues values = newContentValues();

values.put("name","Mike");

values.put("age"11);

introwId = cr.update(uri, values, "id = ?"newString[]{"1"});

if(rowId == 0) {

Log.e("cth","找不到匹配项。");

else{

Log.i("cth","rowId = "+ rowId);

}

}

publicvoidselectData() {

ContentResolver cr = getContext().getContentResolver();

Uri uri = Uri.parse("content://cth.android.contentprovider.StuDbCP/student/");

Cursor cursor = cr.query(uri,newString[]{"name","id"}, nullnullnull);

while(cursor.moveToNext()) {

Log.i("cth",cursor.getString(cursor.getColumnIndex("id")) + " count = "+ cursor.getCount());

}

}

}

 

时间: 2024-11-05 13:30:31

解读ContentResolver和ContentProvider的相关文章

解读Android之ContentProvider(1)CRUD操作

本文翻译自android官方文档,结合自己测试,整理如下. Content providers能够管理结构化的数据集,封装数据,并且能够提供数据安全的机制.Content providers是一种标准的接口,能够跨进程数据共享.中文可以被称为内容提供器. 当我们想从content providers中获取数据时,我们可以使用ContentResolver对象最为客户端访问该providers.ContentResolver对象能够和Content Provider实例进行通信,该实例是继承抽象类

王立平--ContentResolver与ContentProvider

<span style="font-size:18px;"> </span> 应用程序之间唯一共享数据的方法 ContentProvider:负责组织数据,并把数据暴露出去 暴露数据就像是暴露了一个数据库,外界访问其中的数据类似于访问数据库表的数据. 只是用URI来表示访问外界的"数据库",有android底层实现URI与哪个数据库对应.我们只需要知道访问特定数据的URI即可. ContentProvider向外界提供数据操作的接口: que

解读Android之ContentProvider(2)创建自己的Provider

本文翻译自android官方文档,结合自己测试,整理如下. content provider管理数据的访问,我们可以在自己的应用程序中实现一个或多个自定义的provider(通过继承抽象类ContentProvider),当然这些provider需要在manifest文件中注册.尽管content provider是用来为其它程序来访问数据的,但是在自己程序中的activities显然可以对这些数据进行处理. 创建provider之前注意事项 确定是否需要提供content provider.若

ContentProvider与ContentResolver使用

例如以下内容为从网络转载: 使用ContentProvider共享数据: 当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就能够向其它应用共享其数据.虽然使用其它方法也能够对外共享数据,但数据訪问方式会因数据存储的方式而不同,如:採用文件方式对外共享数据,须要进行文件操作读写数据:採用sharedpreferences共享数据,须要使用sharedpreferences API读写数据.而使用ContentProvider共享数据的优点是统一了数据訪问方式.

Android 之 ContentProvider 与 ContentResolver

在Android官方指出的Android的数据存储方式总共有五种,分别是:Shared Preferences.网络存储.文件存储.外储存储.SQLite. 但是我们知道一般这些存储都只是在单独的一个应用程序之中达到一个数据的共享,有时候我们需要操作其他应用程序的一些数据, 例如我们需要操作系统里的媒体库.通讯录等,这时我们就可能通过ContentProvider来满足我们的需求了. 采用文件方式对外共享数据,需要进行文件操作读写数据: 采用sharedpreferences共享数据,需要使用s

Android开发实践 ContentProvider和ContentResolver

1.关于ContentProvider和ContentResolver (1)ContentProvider(内容提供者) ContentProvider是不同应用程序之间进行数据交换的标准API,只提供数据的访问接口. ContentProvider以某种Uri形式对外提供数据,允许其他应用访问或修改数据,其他应用程序通过ContentResolver根据Uri去访问操作指定数据. 将一个字符串转换成Uri: Uri uri = Uri.parse("content://com.gc.cont

内容提供者ContentProvider和内容解析者ContentResolver

简介 ContentProvider 在android中的作用是对外共享数据,也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider 对你应用中的数据进行添删改查.关于数据共享,以前我们学习过文件操作模式,知道通过指定文件的操作模式为Context.MODE_WORLD_READABLE 或Context.MODE_WORLD_WRITEABLE同样也可以对外共享数据.那么,这里为何要使用ContentProvider 对

ContentProvider与ContentResolver

使用ContentProvider共享数据: 当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据.虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据:采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据.而使用ContentProvider共享数据的好处是统一了数据访问方式. 当应用需要通过Content

[Android] ContentProvider实例详解

1.ContentProvider是什么? ContentProvider(内容提供者)是Android的四大组件之一,管理android以结构化方式存放的数据,以相对安全的方式封装数据(表)并且提供简易的处理机制和统一的访问接口供其他程序调用. Android的数据存储方式总共有五种,分别是:Shared Preferences.网络存储.文件存储.外储存储.SQLite.但一般这些存储都只是在单独的一个应用程序之中达到一个数据的共享,有时候我们需要操作其他应用程序的一些数据,就会用到Cont