您的位置:首页 > 移动开发 > Android开发

Android ContentProvider 完全解析及简单DEMO

2016-12-05 06:19 337 查看

Android应用程序运行在不同的进程空间中,因此不同应用程序的数据是不能够直接访问的。为了增强程序之间的数据共享能力,Android系统提供了像SharedPreferences这类简单的跨越程序边界的访问方法,但这些方法都存在一定的局限性。

ContentProvider(数据提供者)是应用程序之间共享数据的一种接口机制,是一种更为高级的数据共享方法。

ContentProvider可以指定需要共享的数据,而其他应用程序则可以在不知道数据来源、路径的情况下,对共享数据进行增删改查等操作。
在Android系统中,许多Android系统内置的数据也是通过ContentProvider提供给用户使用,例如通讯录、音视频文件和图像文件等。

ContentProvider机理

-

调用关系



在创建ContentProvider前,首先要实现底层的数据源,数据源包括数据库、文件系统或网络等,然后继承ContentProvider类中实现基本数据操作的接口函数。调用者不能直接调用ContentProvider的接口函数,需要通过ContentResolver对象,通过URI间接调用ContentProvider。

-

ContentResolver对象与ContentProvider的交互

在ContentResolver对象与ContentProvider进行交互时,通过URI确定要访问的ContentProvider数据集。在发起的一个请求的过程中,Android系统根据URI确定处理这个查询的ContentProvider,然后初始化ContentProvider所有需要的资源,这个初始化的工作是Android系统完成的,无需我们参与。一般情况下只有一个ContentProvider对象,但却可以同时与多个ContentResolver进行交互。

-

ContentProvider的屏蔽性

ContentProvider完全屏蔽了底层数据源的数据存储方法。数据提供者通过ContentProvider提供了一组标准的数据操作接口,但却无须知道数据提供者的内部数据的存储方法。数据提供者可以使用SQLite数据库存储数据,也可以通过文件系统或SharedPreferences存储数据,甚至是使用网络存储的方法,这些数据的存储方法和存储设备对数据使用者都是不可见的。同时,也正是这种屏蔽模式,很大程度上简化了ContentProvider的使用方法,使用者只要调用ContentProvider提供的接口函数,即可完成所有的数据操作,而数据存储方法则是ContentProvider设计者需要考虑的问题。

- ContentProvider提供的数据形式

ContentProvider的数据集类似于数据库的数据表,每行是一条记录,每列具有相同的数据类型。每条记录都包含一个长整型的字段 _ID,用来唯一标识每条记录。ContentProvider可以提供多个数据集,调用者使用URI对不同数据集的数据进行操作。

-

通用资源标识符(Uniform Resource Identifier)

URI是一个用于标识某一互联网资源名称的字符串。 该种标识允许用户对任何(包括本地和互联网)的资源通过特定的协议进行交互操作。在ContentProvider机制中,使用ContentResolver对象通过URI定位ContentProvider提供的资源。

ContentProvider使用的URI语法结构如下:

content://<authority>/<data_path>/<id>
1


1
content:// 是通用前缀,表示该UIR用于ContentProvider定位资源。
< authority > 是授权者名称,用来确定具体由哪一个ContentProvider提供资源。因此一般< authority >都由类的小写全称组成,以保证唯一性
4000

< data_path > 是数据路径,用来确定请求的是哪个数据集。如果ContentProvider近提供一个数据集,数据路径则可以省略;如果ContentProvider提供多个数据集,数据路径必须指明具体数据集。数据集的数据路径可以写成多段格式,例如people/girl和people/boy。
< id > 是数据编号,用来唯一确定数据集中的一条记录,匹配数据集中_ID字段的值。如果请求的数据不只一条,< id >可以省略。
如请求整个people数据集的URI为:

content://com.example.peopleprovider/people
1


1
而请求people数据集中第3条数据的URI则应写为:

content://com.example.peopleprovider/people/3
1


1

创建数据提供者

1. 创建一个类让其继承ContentProvider,并重载6个函数

onCreate()

一般用来初始化底层数据集和建立数据连接等工作

getType()

用来返回指定URI的MIME数据类型,若URI是单条数据,则返回的MIME数据类型以vnd.android.cursor.item开头;若URI是多条数据,则返回的MIME数据类型以vnd.android.cursor.dir/开头。

