Android四大组件之Content Provider
**一、概念**
Content Provider 作为Android应用程序四大组件之一,为存储和查询数据提供统一的接口,实现程序间数据的共享。Android系统内一些常见的数据如音乐、视频、图像等都内置了一系列的Content Provider。
应用程序间共享数据有两种方式:
一是创建子类继承于Content Provider,重写该类用于数据存储和查询的方法。
二是直接使用已经存在的Content Provider,如联系人等。
在Content Provider中数据的是以表的形式存储,在数据表中每一行为一条记录,每一列为类型和数据。每一条记录都包括一个唯一的名叫_ID的数值字段,这个字段唯一标识一条数据记录,在创建表的时候用 INTEGER PRIMARY KEY AUTOINCREMENT来标识此字段。
**二、相关类介绍**
**类URI:**
每一个Content Provider为其管理的多个数据集,分配一个URI,这个URI对外提供了一个能够唯一标识自己数据集的字符串。这样别的应用程序就可以通过这个URI来访问这个数据集。Android中所有的Content Provider的URI都有固定格式:content://**开头,一般可分为4个部分:
**标准前缀:**用来标识一个Content Provider,固定Content://
**URI标识:**定义了是哪个Content Provider提供这些数据。一般是定义该ContentProvider的包类的名字。和AndroidManifest.xml中定义的authorities属性相同。
**路径:**标识URI下的某一个Item。
**记录的ID:**如果URI中包含表示某个记录的ID,则返货该id对应的数据。否则表示返回全部。
注意:"content://com.android.people.provider/contacts/#" 这里#表示匹配任意数字"content://com.android.people.provider/contacts/*"表示匹配任意文本
**UriMatcher:**Uri标识了要操作的数据,而UriMatcher即使Android提供给我们用于操作Uri这个数据的工具类。
常用方法:
public void addURI(String authority, String path, int code) 往UriMatcher对象里面添加Uri。
public int match(Uri uri) 与UriMatcher对象的Uri进行匹配,如果成功返回上面传入的code值,否则返回-1.
**类ContentUris:**类似于UriMatcher,也是一个操作Uri数据的工具类,用于在Uri后面追加一个ID或解析出传入的Uri对象的ID值。
常用方法:
public static Uri withAppendId(Uri contentUri, long id) 为前面contentUri加上ID部分
public static long parseId(Uri contentUri) 从contentUri中获取ID部分
**类ContentProvider:**常用方法:
~~~
public abstract boolean onCreate();
public abstract Uri insert(Uri uri, ContentValues values)
public abstract int delete(Uri uri, String selection, String[] selectionArsg);
public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs);
public abstract Cursor query(Uri uri, String[] peojection, String selection, String[] selectionArgs, String sortOrder)
public abstract String getType(Uri uri)
~~~
这些都是抽象方法需要子类去实现。**类 ContentValues:**
android.content.ContentValues 这个用于存储ContentResolver能处理的数据的集合。
构造函数:
ContentValues() // 创建一个空的ContentValues对象,初始化默认大小
ContentValues(int size)
ContentValues(ContentValues from)
常用方法:
~~~
void clear()
boolean containKey(String key)
Object get(String key)
void put(String key, Type value) // Type: Byte Integer Float Short byte[] String Double Long Boolean
int size()
Type getAsTypeArray(String key) // Type: Object Boolean Byte byte[] Double Float Integer Long Short String
~~~
**类android.content.ContentResolver:**
一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据以类似数据库中表的方式完全暴露出去,那么外界其他应用程序怎么从数据库中获取数据呢,这就需要ContentResolver了,通过URI表示外界访问需要的数据库。 这个类为我们定义了一系列的方法包括:插入、删除、修改、查询等,与ContentProvider基本类似,主要根据传入的参数Uri找到对应的Content Provider,调用其相应的方法。
构造函数:
public ContentResolver(Context context)
一般在代码中我们直接通过: MainActivity.this.getContentResolver()获得当前应用程序的一个ContentResolver实例。
这里我们需要考虑一个问题,就是如果多个程序同时通过ContentResolver共享访问一个ContentProvider,会不会不同步,尤其是数据写入的时候这就需要在AndroidManifest.xml中定义ContentProvider的时候加上:<provider>元素的multiprocess属性。同时Android在ContentResolver中为我们提供了
notifyChange()接口,在数据发生改变时通知其他的ContentObserver。
~~~
final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
final void unregisterContentObserver(ContentObserver observer)
void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork)
void notifyChange(Uri uri, ContentObserver observer)
~~~
**三、使用已经存在的ContentProvider**
使用已经存在的ContentProvider包括两种情况:一是从已经存在的ContentProvider里面读取数据,如获得
手机里面联系人的信息。二是将自己的数据加入到ContentProvider里面,让其他程序能共享次数据,这就需要获得对这个
ContentProvider的写权限了。
Android中电话簿就是通过ContentProvider实现数据共享的,系统中有很多已经存在的共享URI,我们可以使用ContentResolver通过Uri来操作不同的标的数据。如Contacts.People.CONTENT_URI
在Android中为我们提供了两种方法来查询Content Provider:一是使用ContentResolver的query()方法,二是使用Activity对象的manageQuery()方法,他们的参数都相同,而且都返回Cursor对象。但是使用manageQuery()方法返回的Cursor对象的生命周期自动被Activity来管理,被管理的Cursor对象在Activity进入暂停状态的时候调用自己的deactivate()方法卸载,在Activity回到运行状态的时候调用自己的requery()方法重新查询生成的Cursor。而使用ContentResolver的query()方法返回的Cursor对象需要手动加入Activity来管理,这是通过Activity的startManagingCursor()方法来实现的。
**四、创建自己的ContentProvider**
(1) 创建一个继承于ContentProvider的类MyContentProvider
(2) 定义一个public static final Uri 类型变量CONTENT_URI
如:public static final Uri CONTENT_URI = Uri.parse("content://com.android.MyContentProvider")
(3) 定义需要返回给客户端的数据列名,如果使用到数据库SQLite,必须定义一个_id,表示记录的唯一性。
(4) 创建数据存储系统,如文件系统或数据库SQLite系统。
(5) 如果存储字节型数据,如位图文件等,数据列其实是一个表示实际保存文件的URI字符串,用来读取对应的实际文件数据。 处理这种数据类型的Content Provider需要实现一个名为_data的字段,该字段列出了该文件在Android系统的实际路 径,客户端可以通过调用方法ContentResolver.
openOutputStream()来处理该URI指向的文件资源。
(6) 查询返回一个Cursor类型对象,所有执行写操作的方法如insert()、update()及delete()都将被监听,可以通过Content
Resolver().notifyChange()来通过监听器关于数据更新的消息。
(7) 在AndroidManifest.xml中使用<provider>标签来设置Content Provider信息,如:android:authorities、android:name
android:permission等。
**Demo:**
**数据库类DatabaseHelper用来存储个人信息,名字(name)对应年龄(age)**
~~~
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "personInfo.db";
private static final String TB_NAME = "person";
private static final int VERSION = 1;
private static final String creat_cmd = "create table IF NOT EXISTS " + TB_NAME + " (_id integer PRIMARY KEY autoincrement, name text, age integer)";
private static final String upgrade_cmd = "alert table " + TB_NAME + " add sex varchar(8)";
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, VERSION);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL(creat_cmd);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
db.execSQL(upgrade_cmd);
}
}
~~~
**创建自己的ContentProvider**
~~~
// 创建一个MyProvider继承于ContentProvider
public class MyProvider extends ContentProvider {
private DatabaseHelper dbHelper; // 数据库类
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int ALL_PERSON = 1;
private static final int PERSON = 2;
private static final String TAG = "MyProvider";
private static final String TABLE_NAME = "person";
// 定义自己的URI
private static final String AUTHORITY = "com.myAndroid.myProvider";
public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);
static {
URI_MATCHER.addURI(AUTHORITY, "person", ALL_PERSON);
URI_MATCHER.addURI(AUTHORITY, "person/#", PERSON);
}
// ContentProvider的入口,初始化
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
Log.i(TAG, "----onCreate----");
dbHelper = new DatabaseHelper(this.getContext());
return false;
}
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// TODO Auto-generated method stub
Log.i(TAG, "----delete----");
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = 0;
switch (URI_MATCHER.match(arg0)) {
case ALL_PERSON:
count = db.delete(TABLE_NAME, arg1, arg2);
case PERSON:
long id = ContentUris.parseId(arg0);
String where = "_id=" + id;
if(arg1 != null && !"".equals(arg1)) {
where += " and " + arg1;
}
count = db.delete(TABLE_NAME, where, arg2);
default:
throw new IllegalArgumentException("Unknown Uri:" + arg0.toString());
}
getContext().getContentResolver().notifyChange(arg0, null); // 通知注册客户端数据发生改变
return count;
}
@Override
public String getType(Uri arg0) {
// TODO Auto-generated method stub
Log.i(TAG, "----getType----");
switch (URI_MATCHER.match(arg0)) {
case ALL_PERSON:
return "com.android.cursor.dir/person";
case PERSON:
return "com.android.cursor.item/person";
default:
throw new IllegalArgumentException("Unknow Uri" + arg0.toString());
}
}
@Override
public Uri insert(Uri uri, ContentValues arg1) {
// TODO Auto-generated method stub
Log.i(TAG, "----insert----");
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri insertUri = null;
switch (URI_MATCHER.match(uri)) {
case ALL_PERSON:
long rowId = db.insert(TABLE_NAME, "name", arg1);
Log.d(TAG, "insert:"+arg1.toString()+" Id:"+rowId);
insertUri = ContentUris.withAppendedId(uri, rowId);
// this.getContext().getContentResolver().notifyChange(insertUri, null);
break;
default:
throw new IllegalArgumentException("Unknown Uri:" + uri.toString());
}
getContext().getContentResolver().notifyChange(insertUri, null);
return insertUri;
}
// 处理查询,返回Cursor
@Override
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
String arg4) {
// TODO Auto-generated method stub
Log.i(TAG, "----query----");
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursir cursor = null;
switch (URI_MATCHER.match(arg0)) {
case ALL_PERSON: // query all the person info
cursor = db.query(TABLE_NAME, arg1, arg2, arg3, null, null, arg4);
case PERSON: // query only one person info from a given ID
long id = ContentUris.parseId(arg0);
String where = " _id=" + id;
if( (!"".equals(arg2)) && (arg2 != null)) {
where += " and " + arg2 ;
}
cursor = db.query(TABLE_NAME, arg1, where, arg3, null, null, arg4);
default:
throw new IllegalArgumentException("unknow uri" + arg0.toString());
}
if(cursor != null) {
// 注册该Uri对应的数据发生改变时,向客户端发送通知
cursor.setNotificationUri(getContext().getContentResolver(), uri);
}
return cursor;
}
@Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
// TODO Auto-generated method stub
Log.i(TAG, "----update----");
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = 0;
switch (URI_MATCHER.match(arg0)) {
case ALL_PERSON:
count = db.update(TABLE_NAME, arg1, arg2, arg3);
break;
case PERSON:
long id = ContentUris.parseId(arg0);
String where = "_id=" + id;
if( (arg2 != null) && (!"".equals(arg2))) {
where += " and " + arg2;
}
count = db.update(TABLE_NAME, arg1, where, arg3);
break;
default:
throw new IllegalArgumentException("Unknow Uri:"+arg0.toString());
}
getContext().getContentResolver().notifyChange(arg0, null);
return count;
}
}
~~~
**客户端使用Content Provider:**
~~~
// 定义自己的URI
private static final String TABLE_NAME = "person";
private static final String AUTHORITY = "com.myAndroid.myProvider";
public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);
// 插入
ContentResolver contenResolver = MainActivity.this.getContentResolver();
ContentValues values = new ContentValues();
values.put("name", "hello");
values.put("age", 25);
Uri resultUri = contentResolver.insert(CONTENT_URI, values);
if(ContentUris.parseId(resultUri) > 0) { Log.i(TAG, "OK"); }
// 查询
String columns[] = new String[] ("_id", "name", "age");
Cursor cursor = contentResolver.query(CONTENT_URI, columns, null, null, "_id");
if(cursor.moveToFirst()) {
do {
Log.i(TAG, "_id:"+cursor.getInt(cursor.getColumnIndex("_id")));
Log.i(TAG, "name:"+cursor.getString(cursor.getColumnIndex("name")));
Log.i(TAG, "age:"+cursor.getInt(cursor.getColumnIndex("age")));
} while(cursor.moveToNext());
cursor.close();
}
// 删除 ID为1的记录
contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), null, null);
// 修改ID为 1 的记录
ContentValues values = new ContentValues();
values.put("name", "world");
values.put("age", 32);
contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), values, null, null);
~~~
最后我们还需要在AndroidManifest.xml中定义我们的provider属性:
~~~
<provider android:name=".MyProvider" android:authorities="com.myAndroid.myProvider" />
~~~
**使用游标:**
这里我们关注下在query()方法中,我们查询数据库的时候使用到了一个Cursor对象,这个Cursor对象就是游标,它包含0个或多个记录。列名称、顺序和类型都是特定于ContentProvider的,但是返回的每行都包涵啊一个名为_id的默认咧,表示该行的唯一ID。
1、游标是一个行集合
2、读取数据之前,需要使用moveToFirst()将游标移动到第一行之前
3、需要知道列名称和列类型
4、所有字段的访问都是基于列编号,所有必须首先将该列名称转换为列编号
5,、游标可以随意移动(往前、往后,跳过一段距离等)
主要方法:
~~~
boolean moveToFirst()
boolean isBeforeFirst()
boolean isAfterLast()
boolean isClosed()
~~~
**使用Where查询:**
上面代码中使用的manageQuery()的签名为:
public final Cursor manageQuery(Uri uri, String[] projectin, String selection, String[] selectionArgs, String sortOrder);
参数:
Uri: 给定的URI
projection: 声明要返回的行属性,传递null 返回给定URI的所有行
selection 表示过滤器,以SQL Where字句(不含Where本身)的格式声明,可以使用?,将被替换为selectionArgs中的值,按照在列表中出现的顺序显示。
selectionArgs: 过滤器残数
sortOrder: 排序方式
如:查询id为23的笔记:
~~~
manageQuery("Content://com.google.provider.NotePad/notes/23",
null, null, null, null);
manageQuery("Content://com.google.provider.Notepad/notes",
null, "_id=?", new String[] {23}, null);
~~~
- 前言
- Android开发之serviceManager分析
- Android启动之init.c文件main函数分析
- Android开发之ProcessState和IPCThreadState类分析
- Android开发之MediaPlayerService服务详解(一)
- Android系统五大布局详解Layout
- Android四大组件之Content Provider
- Android四大组件之Service
- Android四大组件之BroadcastReceiver
- Android系统中的消息处理Looper、Handler、Message
- Android EditText/TextView使用SpannableString显示复合文本
- Android关键资源详解
- Android常用适配器分析(如何制作简易Launcher)
- Android常用列表控件