Properly batch contact inserts.

Fixes #8580
This commit is contained in:
Greyson Parrelli 2019-02-06 00:36:55 -08:00
parent 1e0f691a56
commit b769c7d9b6
4 changed files with 110 additions and 17 deletions

View File

@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.contacts;
import android.accounts.Account; import android.accounts.Account;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.ContentProviderOperation; import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.OperationApplicationException; import android.content.OperationApplicationException;
import android.database.Cursor; import android.database.Cursor;
@ -101,13 +102,13 @@ public class ContactsDatabase {
boolean remove) boolean remove)
throws RemoteException, OperationApplicationException throws RemoteException, OperationApplicationException
{ {
Set<Address> registeredAddressSet = new HashSet<>(); Set<Address> registeredAddressSet = new HashSet<>(registeredAddressList);
ArrayList<ContentProviderOperation> operations = new ArrayList<>(); ArrayList<ContentProviderOperation> operations = new ArrayList<>();
Map<Address, SignalContact> currentContacts = getSignalRawContacts(account); Map<Address, SignalContact> currentContacts = getSignalRawContacts(account);
List<List<Address>> registeredChunks = Util.chunk(registeredAddressList, 50);
for (Address registeredAddress : registeredAddressList) { for (List<Address> registeredChunk : registeredChunks) {
registeredAddressSet.add(registeredAddress); for (Address registeredAddress : registeredChunk) {
if (!currentContacts.containsKey(registeredAddress)) { if (!currentContacts.containsKey(registeredAddress)) {
Optional<SystemContactInfo> systemContactInfo = getSystemContactInfo(registeredAddress); Optional<SystemContactInfo> systemContactInfo = getSystemContactInfo(registeredAddress);
@ -118,6 +119,11 @@ public class ContactsDatabase {
} }
} }
} }
if (!operations.isEmpty()) {
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
operations.clear();
}
}
for (Map.Entry<Address, SignalContact> currentContactEntry : currentContacts.entrySet()) { for (Map.Entry<Address, SignalContact> currentContactEntry : currentContacts.entrySet()) {
if (!registeredAddressSet.contains(currentContactEntry.getKey())) { if (!registeredAddressSet.contains(currentContactEntry.getKey())) {
@ -137,7 +143,7 @@ public class ContactsDatabase {
} }
if (!operations.isEmpty()) { if (!operations.isEmpty()) {
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); applyOperationsInBatches(context.getContentResolver(), ContactsContract.AUTHORITY, operations, 50);
} }
} }
@ -532,6 +538,18 @@ public class ContactsDatabase {
} }
} }
private void applyOperationsInBatches(@NonNull ContentResolver contentResolver,
@NonNull String authority,
@NonNull List<ContentProviderOperation> operations,
int batchSize)
throws OperationApplicationException, RemoteException
{
List<List<ContentProviderOperation>> batches = Util.chunk(operations, batchSize);
for (List<ContentProviderOperation> batch : batches) {
contentResolver.applyBatch(authority, new ArrayList<>(batch));
}
}
private static class ProjectionMappingCursor extends CursorWrapper { private static class ProjectionMappingCursor extends CursorWrapper {
private final Map<String, String> projectionMap; private final Map<String, String> projectionMap;

View File

@ -218,7 +218,7 @@ public class DirectoryHelper {
} }
} }
} catch (RemoteException | OperationApplicationException e) { } catch (RemoteException | OperationApplicationException e) {
Log.w(TAG, e); Log.w(TAG, "Failed to update contacts.", e);
} }
} }
} }

View File