insert()、delete()、update()、query()

用于对数据集的增删改查操作。

2. 声明CONTENT_URI,实现UriMatcher

示例:

public static final String AUTHORITY = "com.example.peopleprovider";
public static final String PATH_SINGLE = "people/#";
public static final String PATH_MULTIPLE = "people";
public static final String CONTENT_URI_STRING = "content://" + AUTHORITY + "/" + PATH_MULTIPLE;
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING);
public static final int MULTIPLE_PEOPLE = 1;
public static final int SINGLE_PEOPLE = 2;
public static final UriMatcher uriMatcher;
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, PATH_SINGLE, SINGLE_PEOPLE );
uriMatcher.addURI(AUTHORITY, PATH_MULTIPLE , MULTIPLE_PEOPLE );
}
1
2
3
4
5
6
7
8
9
10
11
12
13


1
2
3
4
5
6
7
8
9
10
11
12
13
其中UriMatcher类引用官方文档中的解释:

Utility class to aid in matching URIs in content providers.

可见UriMatcher本质上是一个文本过滤器,用在contentProvider中帮助我们过滤,分辨出查询者想要查询哪个数据表。

UriMatcher的构造函数中,UriMatcher.NO_MATCH是URI无匹配时的返回代码,值为-1。 addURI() 方法用来添加新的匹配项,语法为:

public void addURI(String authority, String path, int code)
1


