Android中ContentProvider组件详解
以下这段是Google Doc中对ContentProvider的大致概述。
内容提供者将一些特定的应用程序数据供给其它应用程序使用。数据可以存储于文件系统、SQLite数据库或其它方式。内容提供者继承于ContentProvider 基类,为其它应用程序取用和存储它管理的数据实现了一套标准方法。然而,应用程序并不直接调用这些方法,而是使用一个 ContentResolver 对象,调用它的方法作为替代。ContentResolver可以与任意内容提供者进行会话,与其合作来对所有相关交互通讯进行管理。
1.ContentProvider
Android提供了一些主要数据类型的ContentProvider,比如音频、视频、图片和私人通讯录等。可在android.provider包下面找到一些Android提供的ContentProvider。通过获得这些ContentProvider可以查询它们包含的数据,当然前提是已获得适当的读取权限。
主要方法:
public boolean onCreate() 在创建ContentProvider时调用
public Cursor query(Uri, String[], String, String[], String) 用于查询指定Uri的ContentProvider,返回一个Cursor
public Uri insert(Uri, ContentValues) 用于添加数据到指定Uri的ContentProvider中
public int update(Uri, ContentValues, String, String[]) 用于更新指定Uri的ContentProvider中的数据
public int delete(Uri, String, String[]) 用于从指定Uri的ContentProvider中删除数据
public String getType(Uri) 用于返回指定的Uri中的数据的MIME类型
*如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头。
例如:要得到所有person记录的Uri为content://contacts/person,那么返回的MIME类型字符串为"vnd.android.cursor.dir/person"。
*如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头。
例如:要得到id为10的person记录的Uri为content://contacts/person/10,那么返回的MIME类型字符串应为"vnd.android.cursor.item/person"。
2.ContentResolver
当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver类来完成,要获取ContentResolver对象,可以使用Context提供的getContentResolver()方法。
- ContentResolver cr = getContentResolver();
public Uri insert (Uri uri, ContentValues values) 用于添加数据到指定Uri的ContentProvider中。
public int delete (Uri uri, String selection, String[] selectionArgs) 用于从指定Uri的ContentProvider中删除数据。
public int update (Uri uri, ContentValues values, String selection, String[] selectionArgs) 用于更新指定Uri的ContentProvider中的数据。
public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 用于查询指定Uri的ContentProvider。
3.Uri
Uri指定了将要操作的ContentProvider,其实可以把一个Uri看作是一个网址,我们把Uri分为三部分。
第一部分是"content://"。可以看作是网址中的"http://"。
第二部分是主机名或authority,用于唯一标识这个ContentProvider,外部应用需要根据这个标识来找到它。可以看作是网址中的主机名,比如"blog.csdn.net"。
第三部分是路径名,用来表示将要操作的数据。可以看作网址中细分的内容路径。
下面是用ContentProvider读取联系人数据,属于系统数据。完整代码下载:android_contentprovider_system.rar
注意:这里的联系人操作有点乱,关键是我还不是很熟,SDK1.6和SDK2.1的联系人操作很有很大不同,希望哪位大侠指点一下。
- /**
- * MainActivity
- *
- * @author zuolongsnail
- */
- public class MainActivity extends Activity {
- private EditText nameET;
- private EditText numberET;
- private Button insertBtn;
- private Button deleteBtn;
- private Button queryBtn;
- private ListView contentView;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- nameET = (EditText) findViewById(R.id.name);
- numberET = (EditText) findViewById(R.id.number);
- insertBtn = (Button) findViewById(R.id.insert);
- deleteBtn = (Button) findViewById(R.id.delete);
- queryBtn = (Button) findViewById(R.id.query);
- // 用于显示数据
- contentView = (ListView) findViewById(R.id.content);
- insertBtn.setOnClickListener(new OperateOnClickListener());
- deleteBtn.setOnClickListener(new OperateOnClickListener());
- queryBtn.setOnClickListener(new OperateOnClickListener());
- }
- class OperateOnClickListener implements OnClickListener {
- @Override
- public void onClick(View v) {
- String name = nameET.getText().toString();
- String number = numberET.getText().toString();
- Person p = new Person(name, number);
- switch (v.getId()) {
- // 插入数据
- case R.id.insert:
- insert(p);
- view();
- break;
- // 删除数据
- case R.id.delete:
- delete(name);
- view();
- break;
- // 查询数据
- case R.id.query:
- view();
- break;
- }
- }
- }
- // 显示数据
- private void view() {
- Cursor c = query("");
- ListAdapter listAdapter = new SimpleCursorAdapter(this, R.layout.list,
- c, new String[] { People._ID, People.NAME, People.NUMBER },
- new int[] { R.id.id, R.id.name, R.id.number });
- contentView.setAdapter(listAdapter);
- }
- // 插入联系人
- private void insert(Person p) {
- // 获得ContentResolver对象
- ContentResolver cr = getContentResolver();
- ContentValues values = new ContentValues();
- values.put(People.NAME, p.name);
- // 表示是否把联系人添加到收藏(加星),1表示加入,0表示不加入,这行代码注释默认是不加入。
- values.put(Contacts.People.STARRED, 1);
- Uri uri = Contacts.People.createPersonInMyContactsGroup(cr, values);
- // 获得联系人People表的Uri
- Uri url = Uri.withAppendedPath(uri,
- Contacts.People.Phones.CONTENT_DIRECTORY);
- values.clear();
- values.put(Contacts.Phones.TYPE, Contacts.Phones.NUMBER);
- values.put(Contacts.Phones.NUMBER, p.number);
- // 插入操作
- cr.insert(url, values);
- }
- // 插入联系人
- private void delete(String name) {
- // 获得ContentResolver对象
- ContentResolver cr = getContentResolver();
- Uri url = Contacts.People.CONTENT_URI;
- // 设置删除条件
- String where = People.NAME + "=?";
- String[] selectionArgs = { name };
- cr.delete(url, where, selectionArgs);
- }
- // 查询联系人
- private Cursor query(String name) {
- // 获得ContentResolver对象
- ContentResolver cr = getContentResolver();
- Uri uri = Contacts.People.CONTENT_URI;
- // 查询对象
- String[] projection = { People._ID, People.NAME, People.NUMBER };
- // 设置查询条件,这里我把selection和selectionArgs参数都设为null,表示查询全部数据
- String selection = null;
- String[] selectionArgs = null;
- if (!"".equals(name)) {
- selection = People.NAME + "=?";
- selectionArgs = new String[] { name };
- }
- // 设置排序条件
- String sortOrder = Contacts.People._ID;
- Cursor c = cr.query(uri, projection, selection, selectionArgs,
- sortOrder);
- // if (c.moveToFirst()) {
- // for (int i = 0; i < c.getCount(); i++) {
- // c.moveToPosition(i);
- // String name = c.getString(c.getColumnIndexOrThrow(People.NAME));
- // String number = c.getString(c
- // .getColumnIndexOrThrow(People.NUMBER));
- // }
- // }
- return c;
- }
- }
程序截图:
android ContentProvider和Uri详解
一、使用ContentProvider(内容提供者)共享数据
ContentProvider是怎么实现数据共享的呢?
下面先来了解一下这几个东东:
(1) URI
URI:统一资源标识符,代表要操作的数据,可以用来标识每个ContentProvider,这样你就可以通过指定的URI找到想要的ContentProvider,从中获取或修改数据。在Android中URI的格式如下图所示(图片来自于网络):
主要分三个部分:scheme, authority and path。scheme表示上图中的content://,authority表示B部分,path表示C和D部分。
A部分:表示是一个Android内容URI,说明由ContentProvider控制数据,该部分是固定形式,不可更改的。
B部分:是URI的授权部分,是唯一标识符,用来定位ContentProvider。格式一般是自定义ContentProvider类的完全限定名称,注册时需要用到,如:com.alexzhou.provider.NoteProvider
C部分和D部分:是每个ContentProvider内部的路径部分,C和D部分称为路径片段,C部分指向一个对象集合,一般用表的名字,如:/notes表示一个笔记集合;D部分指向特定的记录,如:/notes/1表示id为1的笔记,如果没有指定D部分,则返回全部记录。
在开发中通常使用常量来定义URI,如下面的CONTENT_URI:
1
2
|
public
static
final
String AUTHORITY =
"com.alexzhou.provider.NoteProvider"
;
public
static
final
Uri CONTENT_URI = Uri.parse(
"content://"
+ AUTHORITY +
"/notes"
);
|
(2) MIME
MIME是指定某个扩展名的文件用一种应用程序来打开,就像你用浏览器查看PDF格式的文件,浏览器会选择合适的应用来打开一样。Android中的工作方式跟HTTP类似,ContentProvider会根据URI来返回MIME类型,ContentProvider会返回一个包含两部分的字符串。MIME类型一般包含两部分,如:
text/html
text/css
text/xml
application/pdf
等,分为类型和子类型,Android遵循类似的约定来定义MIME类型,每个内容类型的Android MIME类型有两种形式:多条记录(集合)和单条记录。
多条记录
vnd.android.cursor.dir/vnd.alexzhou.note
单条记录
vnd.android.cursor.item/vnd.alexzhou.note
vnd表示这些类型和子类型具有非标准的、供应商特定的形式。Android中类型已经固定好了,不能更改,只能区别是集合还是单条具体记录,子类型vnd.之后的内容可以按照格式随便填写。在使用Intent时,会用到MIME这玩意,根据Mimetype打开符合条件的活动。
3. 接下来通过一个简单的demo,来学习怎么创建自定义的ContentProvider,这里数据源选用SQLite,最常用的也是这个。
(1) 创建一个类NoteContentProvider,继承ContentProvider,需要实现下面5个方法:
query
insert
update
delete
getType
这些方法是eclipse自动生成的,暂时先不动他们。
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
|
/**
author:alexzhou
email :zhoujiangbohai@163.com
date :2013-2-25
**/
public
class
NoteContentProvider
extends
ContentProvider {
@Override
public
boolean
onCreate() {
// TODO Auto-generated method stub
return
false
;
}
@Override
public
Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
return
null
;
}
@Override
public
String getType(Uri uri) {
// TODO Auto-generated method stub
return
null
;
}
@Override
public
Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
return
null
;
}
@Override
public
int
delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
return
0
;
}
@Override
public
int
update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return
0
;
}
}
|
(2)先来设计一个数据库,用来存储笔记信息,主要包含_ID,title,content,create_date四个字段。创建NoteProviderMetaData类,封装URI和数据库、表、字段相关信息,源码如下:
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
|
/**
author:alexzhou
email :zhoujiangbohai@163.com
date :2013-2-25
**/
public
class
NoteProviderMetaData {
public
static
final
String AUTHORITY =
"com.alexzhou.provider.NoteProvider"
;
public
static
final
String DATABASE_NAME =
"note.db"
;
public
static
final
int
DATABASE_VERSION =
1
;
/**
*
*数据库中表相关的元数据
*/
public
static
final
class
NoteTableMetaData
implements
BaseColumns {
public
static
final
String TABLE_NAME =
"notes"
;
public
static
final
Uri CONTENT_URI = Uri.parse(
"content://"
+ AUTHORITY +
"/notes"
);
public
static
final
String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.alexzhou.note"
;
public
static
final
String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/vnd.alexzhou.note"
;
public
static
final
String NOTE_TITLE =
"title"
;
public
static
final
String NOTE_CONTENT =
"content"
;
public
static
final
String CREATE_DATE =
"create_date"
;
public
static
final
String DEFAULT_ORDERBY =
"create_date DESC"
;
public
static
final
String SQL_CREATE_TABLE =
"CREATE TABLE "
+ TABLE_NAME +
" ("
+ _ID +
" INTEGER PRIMARY KEY,"
+ NOTE_TITLE +
" VARCHAR(50),"
+ NOTE_CONTENT +
" TEXT,"
+ CREATE_DATE +
" INTEGER"
+
");"
;
}
}
|
AUTHORITY代表授权,该字符串和在Android描述文件AndroidManifest.xml中注册该ContentProvider时的android:authorities值一样,NoteTableMetaData继承BaseColumns,后者提供了标准的_id字段,表示行ID。
(3) ContentProvider是根据URI来获取数据的,那它怎么区分不同的URI呢,因为无论是获取笔记列表还是获取一条笔记都是调用query方法,现在来实现这个功能。需要用到类UriMatcher,该类可以帮助我们识别URI类型,下面看实现源码:
1
2
3
4
5
6
7
8
9
|
private
static
final
UriMatcher sUriMatcher;
private
static
final
int
COLLECTION_INDICATOR =
1
;
private
static
final
int
SINGLE_INDICATOR =
2
;
static
{
sUriMatcher =
new
UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(NoteProviderMetaData.AUTHORITY,
"notes"
, COLLECTION_INDICATOR);
sUriMatcher.addURI(NoteProviderMetaData.AUTHORITY,
"notes/#"
, SINGLE_INDICATOR);
}
|
这段代码是NoteContentProvider类中的,UriMatcher的工作原理:首先需要在UriMatcher中注册URI模式,每一个模式跟一个唯一的编号关联,注册之后,在使用中就可以根据URI得到对应的编号,当模式不匹配时,UriMatcher将返回一个NO_MATCH常量,这样就可以区分了。
(4) 还需为查询设置一个投影映射,主要是将抽象字段映射到数据库中真实的字段名称,因为这些字段有时是不同的名称,既抽象字段的值可以不跟数据库中的字段名称一样。这里使用HashMap来完成,key是抽象字段名称,value对应数据库中的字段名称,不过这里我把两者的值设置是一样的,在NoteContentProvider.java中添加如下代码。
1
2
3
4
5
6
7
8
|
private
static
HashMap<String, String> sNotesProjectionMap;
static
{
sNotesProjectionMap =
new
HashMap<String, String>();
sNotesProjectionMap.put(NoteProviderMetaData.NoteTableMetaData._ID, NoteProviderMetaData.NoteTableMetaData._ID);
sNotesProjectionMap.put(NoteProviderMetaData.NoteTableMetaData.NOTE_CONTENT, NoteProviderMetaData.NoteTableMetaData.NOTE_CONTENT);
sNotesProjectionMap.put(NoteProviderMetaData.NoteTableMetaData.NOTE_TITLE, NoteProviderMetaData.NoteTableMetaData.NOTE_TITLE);
sNotesProjectionMap.put(NoteProviderMetaData.NoteTableMetaData.CREATE_DATE, NoteProviderMetaData.NoteTableMetaData.CREATE_DATE);
}
|
(5) 在NoteContentProvider.java中创建一个内部类DatabaseHelper,继承自SQLiteOpenHelper,完成数据库表的创建、更新,这样可以通过它获得数据库对象,相关代码如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private
DatabaseHelper mDbHelper;
private
static
class
DatabaseHelper
extends
SQLiteOpenHelper {
public
DatabaseHelper(Context context) {
super
(context, NoteProviderMetaData.DATABASE_NAME,
null
, NoteProviderMetaData.DATABASE_VERSION);
}
@Override
public
void
onCreate(SQLiteDatabase db) {
Log.e(
"DatabaseHelper"
,
"create table: "
+ NoteProviderMetaData.NoteTableMetaData.SQL_CREATE_TABLE);
db.execSQL(NoteProviderMetaData.NoteTableMetaData.SQL_CREATE_TABLE);
}
@Override
public
void
onUpgrade(SQLiteDatabase db,
int
oldVersion,
int
newVersion) {
db.execSQL(
"DROP TABLE IF EXISTS "
+ NoteProviderMetaData.NoteTableMetaData.TABLE_NAME);
onCreate(db);
}
}
|
(6) 现在来分别实现第一步中未实现的5个方法,先来实现query方法,这里借助SQLiteQueryBuilder来为查询设置投影映射以及设置相关查询条件,看源码实现:
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
|
public
Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder =
new
SQLiteQueryBuilder();
switch
(sUriMatcher.match(uri)) {
case
COLLECTION_INDICATOR:
// 设置查询的表
queryBuilder.setTables(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME);
// 设置投影映射
queryBuilder.setProjectionMap(sNotesProjectionMap);
break
;
case
SINGLE_INDICATOR:
queryBuilder.setTables(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME);
queryBuilder.setProjectionMap(sNotesProjectionMap);
queryBuilder.appendWhere(NoteProviderMetaData.NoteTableMetaData._ID +
"="
+ uri.getPathSegments().get(
1
));
break
;
default
:
throw
new
IllegalArgumentException(
"Unknow URI: "
+ uri);
}
String orderBy;
if
(TextUtils.isEmpty(sortOrder))
{
orderBy = NoteProviderMetaData.NoteTableMetaData.DEFAULT_ORDERBY;
}
else
{
orderBy = sortOrder;
}
SQLiteDatabase db = mDbHelper.getReadableDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs,
null
,
null
, orderBy);
return
cursor;
}
|
返回的是一个Cursor对象,它是一个行集合,包含0和多个记录,类似于JDBC中的ResultSet,可以前后移动游标,得到每行每列中的数据。注意的是,使用它需要调用moveToFirst(),因为游标默认是在第一行之前。
(7)实现insert方法,实现把记录插入到基础数据库中,然后返回新创建的记录的URI。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
Uri insert(Uri uri, ContentValues values) {
if
(sUriMatcher.match(uri) != COLLECTION_INDICATOR) {
throw
new
IllegalArgumentException(
"Unknown URI "
+ uri);
}
SQLiteDatabase db = mDbHelper.getWritableDatabase();
long
rowID = db.insert(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME,
null
, values);
if
(rowID >
0
) {
Uri retUri = ContentUris.withAppendedId(NoteProviderMetaData.NoteTableMetaData.CONTENT_URI, rowID);
return
retUri;
}
return
null
;
}
|
(8) 实现update方法,根据传入的列值和where字句来更新记录,返回更新的记录数,看源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Override
public
int
update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
int
count = -
1
;
switch
(sUriMatcher.match(uri)) {
case
COLLECTION_INDICATOR:
count = db.update(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME, values,
null
,
null
);
break
;
case
SINGLE_INDICATOR:
String rowID = uri.getPathSegments().get(
1
);
count = db.update(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME, values, NoteProviderMetaData.NoteTableMetaData._ID +
"="
+ rowID,
null
);
break
;
default
:
throw
new
IllegalArgumentException(
"Unknow URI : "
+ uri);
}
this
.getContext().getContentResolver().notifyChange(uri,
null
);
return
count;
}
|
notifyChange函数是在更新数据时,通知其他监听对象。
(9)实现delete方法,该方法返回删除的记录数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
int
delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
int
count = -
1
;
switch
(sUriMatcher.match(uri)) {
case
COLLECTION_INDICATOR:
count = db.delete(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME, selection, selectionArgs);
break
;
case
SINGLE_INDICATOR:
String rowID = uri.getPathSegments().get(
1
);
count = db.delete(NoteProviderMetaData.NoteTableMetaData.TABLE_NAME, NoteProviderMetaData.NoteTableMetaData._ID +
"="
+ rowID,
null
);
break
;
default
:
throw
new
IllegalArgumentException(
"Unknow URI :"
+ uri);
}
// 更新数据时,通知其他ContentObserver
this
.getContext().getContentResolver().notifyChange(uri,
null
);
return
count;
}
|
(10) 实现getType方法,根据URI返回MIME类型,这里主要用来区分URI是获取集合还是单条记录,这个方法在这里暂时没啥用处,在使用Intent时有用。
1
2
3
4
5
6
7
8
9
10
11
12
|
public
String getType(Uri uri) {
switch
(sUriMatcher.match(uri)) {
case
COLLECTION_INDICATOR:
return
NoteProviderMetaData.NoteTableMetaData.CONTENT_TYPE;
case
SINGLE_INDICATOR:
return
NoteProviderMetaData.NoteTableMetaData.CONTENT_ITEM_TYPE;
default
:
throw
new
IllegalArgumentException(
"Unknow URI: "
+ uri);
}
}
|
(11) 在AndroidManifest.xml中注册该ContentProvider,这样系统才找得到,当然你也可以设置相关的权限,这里就不设置了。
1
|
<
provider
android:name
=
"NoteContentProvider"
android:authorities
=
"com.alexzhou.provider.NoteProvider"
/>
|
到现在为止,自定义ContentProvider的全部代码已经完成,下面创建一个简单的应用来测试一下。
主要测试insert、update、delete、query这四个函数。
1. 现在数据库中没有数据,所以先测试insert,插入一条数据。主要代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
public
static
final
String AUTHORITY =
"com.alexzhou.provider.NoteProvider"
;
public
static
final
Uri CONTENT_URI = Uri.parse(
"content://"
+ AUTHORITY +
"/notes"
);
private
void
insert() {
ContentValues values =
new
ContentValues();
values.put(
"title"
,
"hello"
);
values.put(
"content"
,
"my name is alex zhou"
);
long
time = System.currentTimeMillis();
values.put(
"create_date"
, time);
Uri uri =
this
.getContentResolver().insert(CONTENT_URI, values);
Log.e(
"test "
, uri.toString());
}
|
这里需要获得CONTENT_URI值,其他的应用可以通过ContentResolver接口访问ContentProvider提供的数据。logcat的输出如下:
因为现在数据库和表还不存在,所以首先会创建表,返回的URI的值是第一条记录的URI。
2. 测试query方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private
void
query() {
Cursor cursor =
this
.getContentResolver().query(CONTENT_URI,
null
,
null
,
null
,
null
);
Log.e(
"test "
,
"count="
+ cursor.getCount());
cursor.moveToFirst();
while
(!cursor.isAfterLast()) {
String title = cursor.getString(cursor.getColumnIndex(
"title"
));
String content = cursor.getString(cursor.getColumnIndex(
"content"
));
long
createDate = cursor.getLong(cursor.getColumnIndex(
"create_date"
));
Log.e(
"test "
,
"title: "
+ title);
Log.e(
"test "
,
"content: "
+ content);
Log.e(
"test "
,
"date: "
+ createDate);
cursor.moveToNext();
}
cursor.close();
}
|
logcat输入如下,正是刚才插入的值。
3. 测试update方法
1
2
3
4
5
6
7
|
private
void
update() {
ContentValues values =
new
ContentValues();
values.put(
"content"
,
"my name is alex zhou !!!!!!!!!!!!!!!!!!!!!!!!!!"
);
int
count =
this
.getContentResolver().update(CONTENT_URI, values,
"_id=1"
,
null
);
Log.e(
"test "
,
"count="
+count);
query();
}
|
logcat输出:
刚才插入的值已被更新了。
(4) 测试delete方法
1
2
3
4
5
|
private
void
delete() {
int
count =
this
.getContentResolver().delete(CONTENT_URI,
"_id=1"
,
null
);
Log.e(
"test "
,
"count="
+count);
query();
}
|
看logcat输出:
再次查询时,已经没有了数据。
ContentProvider的内部原理
自定义一个ContentProvider,来实现内部原理
步骤:
1、定义一个CONTENT_URI常量(里面的字符串必须是唯一)
Public static final Uri CONTENT_URI = Uri.parse("content://com.WangWeiDa.MyContentprovider");
如果有子表,URI为:
Public static final Uri CONTENT_URI = Uri.parse("content://com.WangWeiDa.MyContentProvider/users");
2、定义一个类,继承ContentProvider
Public class MyContentProvider extends ContentProvider
3、实现ContentProvider的所有方法(query、insert、update、delete、getType、onCreate)
package com.WangWeiDa.cp;
import java.util.HashMap;
import com.WangWeiDa.cp.MyContentProviderMetaData.UserTableMetaData;
import com.WangWeiDa.data.DatabaseHelp;
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.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
public class MyContentProvider extends ContentProvider {
//访问表的所有列
public static final int INCOMING_USER_COLLECTION = 1;
//访问单独的列
public static final int INCOMING_USER_SINGLE = 2;
//操作URI的类
public static final UriMatcher uriMatcher;
//为UriMatcher添加自定义的URI
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(MyContentProviderMetaData.AUTHORITIES,"/user",
INCOMING_USER_COLLECTION);
uriMatcher.addURI(MyContentProviderMetaData.AUTHORITIES,"/user/#",
INCOMING_USER_SINGLE);
}
private DatabaseHelp dh;
//为数据库表字段起别名
public static HashMap userProjectionMap;
static
{
userProjectionMap = new HashMap();
userProjectionMap.put(UserTableMetaData._ID,UserTableMetaData._ID);
userProjectionMap.put(UserTableMetaData.USER_NAME, UserTableMetaData.USER_NAME);
}
/**
* 删除表数据
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
System.out.println("delete");
//得到一个可写的数据库
SQLiteDatabase db = dh.getWritableDatabase();
//执行删除,得到删除的行数
int count = db.delete(UserTableMetaData.TABLE_NAME, selection, selectionArgs);
return count;
}
/**
* 数据库访问类型
*/
@Override
public String getType(Uri uri) {
System.out.println("getType");
//根据用户请求,得到数据类型
switch (uriMatcher.match(uri)) {
case INCOMING_USER_COLLECTION:
return MyContentProviderMetaData.UserTableMetaData.CONTENT_TYPE;
case INCOMING_USER_SINGLE:
return MyContentProviderMetaData.UserTableMetaData.CONTENT_TYPE_ITEM;
default:
throw new IllegalArgumentException("UnKnown URI"+uri);
}
}
/**
* 插入数据
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
//得到一个可写的数据库
SQLiteDatabase db = dh.getWritableDatabase();
//向指定的表插入数据,得到返回的Id
long rowId = db.insert(UserTableMetaData.TABLE_NAME, null, values);
if(rowId > 0){//判断插入是否执行成功
//如果添加成功,利用新添加的Id和
Uri insertedUserUri = ContentUris.withAppendedId(UserTableMetaData.CONTENT_URI, rowId);
//通知监听器,数据已经改变
getContext().getContentResolver().notifyChange(insertedUserUri, null);
return insertedUserUri;
}
return uri;
}
/**
* 创建ContentProvider时调用的回调函数
*/
@Override
public boolean onCreate() {
System.out.println("onCreate");
//得到数据库帮助类
dh = new DatabaseHelp(getContext(),MyContentProviderMetaData.DATABASE_NAME);
return false;
}
/**
* 查询数据库
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
//创建一个执行查询的Sqlite
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
//判断用户请求,查询所有还是单个
switch(uriMatcher.match(uri)){
case INCOMING_USER_COLLECTION:
//设置要查询的表名
qb.setTables(UserTableMetaData.TABLE_NAME);
//设置表字段的别名
qb.setProjectionMap(userProjectionMap);
break;
case INCOMING_USER_SINGLE:
qb.setTables(UserTableMetaData.TABLE_NAME);
qb.setProjectionMap(userProjectionMap);
//追加条件,getPathSegments()得到用户请求的Uri地址截取的数组,get(1)得到去掉地址中/以后的第二个元素
qb.appendWhere(UserTableMetaData._ID + "=" + uri.getPathSegments().get(1));
break;
}
//设置排序
String orderBy;
if(TextUtils.isEmpty(sortOrder)){
orderBy = UserTableMetaData.DEFAULT_SORT_ORDER;
}
else{
orderBy = sortOrder;
}
//得到一个可读的数据库
SQLiteDatabase db = dh.getReadableDatabase();
//执行查询,把输入传入
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
//设置监听
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
/**
* 更新数据库
*/
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
System.out.println("update");
//得到一个可写的数据库
SQLiteDatabase db = dh.getWritableDatabase();
//执行更新语句,得到更新的条数
int count = db.update(UserTableMetaData.TABLE_NAME, values, selection, selectionArgs);
return count;
}
}
4、在AndroidMinifest.xml中进行声明
android:name=".cp.MyContentProvider" android:authorities="com.WangWeiDa.cp.MyContentProvider" />
**为ContentProvider提供一个常量类MyContentProviderMetaData.java
package com.WangWeiDa.cp;
import android.net.Uri;
import android.provider.BaseColumns;
public class MyContentProviderMetaData {
//URI的指定,此处的字符串必须和声明的authorities一致
public static final String AUTHORITIES = "com.wangweida.cp.MyContentProvider";
//数据库名称
public static final String DATABASE_NAME = "myContentProvider.db";
//数据库的版本
public static final int DATABASE_VERSION = 1;
//表名
public static final String USERS_TABLE_NAME = "user";
public static final class UserTableMetaData implements BaseColumns{
//表名
public static final String TABLE_NAME = "user";
//访问该ContentProvider的URI
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIES + "/user");
//该ContentProvider所返回的数据类型的定义
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.myprovider.user";
public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.myprovider.user";
//列名
public static final String USER_NAME = "name";
//默认的排序方法
public static final String DEFAULT_SORT_ORDER = "_id desc";
}
}
Android之ContentProvider总结
1.适用场景
1) ContentProvider为存储和读取数据提供了统一的接口
2) 使用ContentProvider,应用程序可以实现数据共享
3) android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等)
2.相关概念介绍
1)ContentProvider简介
当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。
2)Uri类简介
Uri uri = Uri.parse("content://com.changcheng.provider.contactprovider/contact")
在Content Provider中使用的查询字符串有别于标准的SQL查询。很多诸如select, add, delete, modify等操作我们都使用一种特殊的URI来进行,这种URI由3个部分组成, “content://”, 代表数据的路径,和一个可选的标识数据的ID。以下是一些示例URI:
content://media/internal/images 这个URI将返回设备上存储的所有图片
content://contacts/people/ 这个URI将返回设备上的所有联系人信息
content://contacts/people/45 这个URI返回单个结果(联系人信息中ID为45的联系人记录)
尽管这种查询字符串格式很常见,但是它看起来还是有点令人迷惑。为此,Android提供一系列的帮助类(在android.provider包下),里面包含了很多以类变量形式给出的查询字符串,这种方式更容易让我们理解一点,因此,如上面content://contacts/people/45这个URI就可以写成如下形式:
Uri person = ContentUris.withAppendedId(People.CONTENT_URI, 45);
然后执行数据查询:
Cursor cur = managedQuery(person, null, null, null);
这个查询返回一个包含所有数据字段的游标,我们可以通过迭代这个游标来获取所有的数据:
package com.wissen.testApp; public class ContentProviderDemo extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); displayRecords(); } private void displayRecords() { //该数组中包含了所有要返回的字段 String columns[] = new String[] { People.NAME, People.NUMBER }; Uri mContacts = People.CONTENT_URI; Cursor cur = managedQuery( mContacts, columns, // 要返回的数据字段 null, // WHERE子句 null, // WHERE 子句的参数 null // Order-by子句 ); if (cur.moveToFirst()) { String name = null; String phoneNo = null; do { // 获取字段的值 name = cur.getString(cur.getColumnIndex(People.NAME)); phoneNo = cur.getString(cur.getColumnIndex(People.NUMBER)); Toast.makeText(this, name + ” ” + phoneNo, Toast.LENGTH_LONG).show(); } while (cur.moveToNext()); } } }
上例示范了一个如何依次读取联系人信息表中的指定数据列name和number。
修改记录:
我们可以使用ContentResolver.update()方法来修改数据,我们来写一个修改数据的方法:
private void updateRecord(int recNo, String name) { Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, recNo); ContentValues values = new ContentValues(); values.put(People.NAME, name); getContentResolver().update(uri, values, null, null); }
现在你可以调用上面的方法来更新指定记录:
updateRecord(10, ”XYZ”); //更改第10条记录的name字段值为“XYZ”
添加记录:
要增加记录,我们可以调用ContentResolver.insert()方法,该方法接受一个要增加的记录的目标URI,以及一个包含了新记录值的Map对象,调用后的返回值是新记录的URI,包含记录号。
上面的例子中我们都是基于联系人信息簿这个标准的Content Provider,现在我们继续来创建一个insertRecord() 方法以对联系人信息簿中进行数据的添加:
private void insertRecords(String name, String phoneNo) { ContentValues values = new ContentValues(); values.put(People.NAME, name); Uri uri = getContentResolver().insert(People.CONTENT_URI, values); Log.d(”ANDROID”, uri.toString()); Uri numberUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY); values.clear(); values.put(Contacts.Phones.TYPE, People.Phones.TYPE_MOBILE); values.put(People.NUMBER, phoneNo); getContentResolver().insert(numberUri, values); }
这样我们就可以调用insertRecords(name, phoneNo)的方式来向联系人信息簿中添加联系人姓名和电话号码。
删除记录:
Content Provider中的getContextResolver.delete()方法可以用来删除记录,下面的记录用来删除设备上所有的联系人信息:
private void deleteRecords() { Uri uri = People.CONTENT_URI; getContentResolver().delete(uri, null, null); }
你也可以指定WHERE条件语句来删除特定的记录:
getContentResolver().delete(uri, “NAME=” + “‘XYZ XYZ’”, null);
这将会删除name为‘XYZ XYZ’的记录。
3. 创建ContentProvider
要创建我们自己的Content Provider的话,我们需要遵循以下几步:
a. 创建一个继承了ContentProvider父类的类
b. 定义一个名为CONTENT_URI,并且是public static final的Uri类型的类变量,你必须为其指定一个唯一的字符串值,最好的方案是以类的全名称, 如:
public static final Uri CONTENT_URI = Uri.parse( “content://com.google.android.MyContentProvider”);
c. 定义你要返回给客户端的数据列名。如果你正在使用Android数据库,必须为其定义一个叫_id的列,它用来表示每条记录的唯一性。
d. 创建你的数据存储系统。大多数Content Provider使用Android文件系统或SQLite数据库来保持数据,但是你也可以以任何你想要的方式来存储。
e. 如果你要存储字节型数据,比如位图文件等,数据列其实是一个表示实际保存文件的URI字符串,通过它来读取对应的文件数据。处理这种数据类型的Content Provider需要实现一个名为_data的字段,_data字段列出了该文件在Android文件系统上的精确路径。这个字段不仅是供客户端使用,而且也可以供ContentResolver使用。客户端可以调用ContentResolver.openOutputStream()方法来处理该URI指向的文件资源;如果是ContentResolver本身的话,由于其持有的权限比客户端要高,所以它能直接访问该数据文件。
f. 声明public static String型的变量,用于指定要从游标处返回的数据列。
g. 查询返回一个Cursor类型的对象。所有执行写操作的方法如insert(), update() 以及delete()都将被监听。我们可以通过使用ContentResover().notifyChange()方法来通知监听器关于数据更新的信息。
h. 在AndroidMenifest.xml中使用<provider>标签来设置Content Provider。
i. 如果你要处理的数据类型是一种比较新的类型,你就必须先定义一个新的MIME类型,以供ContentProvider.geType(url)来返回。MIME类型有两种形式:一种是为指定的单个记录的,还有一种是为多条记录的。这里给出一种常用的格式:
vnd.android.cursor.item/vnd.yourcompanyname.contenttype (单个记录的MIME类型)
比如, 一个请求列车信息的URI如content://com.example.transportationprovider/trains/122 可能就会返回typevnd.android.cursor.item/vnd.example.rail这样一个MIME类型。
vnd.android.cursor.dir/vnd.yourcompanyname.contenttype (多个记录的MIME类型)
比如, 一个请求所有列车信息的URI如content://com.example.transportationprovider/trains 可能就会返回vnd.android.cursor.dir/vnd.example.rail这样一个MIME 类型。
下列代码将创建一个Content Provider,它仅仅是存储用户名称并显示所有的用户名称(使用 SQLLite数据库存储这些数据):
public class MyUsers { public static final String AUTHORITY = “com.wissen.MyContentProvider”; // BaseColumn类中已经包含了 _id字段 public static final class User implements BaseColumns { public static final Uri CONTENT_URI = Uri.parse(”content://com.wissen.MyContentProvider”); // 表数据列 public static final String USER_NAME = “USER_NAME”; } }
上面的类中定义了Content Provider的CONTENT_URI,以及数据列。下面我们将定义基于上面的类来定义实际的Content Provider类:
public class MyContentProvider extends ContentProvider { private SQLiteDatabase sqlDB; private DatabaseHelper dbHelper; private static final String DATABASE_NAME = “Users.db”; private static final int DATABASE_VERSION= 1; private static final String TABLE_NAME= “User”; private static final String TAG = “MyContentProvider”; private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { //创建用于存储数据的表 db.execSQL(”Create table ” + TABLE_NAME + “( _id INTEGER PRIMARY KEY AUTOINCREMENT, USER_NAME TEXT);”); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(”DROP TABLE IF EXISTS ” + TABLE_NAME); onCreate(db); } } @Override public int delete(Uri uri, String s, String[] as) { return 0; } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues contentvalues) { sqlDB = dbHelper.getWritableDatabase(); long rowId = sqlDB.insert(TABLE_NAME, “”, contentvalues); if (rowId > 0) { Uri rowUri = ContentUris.appendId(MyUsers.User.CONTENT_URI.buildUpon(), rowId).build(); getContext().getContentResolver().notifyChange(rowUri, null); return rowUri; } throw new SQLException(”Failed to insert row into ” + uri); } @Override public boolean onCreate() { dbHelper = new DatabaseHelper(getContext()); return (dbHelper == null) ? false : true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); SQLiteDatabase db = dbHelper.getReadableDatabase(); qb.setTables(TABLE_NAME); Cursor c = qb.query(db, projection, selection, null, null, null, sortOrder); c.setNotificationUri(getContext().getContentResolver(), uri); return c; } @Override public int update(Uri uri, ContentValues contentvalues, String s, String[] as) { return 0; } }
一个名为MyContentProvider的Content Provider创建完成了,它用于从Sqlite数据库中添加和读取记录。
Content Provider的入口需要在AndroidManifest.xml中配置:
<provider android:name=”MyContentProvider” android:authorities=”com.wissen.MyContentProvider” />
之后,让我们来使用这个定义好的Content Provider:
1)为应用程序添加ContentProvider的访问权限。
2)通过getContentResolver()方法得到ContentResolver对象。
3)调用ContentResolver类的query()方法查询数据,该方法会返回一个Cursor对象。
4)对得到的Cursor对象进行分析,得到需要的数据。
5)调用Cursor类的close()方法将Cursor对象关闭。
public class MyContentDemo extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); insertRecord(”MyUser”); displayRecords(); } private void insertRecord(String userName) { ContentValues values = new ContentValues(); values.put(MyUsers.User.USER_NAME, userName); getContentResolver().insert(MyUsers.User.CONTENT_URI, values); } private void displayRecords() { String columns[] = new String[] { MyUsers.User._ID, MyUsers.User.USER_NAME }; Uri myUri = MyUsers.User.CONTENT_URI; Cursor cur = managedQuery(myUri, columns,null, null, null ); if (cur.moveToFirst()) { String id = null; String userName = null; do { id = cur.getString(cur.getColumnIndex(MyUsers.User._ID)); userName = cur.getString(cur.getColumnIndex(MyUsers.User.USER_NAME)); Toast.makeText(this, id + ” ” + userName, Toast.LENGTH_LONG).show(); } while (cur.moveToNext()); } } }