Android 四大组件之 ContentProvider

ContentProvider是应用程序之间数据存储和检索的一个桥梁,它的作用就是使得各个应用程序之间实现数据共享。ContentProvider是一个抽象类,它提供了一套标准的接口来获取和操作数据。可以把数据封装到ContentProvider中,从而使这些数据可以被其他的应用程序所共享。

ContentProvider

ContentProvider架构思想:

  1. 对数据的抽象,为所有的组件提供统一的访问数据的方式,从而让组件不必关心具体数据的呈现形式(文件or数据库)。
  2. 接口更加方便,ContentProvider的访问标识为Uri,通过统一的ContentResolver进行访问,而ContentResolver和Uri跟Application的上下文Context以及组件之间的信息传送工具Intent都是无缝接合,这就让组件之间进行数据共享和数据传递更加的方便和快捷。

Uri

Uri通用资源标志符(Universal Resource Identifier, 简称”URI”)。
代表了要操作的数据,一个Uri由以下几部分组成:

  1. scheme: ContentProvider的scheme由Android规定为:content://
  2. 主机名(Authority): 用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
  3. 路径(path): 可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:

比如:

  • 要操作contact表中id为10的记录: /contact/10
  • 要操作contact表中id为10的记录的name字段: /contact/10/name
  • 要操作contact表中的所有记录: /contact

如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,Uri uri = Uri.parse("content://rcsim/chat");

## ContentProvider的创建

要创建我们自己的ContentProvider的话,我们需要遵循以下几步:

  1. 创建一个继承ContentProvider的类,需要实现其主要的抽象方法:
1
2
3
4
5
6
7
8
9
10
11
public boolean onCreate()
public Uri insert(Uri uri, ContentValues values)
public int delete(Uri uri, String selection, String[] selectionArgs)
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
public String getType(Uri uri)
  1. 声明CONTENT_URI,实现UriMatcher

Uri代表了要操作的数据,我们需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher和ContentUris。UriMatcher类用于匹配Uri。

如下是SettingsProvider中的声明:

1
2
3
4
5
6
7
8
9
public static final String AUTHORITY = "settings";
public static final class System extends NameValueTable {
private static final float DEFAULT_FONT_SCALE = 1.0f;
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/system");
}
1
2
3
4
5
private static final UriMatcher sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 添加需要匹配的Uri,匹配则返回相应的匹配码
Urimatcher.addURI(AUTHORITY, "chat", CHAT_ALL);
Urimatcher.addURI(AUTHORITY, "chat/#", CHAT_ID);

常量 UriMatcher.NO_MATCH 表示不匹配任何路径的返回码

# 号为通配符,* 号为任意字符。

ContentUris类用于操作Uri路径后面的ID部分,它有两个比较实用的方法:

withAppendedId(uri, id)用于为路径加上ID部分:

1
2
Uri uri = Uri.parse("content://rcsim/chat")
Uri resultUri = ContentUris.withAppendedId(uri, 10);

parseId(uri)方法用于从路径中获取ID部分:

1
2
3
Uri uri = Uri.parse("content: //rcsim/chat /10")
long msgId = ContentUris.parseId(uri);
ContentUris.withAppendedId() 和 Uri.withAppendedPath()。
  1. AndroidMenifest.xml中使用<provider>标签来配置ContentProvider
1
2
<provider android:name="RcsimProvider"
android:authorities="rcsim"/>

如果要处理的数据类型是一种比较新的类型,你就必须先定义一个新的MIME类型,以供ContentProvider.geType(url)来返回。MIME类型有两种形式:一种是为指定的单个记录的,还有一种是为多条记录的。

使用ContentProvider

当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver类来完成,要获取ContentResolver对象,可以使用Activity提供的getContentResolver()方法。ContentResolver类提供了与ContentProvider类相同签名的四个方法:

1
2
3
4
5
6
7
public Uri insert(Uri uri, ContentValues values)
public int delete(Uri uri, String selection, String[] selectionArgs)
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

这些方法的第一个参数为Uri,代表要操作的ContentProvider和对其中的什么数据进行操作。

监听ContentProvider中数据的变化

如果ContentProvider的访问者需要知道ContentProvider中的数据发生变化,可以在ContentProvider发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者:

1
2
3
4
5
6
7
public class XXXContentProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
db.insert(table, null, values);
getContext().getContentResolver().notifyChange(uri, null);
}
}

如果ContentProvider的访问者需要得到数据变化通知,必须使用ContentObserver对数据进行监听。ContentObserver——内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化。
它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法:

1
2
3
4
5
6
7
8
9
10
11
12
getContentResolver().registerContentObserver(uri, true, new XXXObserver (new Handler()));
public class XXXObserver extends ContentObserver{
public XXXObserver (Handler handler) {
super(handler);
}
public void onChange(boolean selfChange) {
// TODO
}
}

ContentProvider的存储介质可以是文件,也可以是SQLite数据库

SQLite数据库

Android提供了SQLiteDatabase类,该类封装了一些操作数据库的API,使用该类可以完成对数据进行添加(Create)、查询(Retrieve)、更新(Update)和删除(Delete)操作(这些操作简称为CRUD)。

SQLiteDatabase实例对象可以通过SQLiteOpenHelper.getWriteableDatabase()获取。

通过继承SQLiteOpenHelper,来实现应用自身的数据存储。

SQLiteOpenHelper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private SQLiteDatabase getDatabaseLocked(boolean writable) {
// ...
db.beginTransaction();
try {
if (version == 0) {
onCreate(db);
} else {
if (version > mNewVersion) {
onDowngrade(db, version, mNewVersion);
} else {
onUpgrade(db, version, mNewVersion);
}
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}