1
其中authority表示匹配的授权者名称,path表示数据路径(#代表任何数字),code表示返回代码。

关于UriMatcher的使用

switch(uriMatcher.match(uri)){
case MULTIPLE_PEOPLE:
//多条数据的处理
break;
case SINGLE_PEOPLE:
//单条数据的处理
break;
default:
throw new IllegalArgumentException("不支持的URI:" + uri);
}
1
2
3
4
5
6
7
8
9
10


1
2
3
4
5
6
7
8
9
10

3. 注册ContentProvider

在AndroidManifest.xml文件中的 application节点下使用< provider >标签注册。示例:

<provider
android:authorities="com.example.peopleprovider"
android:name=".Peopleprovider" />
1
2
3


1
2
3
上例中注册了一个授权者名称为com.example.peopleprovider的ContentProvider,其实现类为 Peopleprovider 。

使用数据提供者

每个Android组件都有一个ContentResolver对象,通过调用getContentResolver() 方法可得到ContentResolver对象。

准备工作:

ContentResolver resolver = getContentResolver();
String KEY_ID = "_id";
String KEY_NAME = "name";
String KEY_AGE = "age";
String KEY_HEIGHT = "height";

1. 添加操作

通过insert()函数添加单条数据

(returns : the URL of the newly created row.)

ContentValues values = new ContentValues();
values.put(KEY_NAME, "Tom");
values.put(KEY_AGE, 21);
values.put(KEY_HEIGHT, 1.81f);
Uri newUri = resolver.insert(CONTENT_URI, values);
1
2
3
4
5


1
2
3
4
5
通过bulkInsert()函数添加多条数据

(returns:the number of newly created rows.)

ContentValues[] arrayValues = new ContentValues[10];
//实例化每一个ContentValues...
int count = resolver.bulkInsert(CONTENT_URI, arrayValues );
1
2
3


1
2
3

2. 删除操作

指定ID删除单条数据

Uri uri = Uri.parse(CONTENT_URI_STRING + "/" +"2");
int result = resolver.delete(uri, null, null);
1
2


1
2
通过selection语句删除多条数据

String selection = KEY_ID + ">4";
int result = resolver.delete(CONTENT_URI, selection, null);
1
2


1
2

3. 更新操作

ContentValues values = new ContentValues();
values.put(KEY_NAME, "Tom");
values.put(KEY_AGE, 21);
values.put(KEY_HEIGHT, 1.81f);
Uri rui = Uri.parse(CONTENT_URI_STRING + "/" + "7");
int result = resolver.update(uri, values, null, null);
1
2
3
4
5
6


1
2
3
4
5
6

4. 查询操作

Uri uri = Uri.parse(CONTENT_URI_STRING + "/" + "2");
Cursor cursor = resolver.query(uri, new String[]{KEY_ID, KEY_NAME, KEY_AGE, KEY_HEIGHT}, null, null, null);
1
2


1
2
在URI中定义了需要查询数据的ID后,在query()函数中没有必要再加入其他的查询条件,如果要获取数据集全部数据,则可以直接使用CONTENT_URI且不加查询条件。

在Android系统中,数据库查询结果的返回值并不是数据集合的完整拷贝,而是返回数据集的指针,这个指针就是Cursor类。ContentProvider的数据集类似数据库的数据表,其查询结果的返回值同样是数据集的指针:Cursor类。在提取Cursor数据中的数据前,推荐测试Cursor中的数据数量,避免在数据获取中产生异常。示例如下:

public people[] getPeople(Cursor cursor){
int resultCounts = cursor.getCount();
if(resultCounts == 0 !cursor.moveToFirst()){
return null;
}
People[] peoples = new People[resultCounts];
for(int i=0; i<resultCounts; i++){
peoples[i] = new People();
peoples[i].ID = cursor.getInt(0);
peoples[i].Name = cursor.getString(cursor.getColumnIndex(KEY_NAME));
peoples[i].Age = cursor.getInt(cursor.getColumnIndex(KEY_AGE));
peoples[i].Height= cursor.getFloat(cursor.getColumnIndex(KEY_HEIGHT));
cursor.moveToNext();
}
return peoples;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

ContentProvider Demo

Demo结构如下:





People.Java

public class People {
public static final String MIME_DIR_PREFIX = "vnd.android.cursor.dir";
public static final String MIME_ITEM_PREFIX = "vnd.android.cursor.item";
public static final String MIME_ITEM = "vnd.example.people";

public static final String MIME_TYPE_SINGLE = MIME_ITEM_PREFIX + "/" + MIME_ITEM ;
public static final String MIME_TYPE_MULTIPLE = MIME_DIR_PREFIX + "/" + MIME_ITEM ;

public static final String AUTHORITY = "com.example.peopleprovider";
public static final String PATH_SINGLE = "people/#";
public static final String PATH_MULTIPLE = "people";
public static final String CONTENT_URI_STRING = "content://" + AUTHORITY + "/" + PATH_MULTIPLE;
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING);

public static final String KEY_ID = "_id";
public static final String KEY_NAME = "name";
public static final String KEY_AGE = "age";
public static final String KEY_HEIGHT = "height";

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PeopleProvider.java

package com.example.contentproviderdemo;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.support.annotation.Nullable;

/**
* Created by yinghao on 2016/5/3.
*/
public class PeopleProvider extends ContentProvider {

private static final String DB_NAME="people.db";
private static final String DB_TABLE="peopleinfo";
private static final int DB_VERSION = 1;

private SQLiteDatabase db;
private DBOpenHelper dbOpenHelper;

private static final int MULTIPLE_PEOPLE = 1;
private static final int SINGLE_PEOPLE = 2;
private static final UriMatcher uriMatcher ;

static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(People.AUTHORITY, People.PATH_MULTIPLE, MULTIPLE_PEOPLE);
uriMatcher.addURI(People.AUTHORITY, People.PATH_SINGLE, SINGLE_PEOPLE);
}

@Override
public boolean onCreate() {
Context context = getContext();
dbOpenHelper = new DBOpenHelper(context, DB_NAME, null, DB_VERSION);
db = dbOpenHelper.getWritableDatabase();
if(db == null){
return false;
}else{
return true;
}
}

@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(DB_TABLE);
switch (uriMatcher.match(uri)){
case SINGLE_PEOPLE:
qb.appendWhere(People.KEY_ID+"="+uri.getPathSegments().get(1));
break;
default:
break;
}
Cursor cursor = qb.query(db,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}

@Nullable
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)){
case MULTIPLE_PEOPLE:
return People.MIME_TYPE_MULTIPLE;
case SINGLE_PEOPLE:
return People.MIME_TYPE_SINGLE;
default:
throw new IllegalArgumentException("Unkown uro:"+uri);
}
}