@ -63,6 +63,7 @@ import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -77,7 +78,7 @@ import java.util.concurrent.TimeUnit;
public class Util { public class Util {
private static final String TAG = Util.class.getSimpleName(); private static final String TAG = Util.class.getSimpleName();
public static Handler handler = new Handler(Looper.getMainLooper()); private static volatile Handler handler;
public static <T> List<T> asList(T... elements) { public static <T> List<T> asList(T... elements) {
List<T> result = new LinkedList<>(); List<T> result = new LinkedList<>();
@ -141,6 +142,17 @@ public class Util {
return map.containsKey(key) ? map.get(key) : defaultValue; return map.containsKey(key) ? map.get(key) : defaultValue;
} }
public static <E> List<List<E>> chunk(@NonNull List<E> list, int chunkSize) {
List<List<E>> chunks = new ArrayList<>(list.size() / chunkSize);
for (int i = 0; i < list.size(); i += chunkSize) {
List<E> chunk = list.subList(i, Math.min(list.size(), i + chunkSize));
chunks.add(chunk);
}
return chunks;
}
public static CharSequence getBoldedString(String value) { public static CharSequence getBoldedString(String value) {
SpannableString spanned = new SpannableString(value); SpannableString spanned = new SpannableString(value);
spanned.setSpan(new StyleSpan(Typeface.BOLD), 0, spanned.setSpan(new StyleSpan(Typeface.BOLD), 0,
@ -394,20 +406,20 @@ public class Util {
} }
public static void postToMain(final @NonNull Runnable runnable) { public static void postToMain(final @NonNull Runnable runnable) {
handler.post(runnable); getHandler().post(runnable);
} }
public static void runOnMain(final @NonNull Runnable runnable) { public static void runOnMain(final @NonNull Runnable runnable) {
if (isMainThread()) runnable.run(); if (isMainThread()) runnable.run();
else handler.post(runnable); else getHandler().post(runnable);
} }
public static void runOnMainDelayed(final @NonNull Runnable runnable, long delayMillis) { public static void runOnMainDelayed(final @NonNull Runnable runnable, long delayMillis) {
handler.postDelayed(runnable, delayMillis); getHandler().postDelayed(runnable, delayMillis);
} }
public static void cancelRunnableOnMain(@NonNull Runnable runnable) { public static void cancelRunnableOnMain(@NonNull Runnable runnable) {
handler.removeCallbacks(runnable); getHandler().removeCallbacks(runnable);
} }
public static void runOnMainSync(final @NonNull Runnable runnable) { public static void runOnMainSync(final @NonNull Runnable runnable) {
@ -510,4 +522,15 @@ public class Util {
return new DecimalFormat("#,##0.#").format(sizeBytes/Math.pow(1024, digitGroups)) + " " + units[digitGroups]; return new DecimalFormat("#,##0.#").format(sizeBytes/Math.pow(1024, digitGroups)) + " " + units[digitGroups];
} }
private static Handler getHandler() {
if (handler == null) {
synchronized (Util.class) {
if (handler == null) {
handler = new Handler(Looper.getMainLooper());
}
}
}
return handler;
}
} }

View File

@ -0,0 +1,52 @@
package org.thoughtcrime.securesms.util;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class UtilTest {
@Test
public void chunk_oneChunk() {
List<String> input = Arrays.asList("A", "B", "C");
List<List<String>> output = Util.chunk(input, 3);
assertEquals(1, output.size());
assertEquals(input, output.get(0));
output = Util.chunk(input, 4);
assertEquals(1, output.size());
assertEquals(input, output.get(0));
output = Util.chunk(input, 100);
assertEquals(1, output.size());
assertEquals(input, output.get(0));
}
@Test
public void chunk_multipleChunks() {
List<String> input = Arrays.asList("A", "B", "C", "D", "E");
List<List<String>> output = Util.chunk(input, 4);
assertEquals(2, output.size());
assertEquals(Arrays.asList("A", "B", "C", "D"), output.get(0));
assertEquals(Arrays.asList("E"), output.get(1));
output = Util.chunk(input, 2);
assertEquals(3, output.size());
assertEquals(Arrays.asList("A", "B"), output.get(0));
assertEquals(Arrays.asList("C", "D"), output.get(1));
assertEquals(Arrays.asList("E"), output.get(2));
output = Util.chunk(input, 1);
assertEquals(5, output.size());
assertEquals(Arrays.asList("A"), output.get(0));
assertEquals(Arrays.asList("B"), output.get(1));
assertEquals(Arrays.asList("C"), output.get(2));
assertEquals(Arrays.asList("D"), output.get(3));
assertEquals(Arrays.asList("E"), output.get(4));
}
}