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

来源:互联网 发布:sql update 更新多行 编辑:程序博客网 时间:2024/05/16 10:43

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

1.批量操作数据库的方法

1)ContentProvider中提供了批量处理数据的方法applyBatch,Android源码在ContentProvider.java中实现如下:

@Override        public ContentProviderResult[] applyBatch(String callingPkg,                ArrayList<ContentProviderOperation> 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 {//<span style="font-family: Arial, Helvetica, sans-serif;">ContentProvider</span><span style="font-family: 宋体;">不同的子对象类型的方法调用是在这里进行分发的</span>                <span style="color:#3333ff;">ContentProviderResult[] results = ContentProvider.this.applyBatch(operations);</span>                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<Contact> contactList) throws RemoteException, OperationApplicationException {    ArrayList<ContentProviderOperation> 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: startTransactionI/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.<span style="color:#3333ff;">ContentProviderOperation.apply</span>(ContentProviderOperation.java:240)//这里判断具体的操作类型TYPE_INSERT、TYPE_DELETE、TYPE_UPDATE、TYPE_ASSERTI/ContactsProvider( 2736):      at com.android.providers.contacts.AbstractContactsProvider.applyBatch(AbstractContactsProvider.java:237)I/ContactsProvider( 2736):      at com.android.providers.contacts.<span style="color:#6633ff;">ContactsProvider2.applyBatch</span>(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 */);    }

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










0 0