为了便于Android中进行批量数据库操作时效率更高,Android中推荐使用ContentProviderOperation,因而,ContentProviderOperation也成了操作数据库的利器。
官方推荐的理由如下:
1.所有的操作都在一个事务中执行,这样可以保证数据的完整性;
2.由于批量操作在一个事务中执行,只需要打开和关闭一次事务就可以了;
3.批量操作和单个多次操作相比,减少了contentProvider在上下文之间的切换,这样极大的提高了应用执行的性能;
ContentProviderOperation在源码中的路径为:frameworks/base/core/java/android/content/ContentProviderOperation.java
使用中创建ContentProviderOperation的对象主要是调用其提供的构建器build来创建。
Builder中提供的主要方法如下:
1)创建一个进行插入操作的builder对象
public static Builder newInsert(Uri uri) {
return new Builder(TYPE_INSERT, uri);
}
2)创建一个进行删除操作的builder对象
public static Builder newDelete(Uri uri) {
return new Builder(TYPE_DELETE, uri);
}
3)创建一个进行更新操作的builder对象
public static Builder newUpdate(Uri uri) {
return new Builder(TYPE_UPDATE, uri);
}
4)创建一个查询操作的builder对象(查询有没有符合条件的数据,如果没有,会抛出一个OperationApplicationException异常。)
public static Builder newAssertQuery(Uri uri) {
return new Builder(TYPE_ASSERT, uri);
}
5)设置查询条件(更新删除时使用)
对于数据库中固定列的数据我也要根据提供的条件来进行修改和删除或者查询操作的时候,可以用这个方法来设置条件。
public Builder withSelection(String selection, String[] selectionArgs) {
if (mType != TYPE_UPDATE && mType != TYPE_DELETE && mType != TYPE_ASSERT) {
throw new IllegalArgumentException(
"only updates, deletes, and asserts can have selections");
}
mSelection = selection;
if (selectionArgs == null) {
mSelectionArgs = null;
} else {
mSelectionArgs = new String[selectionArgs.length];
System.arraycopy(selectionArgs, 0, mSelectionArgs, 0, selectionArgs.length);
}
return this;
}
比如:将表中号码为18513523456的记录的CARDTYPE更新成“北京联通GSM卡”就可以这样做:
ContentProviderOperation operation = ContentProviderOperation
.newUpdate(content.CONTENT_URI)
.withSelection(content.NUMBER + "=?",
new String[] { "18513523456" })
.withValue(
content.CARDTYPE,// 电话号码对应的卡类型
“北京联通GSM卡”) .build();
6)定义多列数据值
public Builder withValues(ContentValues values) {
if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
throw new IllegalArgumentException(
"only inserts, updates, and asserts can have values");
}
if (mValues == null) {
mValues = new ContentValues();
}
mValues.putAll(values);
return this;
}
7)定义一列数据值。数据格式都是(key,value)对来存放的,key对应表中的列名,value为对应列名的值。
public Builder withValue(String key, Object value) {
if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
throw new IllegalArgumentException("only inserts and updates can have values");
}
if (mValues == null) {
mValues = new ContentValues();
}
if (value == null) {
mValues.putNull(key);
} else if (value instanceof String) {
mValues.put(key, (String) value);
} else if (value instanceof Byte) {
mValues.put(key, (Byte) value);
} else if (value instanceof Short) {
mValues.put(key, (Short) value);
} else if (value instanceof Integer) {
mValues.put(key, (Integer) value);
} else if (value instanceof Long) {
mValues.put(key, (Long) value);
} else if (value instanceof Float) {
mValues.put(key, (Float) value);
} else if (value instanceof Double) {
mValues.put(key, (Double) value);
} else if (value instanceof Boolean) {
mValues.put(key, (Boolean) value);
} else if (value instanceof byte[]) {
mValues.put(key, (byte[]) value);
} else {
throw new IllegalArgumentException("bad value type: " + value.getClass().getName());
}
return this;
}
8)withYieldAllowed的妙用避免长时间操作数据库而引起ANR
public Builder withYieldAllowed(boolean yieldAllowed) {
mYieldAllowed = yieldAllowed;
return this;
}
withYieldAllowed的介绍,在网上查到了下面这段介绍:
批量操作一大堆数据可能会长期锁定数据库,从而阻止其他应用访问该数据库并且有可能会引起ANR(应用无响应)对话框出现。
为了避免长期锁定数据库,只要在批量操作中添加“yield points”即可。一个yield points告诉Content Provider,在执行下一个操作之前可以先提交当前的数据,然后通知其他应用,如果有其他应用请求数据的话,就先让其他应用操作,等其他应用操作完成后,再继续打开一个事务来执行下一个操作。如果没有其他程序请求数据,则一个yield points不会自动提交事务,而是继续执行下一个批量操作。通常情况下一个同步Adapter应该在开始操作一行原数据之前添加一个yield points。如果您的数据也需要被其他应用使用的话,考虑在用ContentProviderOperation执行批量操作的时候添加yield points吧。
9)一次操作多个相关联表的时候使用withValueBackReference
public Builder withValueBackReference(String key, int previousResult) {
if (mType != TYPE_INSERT && mType != TYPE_UPDATE && mType != TYPE_ASSERT) {
throw new IllegalArgumentException(
"only inserts, updates, and asserts can have value back-references");
}
if (mValuesBackReferences == null) {
mValuesBackReferences = new ContentValues();
}
mValuesBackReferences.put(key, previousResult);
return this;
}
这个方法的使用在网上找到了一位仁兄的解释(直接引用,需要用的时候仔细研究):
如果我们一次只操作一个表的话,直接使用withValue就OK了,但是一次使用两个有关联的表则需要使用withValueBackReference,比如stackoverflow中举例,一个Foo拥有bar,插入Foo时同时将其关联的bar的信息也一并插入数据库,但在使用批次操作的时候我们不知道插入的Foo的对应的id,所以需要通过withValueBackReference来建立关系:withValueBackReference (BAR.FOO_ID, 0)中的0就是results[0]中返回对应的foo 0的id
ContentProviderOperation最使我佩服的是使用了构建器Builder设计模式,大大简化了代码编写而且简洁易懂。
观察上边列出的Builder中的方法,每个返回的类型都是Builder,所以在下面的ContentProviderOperation使用例子中,ops.add方法中的作为参数的方法最终返回的还是个builder对象。简单易懂就体现在这里。
ArrayList ops = new ArrayList<>();
int rawContactInsertIndex = 0;
for (Contact contact : contactList) {
// 添加姓名
ops.add(ContentProviderOperation
.newInsert(
android.provider.ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID,
rawContactInsertIndex)
.withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, contact.getName())
.withYieldAllowed(true).build());