@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
long id =db.insert(DB_TABLE, null, values);
if(id>0){
Uri newUri = ContentUris.withAppendedId(People.CONTENT_URI,id);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
throw new SQLException("failed to insert row into " + uri);
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
switch (uriMatcher.match(uri)){
case MULTIPLE_PEOPLE:
count = db.delete(DB_TABLE, selection, selectionArgs);
break;
case SINGLE_PEOPLE:
String segment = uri.getPathSegments().get(1);
count = db.delete(DB_TABLE, People.KEY_ID + "=" + segment, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unsupported URI:" + uri);
}
getContext().getContentResolver().notifyChange(uri,null);
return count;
}

@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count;
switch (uriMatcher.match(uri)){
case MULTIPLE_PEOPLE:
count = db.update(DB_TABLE, values, selection, selectionArgs);
break;
case SINGLE_PEOPLE:
String segment = uri.getPathSegments().get(1);
count = db.update(DB_TABLE, values, People.KEY_ID + "=" + segment, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknow URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}

private static class DBOpenHelper extends SQLiteOpenHelper {

private static final  String DB_CREATE = "create table "+
DB_TABLE+"("+People.KEY_ID+" integer primary key autoincrement, "+
People.KEY_NAME+" text not null, "+People.KEY_AGE+" integer, "+
People.KEY_HEIGHT+" float);";

public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}

@Override
public void onCreate(SQLiteDatabase db) {

db.execSQL(DB_CREATE);

}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
onCreate(db);

}
}

}
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162


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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
MainActivity.java

package com.example.contentresolverdemo;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private EditText nameText;
private EditText ageText;
private EditText heightText;
private EditText idEntry;
private TextView labelView;
private TextView displayView;
private Button add;
private Button queryAll;
private Button clear;
private Button del;
private Button query;
private Button deleteAll;
private Button update;
private ContentResolver resolver;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
resolver = this.getContentResolver();
initView();
initEvent();
}

private void initEvent() {

add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContentValues values = new ContentValues();
values.put(People.KEY_NAME, nameText.getText().toString());
values.put(People.KEY_AGE, Integer.parseInt(ageText.getText().toString()));
values.put(People.KEY_HEIGHT, Float.parseFloat(heightText.getText().toString()));
Uri newUri = resolver.insert(People.CONTENT_URI, values);
labelView.setText("添加成功,URI:" + newUri);
}
});

queryAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Cursor cursor = resolver.query(People.CONTENT_URI,
new String[]{People.KEY_ID, People.KEY_NAME, People.KEY_AGE, People.KEY_HEIGHT},
null, null, null);
if (cursor == null) {
labelView.setText("数据库中没有数据");
return;
}
labelView.setText("数据库:" + String.valueOf(cursor.getCount()) + "条记录");
String msg= "";
if (cursor.moveToFirst()) {
do {
msg += "ID: " + cursor.getString(cursor.getColumnIndex(People.KEY_ID)) + ",";
msg += "姓名: " + cursor.getString(cursor.getColumnIndex(People.KEY_NAME)) + ",";
msg += "年龄: " + cursor.getInt(cursor.getColumnIndex(People.KEY_AGE)) + ",";
msg += "身高: " + cursor.getFloat(cursor.getColumnIndex(People.KEY_HEIGHT)) + ",";
} while (cursor.moveToNext());
}
displayView.setText(msg);
}
});

deleteAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
resolver.delete(People.CONTENT_URI, null, null);
String msg = "数据全部删除";
labelView.setText(msg);
}
});

update.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContentValues values = new ContentValues();
values.put(People.KEY_NAME, nameText.getText().toString());
values.put(People.KEY_AGE, Integer.parseInt(ageText.getText().toString()));
values.put(People.KEY_HEIGHT, Float.parseFloat(heightText.getText().toString()));
Uri uri = Uri.parse(People.CONTENT_URI_STRING + "/" + idEntry.getText().toString());
int result = resolver.update(uri, values, null, null);
String msg = "更新ID为" + idEntry.getText().toString() + "的数据" + (result > 0 ? "成功" : "失败");
labelView.setText(msg);
}
});
}

private void initView() {
}
}
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


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
ContentProviderDemo的AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.contentproviderdemo">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<provider
android:authorities="com.example.peopleprovider"
android:name=".PeopleProvider"/>
</application>

</manifest>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ContentResolverDemo的AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.contentresolverdemo">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: