转自:http://www.eoeandroid.com/thread-48518-1-1.html
我们平时在做Android开发的时候,一定经常会接触到数据库操作,android使用sqlite作为它的本地数据库,并提供了一种叫做Content Provider的数据访问机制,简单来说,它就像一个web服务,有自己的URI,我们也是通过URI的形式来访问它的数据,通过这种形式的接口,使得我们的数据不仅在我们自己的应用中可以访问,甚至还可以被系统中的其他应用所调用。 一个典型的例子就是我们手机中的通讯录,android给我们暴露了一个接口,我们只要申请到相应的权限,通过访问这个接口,就可以得到通讯录的信息了。 说了这么多,现在言归正传,这篇文章主要是和大家分享一下Content Provider的实现方式,通过一些更加标准的代码架构,可以使我们的项目的效率更高,并且提高可维护性。
为了说明问题,我们只位这个表设置了三个字段,分别使记录的id,记事内容,和编辑时间, 大家需要注意个是id字段需要在前面加一个下划线,否则在和ListView绑定的时候会出问题。
现在有了表接口,那么我们就可以将这张表抽象程一个数据结构,代码如下: 如上代码,我们定义好了我们需要的元数据,这个类首先定义了和ContentProvider相关的信息,AUTHORITY表示ContentProvider的URI,DATABASE_NAME和DATABASE_VERSION分别表示我们的数据库名和数据库版本。静态内部类NoteTableMetaData,表示我们刚才定义个表,首先TABLE_NAME表示表名,DEFAULT_SORT_ORDER 作为默认的排序规则,当然如果我们在查询中自定义了排序规则的话,这个就会被覆盖,CONTENT_URI定义了Provider对外访问的协议,CONTENT_TYPE,CONTENT_ITEM_TYPE用于表示两种uri模式的实体类型,这个其实很像我们http协议中的MIME类型。接下来的数据就是我们数据库字段的映射了,可以用注释标出他们的数据类型,这样,我们的元数据就完工拉,这个工作虽然有些枯燥,但是对于我们项目的可维护性还是很有帮助的,例如如果我们需要改动某个字段的名字,我们只需要修改元数据类就完成了所有的工作。
有了这些源数据,我们就可以开始来写ContentProvider了,首先,我们要定义一个默认的映射表,代码如下: 这个HashMap其实就相当于我们select语句中的别名,在后面的sqlite操作中会用到这个数据,一般情况下,我们不需要更改别名,所以在map中将键和值都设置为同样的就可以了。
由于我们的NoteProvider要处理两种形式的URI,所以我们需要一个机制来区分不同的URI,这就要用到UriMatcher,代码如下: UriMatcher用来区分不同的URI,首先我们将它定义为一个静态属性,然后在静态初始化块中,使用它的addURI方法,为它添加了两个URI规则,并为每个规则设置了一个表示常量,这里有QUERY_LIST和QUERY_ITEM,而这个方法的前两个参数就是用来构造这个URI规则的,例如第一条规则中,第一个参数我们用到了元数据中的 AUTHORITY和一个notes字符串, 这样,这条规则最终就会成为这种形式org.spring.provider.NoteProvider/notes 而另外一条规则就是这样org.spring.provider.NoteProvider/notes/# ,注意到第二条规则中的井号,它是一个占位符,在实际的场景中,这个位置会用一个数字来代替。说了这么多,我们为什么要用两个URI来为这个Provider来提供接口呢,相信只要做过web开发的朋友就会知道,假如我们要做一个CRUD功能,我们首先需要一个页面来显示数据,这个页面是不接受ID参数的。但我们还需要有一个编辑功能的页面,而这个页面就需要接受一个ID来区分要编辑哪一条记录。这样就不难理解我们为什么要用两种URI了,这里的第一条URI规则,就相当于那个显示数据的页面,而第二个URI 中的井号的位置就相当于编辑页面中的ID。有了这个matcher后,我们就可以根据不同的URI开执行各自所需的操作。
现在Provider的基本信息已经基本完成了,因为我们的Provider需要和数据库进行交互,所以我们还需要一个中间层,可以使用SQLiteOpenHelper。 如上代码,我们扩展了自己的Helper,并将它定义为Provider 的私有属性,这个类的实现中我们还是使用元数据来进行操作,例如创建数据库和更新数据库的操作,字段名和表名使用的是元数据中的属性。
现在有了这些基础架构后,我们就可以实现相应的数据库操作方法了,先从query说起: 这里使用了SQLiteQueryBuilder来构建数据库查询,这样可使我们从更抽象的层次来处理数据库交互,在这里我们之前定义的matcher就派上用场拉,我们判断了一下URI的类型,如果使QUERY_LIST,就查询所有的数据,而如果是QUERY_ITEM类型,就只查询特定ID下的记录。这一点在我们的代码中应该很明显,当然如果给出的URI不符合任意一条规则,那么就直接抛出异常。 接下来我判断了一下这次查询是否明确指定了排序规则,如果没指定就使用我们之前定一个默认规则来对数据进行排序,随后就是获取数据库引用,并执行插叙操作了。 这里要注意一下c.setNotificationUri方法,这个方法为当的URI注册了一下通知,简单来说就是这样,如果有其他的调用改变了底层数据库并发送了更改消息,那么这些注册了通知的URI会自动更新他们的数据集,而不用我们手动的进行刷新,这样可以省去很多繁琐的编码工作。最典型的例子就是为ListView绑定数据时,如果使用了这种机制,我们在修改数据库中的数据后,ListView 的显示也会自动的刷新。
接下来介绍一下insert方法: 先说一下这个方法传进来的参数,第一个参数时调用的URI,接下来的values就相当于insert语句中的列名和值的一对组合,由于这个插入方法只接受不带ID的URI所以我们在一开始的时候进行了一下判断,然后我们检测了一下是否指定了日期,如果没有就以当前时间作为默认值,随后就是数据库调用了。 我们通过返回rowID来判断该条记录是否插入成功,如果成功就返回带着这条记录ID的URI,否则就抛出异常。注意到这里的notifyChange方法,这个正好和前面的注册通知相对应,它会通知所有注册的URI,数据库已经改变。
下面再来介绍一下update方法: 这里个方法里的内容和前面很类似,唯一的区别就是通过URI来确定更新的方式。这一点在代码中写的也比较明白,所以就不赘述了。最后再介绍一下delete 方法: 这个方法也是判断两种不同的URI,如果时QUERY_LIST类型的URI,那么它就会删除所有的记录(当然,如果我们不需要这种操作,也可以忽略这种URI),另一种就是根据ID来删除相应的记录。这个方法应该也不难理解。 大家注意到,我们在这些方法中,大量的用到了我们元数据类中的信息,这样做的好处前面也说过了,隔离了底层的表结构后,让数据库结构的修改变得非常容易。
当然,我们还需要实现getType方法,来返回不同URI对应的MIME类型,这个信息,在我们的元数据中已有定义: 好啦,到现在位置我们的Provider就已经实现好了,最后不要忘了在manifest中注册这个Provider: 到此为止,我们的数据访问接口就实现完成了,我们可以用下面的方式很容易的进行数据访问:
这篇文章主要是给大家提供了一种ContentProvider的架构方法,通过一些良好的代码组织,可以让我们的开发工作变得更加轻松,并且有效的增强应用的建壮性,对我们日常的开发还是很有帮助的。当然这种方法并不是唯一的,更不敢说这个是最好的。更希望它能够起到一种抛砖引玉的作用,大家可以在这个基础之上进一步的探索,找出更加适合自己的架构方式~
以下是完整代码:
public class NoteProvider extends ContentProvider{
private static HashMap<String, String> noteProjectionMap;
static {
noteProjectionMap = new HashMap<String, String>();
noteProjectionMap.put(NoteTableMetaData.NOTE_ID, NoteTableMetaData.NOTE_ID);
noteProjectionMap.put(NoteTableMetaData.NOTE_CONTENT, NoteTableMetaData.NOTE_CONTENT);
noteProjectionMap.put(NoteTableMetaData.NOTE_PUB_DATE, NoteTableMetaData.NOTE_PUB_DATE);
}
private static UriMatcher matcher;
private static final int QUERY_LIST = 1;
private static final int QUERY_ITEM = 2;
static {
matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(NoteMetaData.AUTHORITY, "notes", QUERY_LIST);
matcher.addURI(NoteMetaData.AUTHORITY, "notes/#", QUERY_ITEM);
}
private DatabaseHelper dbHelper;
class DatabaseHelper extends SQLiteOpenHelper{
DatabaseHelper(Context context) {
super(context, NoteMetaData.DATABASE_NAME, null, NoteMetaData.DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(
"create table " + NoteTableMetaData.TABLE_NAME + " ( "
+ NoteTableMetaData.NOTE_ID + " integer primary key, "
+ NoteTableMetaData.NOTE_CONTENT + " varchar(2000), "
+ NoteTableMetaData.NOTE_PUB_DATE + " integer"
+ ");"
);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists " + NoteTableMetaData.TABLE_NAME);
onCreate(db);
}
}
@Override
public String getType(Uri uri) {
switch (matcher.match(uri)) {
case QUERY_LIST:{
return NoteTableMetaData.CONTENT_TYPE;
}
case QUERY_ITEM:{
return NoteTableMetaData.CONTENT_ITEM_TYPE;
}
default:{
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
switch (matcher.match(uri)) {
case QUERY_LIST:{
qb.setTables(NoteTableMetaData.TABLE_NAME);
qb.setProjectionMap(noteProjectionMap);
}break;
case QUERY_ITEM:{
qb.setTables(NoteTableMetaData.TABLE_NAME);
qb.setProjectionMap(noteProjectionMap);
qb.appendWhere(NoteTableMetaData.NOTE_ID + "=" + uri.getPathSegments().get(1));
}break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
String orderBy;
if(TextUtils.isEmpty(sortOrder)) {
orderBy = NoteTableMetaData.DEFAULT_SORT_ORDER;
}else {
orderBy = sortOrder;
}
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count;
switch (matcher.match(uri)) {
case QUERY_LIST:{
count = db.delete(NoteTableMetaData.TABLE_NAME, selection, selectionArgs);
}break;
case QUERY_ITEM:{
String rowID = uri.getPathSegments().get(1);
count = db.delete(NoteTableMetaData.TABLE_NAME,
NoteTableMetaData.NOTE_ID + "=" + rowID +
(!TextUtils.isEmpty(selection) ? (" and ( " + selection + " ) ") : "" ), selectionArgs);
}break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
if(matcher.match(uri) != QUERY_LIST) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
if(values.containsKey(NoteTableMetaData.NOTE_PUB_DATE) == false) {
Long now = Long.valueOf(System.currentTimeMillis());
values.put(NoteTableMetaData.NOTE_PUB_DATE, now);
}
SQLiteDatabase db = dbHelper.getWritableDatabase();
long rowID = db.insert(NoteTableMetaData.TABLE_NAME, NoteTableMetaData.NOTE_CONTENT, values);
if(rowID > 0) {
Uri insertedUri = ContentUris.withAppendedId(NoteTableMetaData.CONTENT_URI, rowID);
getContext().getContentResolver().notifyChange(insertedUri, null);
return insertedUri;
}
throw new android.database.SQLException("Failed to insert row into " + uri);
}
@Override
public boolean onCreate() {
dbHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count;
switch (matcher.match(uri)) {
case QUERY_LIST:{
count = db.update(NoteTableMetaData.TABLE_NAME, values,selection, selectionArgs);
}break;
case QUERY_ITEM:{
String rowID = uri.getPathSegments().get(1);
count = db.update(NoteTableMetaData.TABLE_NAME, values,
NoteTableMetaData.NOTE_ID + "=" + rowID
+ (!TextUtils.isEmpty(selection) ? ("and ( " + selection + " ) ") : ""), selectionArgs);
}break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}