Android中向ContactsProvider中插入大量联系人
應用場景
項目中存在這樣的需求,通過設備之間通過藍牙傳輸聯系人,并且需要將獲取過來的聯系人插入到ContactsProvider中
批量插入聯系人的標準代碼
在Android的源碼中,ContactsContract.java中為我們展示了批量插入聯系人的方法。
The batch method is by far preferred. It inserts the raw contact and itsconstituent data rows in a single database transactionand causes at most one aggregation pass.<pre>ArrayList<ContentProviderOperation> ops =new ArrayList<ContentProviderOperation>();...int rawContactInsertIndex = ops.size();ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI).withValue(RawContacts.ACCOUNT_TYPE, accountType).withValue(RawContacts.ACCOUNT_NAME, accountName).build());ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI).withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex).withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE).withValue(StructuredName.DISPLAY_NAME, "Mike Sullivan").build());getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);應用實例
在項目聯系人列表一開始是存在如下的一個本地聯系人列表中
public class TsContact implements Parcelable {public String mPhoneNumber;public int mPhoneType;public String mFirstName;public String mLastName;public String mMiddlename;... }假如List < TsContact> contactsList中存放了聯系人列表,那么如何將這個TsContacts列表同步到ContactsProvider中呢?
我一開始采用的代碼如下:
大功告成,運行成功,很開心。效率也比單個插入高很多。
遇到了TransactionTooLargeException
代碼運行了一段時間以后,測試提了一個bug,如果聯系人很多的時候,無法更新聯系人。
查了一下,當聯系人列表過大的時候,比如說超過一千,會拋出異常:TransactionTooLargeException。這是因為我們使用applyBatch接口來插入數據,最終還是需要通過binder將這些數據傳遞給ContactsProvider。而binder是輕量級跨進程通信機制,其傳遞數據上限。在Android 5.0中其定義為:
BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))
那么當聯系人列表過大,則傳遞的數據超過了binder所能傳遞數據的上限,拋出了異常。
怎么解決這個問題呢?那就是把聯系人列表截斷,分多次傳輸。
錯誤的分批批處理
一開始的我想到的是仍然將聯系人列表一次性轉化為插入操作的列表ArrayList< ContentProviderOperation > ops,再執行applyBatch,如果遇到TransactionTooLargeException,則將插入列表截成兩段,重新插入,如果還有異常,繼續截成兩段,就這樣二分下去,肯定可以插入 。恩,是的perfect code。
那么代碼就變成了
private void syncTSContactsToContactsProvider(List<TsContact> contactsList) {ArrayList<ContentProviderOperation> ops = new ArrayList<>();for (int index = 0; index < contactsList.size(); index++) {TsContact contact = contactsList.get(index);int 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(ContactsContract.Data.CONTENT_URI).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex).withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, contact.mLastName).withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, contact.mFirstName).withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, contact.mMiddlename).withYieldAllowed(true).build());ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactInsertIndex).withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE).withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, contact.mPhoneNumber).withValue(ContactsContract.CommonDataKinds.Phone.TYPE, contact.mPhoneType).withValue(ContactsContract.CommonDataKinds.Phone.LABEL, "").withYieldAllowed(true).build());}int transactionSize = ops.size();ArrayList<ContentProviderOperation> subOps = new ArrayList<>();while (!ops.isEmpty()) {subOps.clear();if (transactionSize > ops.size()) {transactionSize = ops.size();}subOps.addAll(ops.subList(0, transactionSize));try {getContentResolver().applyBatch(ContactsContract.AUTHORITY, subOps);ops.removeAll(subOps);} catch (final TransactionTooLargeException e) {// If the transaction is too large, try splitting it.if (transactionSize == 1) {Log.e(TAG, "Single operation transaction too large");}Log.d(TAG, "Transaction operation count %d too large, halving..." + transactionSize);transactionSize = transactionSize / 2;if (transactionSize < 1) {transactionSize = 1;}} catch (final RemoteException e) {Log.e(TAG, "RemoteException in commit");e.printStackTrace();} catch (final OperationApplicationException e) {Log.e(TAG, "OperationApplicationException in commit");e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}ops.clear(); }恩,寫完很滿意,但是當ops被分成幾段,多次處理以后,只有第一次的插入是有效,后面的插入都是無效的了。
正確的批處理的插入姿勢
之所以ArrayList< ContentProviderOperation > ops被截斷 以后的插入,后面的插入都失敗了,是因為插入到id已經不對了,應該每次重新構建一個新的ContentProviderOperation列表。
所以正確的處理方式是:每次截取聯系人列表的一段,構建一個ContentProviderOperation列表,插入完成以后,取聯系人列表的下一段,再重新構建一個新的ContentProviderOperation列表,再次插入
所以正確的代碼是這樣的
總結
以上是生活随笔為你收集整理的Android中向ContactsProvider中插入大量联系人的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (转)pb控制打印机
- 下一篇: android sina oauth2.