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之後的程式碼流程究竟是怎樣的呢?
下面是我列印的一段程式碼呼叫棧:
結合原始碼分析上邊呼叫棧中的類,會發現 原來 AbstractContactsProvider 類繼承了 ContentProvider ,而 ContactsProvider2繼承了AbstractContactsProvider ,在 ContactsProvider2中的applyBatch呼叫了其父類的applyBatch。然後根據ContentProviderOperation.apply來判斷其操作型別為insert,接著呼叫了AbstractContactsProvider 中的 insert方法,從而實現了通訊錄的批量入庫操作。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)
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 */);
}
監控具體資料庫變化的完整流程就是這樣。