1. 程式人生 > >ContentProvider提供的對資料庫批量操作的方法和對資料庫變化監控的方法

ContentProvider提供的對資料庫批量操作的方法和對資料庫變化監控的方法

最近專案中用到了資料批量入庫和監控資料庫變化的需求,整理總結如下:

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: 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.<span style="color:#3333ff;">ContentProviderOperation.apply</span>(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.<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 */);
    }

監控具體資料庫變化的完整流程就是這樣。