最近项目中用到了数据批量入库和监控数据库变化的需求,整理总结如下:
1.批量操作数据库的方法
1)ContentProvider中提供了批量处理数据的方法applyBatch,Android源码在ContentProvider.java中实现如下:
@Override
public ContentProviderResult[] applyBatch(String callingPkg,
ArrayList operations)
throws OperationApplicationException {
int numOperations = operations.size();
final int[] userIds = new int[numOperations];
for (int i = 0; i < numOperations; i++) {
ContentProviderOperation operation = operations.get(i);
Uri uri = operation.getUri();
validateIncomingUri(uri);
userIds[i] = getUserIdFromUri(uri);
if (userIds[i] != UserHandle.USER_CURRENT) {
// Removing the user id from the uri.
operation = new ContentProviderOperation(operation, true);
operations.set(i, operation);
}
if (operation.isReadOperation()) {
if (enforceReadPermission(callingPkg, uri, null)
!= AppOpsManager.MODE_ALLOWED) {
throw new OperationApplicationException("App op not allowed", 0);
}
}
if (operation.isWriteOperation()) {
if (enforceWritePermission(callingPkg, uri, null)
!= AppOpsManager.MODE_ALLOWED) {
throw new OperationApplicationException("App op not allowed", 0);
}
}
}
final String original = setCallingPackage(callingPkg);
try {//ContentProvider不同的子对象类型的方法调用是在这里进行分发的
ContentProviderResult[] results = ContentProvider.this.applyBatch(operations);
if (results != null) {
for (int i = 0; i < results.length ; i++) {
if (userIds[i] != UserHandle.USER_CURRENT) {
// Adding the userId to the uri.
results[i] = new ContentProviderResult(results[i], userIds[i]);
}
}
}
return results;
} finally {
setCallingPackage(original);
}
}
那么,如何使用该方法呢?我们仍然以Android源码中对通讯录的操作为例来讲。
应用端使用ContentProvider提供的applyBatch来进行批量处理,那么applyBatch是怎么使用的呢?以通讯录中联系人批量入库为例(下面这段代码是在自己项目中使用时从网上一位仁兄那里看到的,结果上来就能用上):
public static void batchInsertContacts(Context context, List contactList) throws RemoteException, OperationApplicationException {
ArrayList ops = new ArrayList<>();
int rawContactInsertIndex = 0;
for (Contact contact : contactList) {
rawContactInsertIndex = ops.size(); // 有了它才能给真正的实现批量添加
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.withYieldAllowed(true).build());
// 添加姓名
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());
// 添加号码
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.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, contact.getPhone())
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
.withValue(ContactsContract.CommonDataKinds.Phone.LABEL, "").withYieldAllowed(true).build());
}
//这里调用了applyBatch,是真正批量入库所在
context.getContentResolver()
.applyBatch(ContactsContract.AUTHORITY, ops);
}
2)那么在这里调用了applyBatch之后的代码流程究竟是怎样的呢?
下面是我打印的一段代码调用栈:
I/ContactsProvider( 2736): java.lang.RuntimeException: startTransaction
I/ContactsProvider( 2736): at com.android.providers.contacts.AbstractContactsProvider.startTransaction(AbstractContactsProvider.java:254)
I/ContactsProvider( 2736): at com.android.providers.contacts.AbstractContactsProvider.insert(AbstractContactsProvider.java:134)
I/ContactsProvider( 2736): at com.android.providers.contacts.ContactsProvider2.insert(ContactsProvider2.java:2166)
I/ContactsProvider( 2736): at android.content.ContentProviderOperation.apply(ContentProviderOperation.java:240)//这里判断具体的操作类型TYPE_INSERT、TYPE_DELETE、TYPE_UPDATE、TYPE_ASSERT
I/ContactsProvider( 2736): at com.android.providers.contacts.AbstractContactsProvider.applyBatch(AbstractContactsProvider.java:237)
I/ContactsProvider( 2736): at com.android.providers.contacts.ContactsProvider2.applyBatch(ContactsProvider2.java:2321)
I/ContactsProvider( 2736): at android.content.ContentProvider$Transport.applyBatch(ContentProvider.java:288)//首先调用到ContentProvider中的applyBatch,然后根据子类对象类型进行分发,在这里因为是Contacts所以会分发到ContactsProvider2中去
I/ContactsProvider( 2736): at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:192)
I/ContactsProvider( 2736): at android.os.Binder.execTransact(Binder.java:446)
结合源码分析上边调用栈中的类,会发现
原来
AbstractContactsProvider
类继承了
ContentProvider
,而
ContactsProvider2继承了AbstractContactsProvider
,在
ContactsProvider2中的applyBatch调用了其父类的applyBatch。然后根据ContentProviderOperation.apply来判断其操作类型为insert,接着调用了AbstractContactsProvider
中的
insert方法,从而实现了通讯录的批量入库操作。
3)从上边通讯录入库的流程,我们可以总结出具体业务实现批量入库的方法。其实很简单,只要在对应的业务类中继承ContentProvider并实现applyBatch方法就可以了。当然也得有自己操作数据库的insert、delete、update方法的实现。
2.监控数据库的方法
ContentProvider提供了ContentObserver这么一个抽象类来帮助我们监控数据库的变化。使用该类的地方只需要继承该类来操作就可以了。
以Android源码中CallLogFragment中的代码为例说明使用方法:
1)继承ContentObserver来创建类,这种类一般都是私有的,因为它只在本地使用,属于类中定义的类
private class CustomContentObserver extends ContentObserver {//该类继承了ContentObserver
public CustomContentObserver() {
super(mHandler);
}
@Override
public void onChange(boolean selfChange) {//onChange方法被调用,说明监控的数据库发生了变化,是我们可以进行具体业务处理的地方
mRefreshDataRequired = true;
}
}
2)用新定义的类来创建一个对象
private final ContentObserver mCallLogObserver = new CustomContentObserver();//这个是定义的监控对象,用它来监控数据库变化
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
.......................
getActivity().getContentResolver().registerContentObserver(CallLog.CONTENT_URI, true,
mCallLogObserver);//第一个参数是要监控的数据库表的URI也就是被监控对象;第二个参数设置为true,如果注册监听的URI为content://111,那么URI为content://111/222的数据改变也会触发该监听器;第三个参数是我们上边创建的监听器
4)进行去注册操作
@Override
public void onDestroy() {
super.onDestroy();
............................
getActivity().getContentResolver().unregisterContentObserver(mCallLogObserver);//程序结束的时候一定要进行去注册监听器
}
5)实际上经过上边的注册流程之后,只要监听的数据库发生了变化,上边实现的onChange都能监控到。这是因为,只要数据发生变化,ContentResolver中的notifyChange就会被调用
public void notifyChange(Uri uri, ContentObserver observer) {
notifyChange(uri, observer, true /* sync to network */);
}
监控具体数据库变化的完整流程就是这样。