ContentProvider提供的对数据库批量操作的方法和对数据库变化监控的方法

最近项目中用到了数据批量入库和监控数据库变化的需求,整理总结如下:

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();//这个是定义的监控对象,用它来监控数据库变化

3)注册该对象监控的URI

@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 */);
    }

监控具体数据库变化的完整流程就是这样。










你可能感兴趣的:(Android源码,android应用)