La inserción de miles de entradas de contacto mediante applyBatch es lenta

Estoy desarrollando una aplicación en la que necesito insertar muchas entradas de contacto. En la fecha actual aproximadamente 600 contactos con un total de 6000 números de teléfono. El contacto más grande tiene 1800 números de teléfono.

El estado actual es que he creado una cuenta personalizada para contener los contactos, por lo que el usuario puede seleccionar ver el contacto en la vista Contactos.

Pero la inserción de los contactos es dolorosamente lenta. Inserto los contactos usando ContentResolver.applyBatch. He intentado con diferentes tamaños de la lista ContentProviderOperation (100, 200, 400), pero el tiempo de ejecución total es de aprox. lo mismo. Para insertar todos los contactos y números se tarda unos 30 minutos!

La mayoría de los problemas que he encontrado con respecto a la inserción lenta en SQlite trae a colación transacciones. Pero como uso el método ContentResolver.applyBatch, no controlo esto, y asumiría que ContentResolver se ocupa de la administración de transacciones para mí.

Así que, a mi pregunta: ¿Estoy haciendo algo mal, o hay algo que pueda hacer para acelerar esto?

Anders

Edit: @ Jcwenger: Oh, ya veo. ¡Buena explicación!

Así que tendré que insertar primero en la tabla raw_contacts y, a continuación, la tabla de datos con el nombre y los números. Lo que voy a perder es la referencia a la raw_id que utilizo en el applyBatch.

Así que voy a tener que obtener todas las id's de las nuevas líneas raw_contacts insertadas para usar como claves foráneas en la tabla de datos?

Utilice ContentResolver.bulkInsert (Uri url, ContentValues[] values) ApplyBatch() ContentResolver.bulkInsert (Uri url, ContentValues[] values) lugar de ApplyBatch()

ApplyBatch (1) utiliza transacciones y (2) bloquea el ContentProvider una vez para todo el lote en lugar de bloquear / desbloquear una vez por operación. Debido a esto, es ligeramente más rápido que hacerlo uno a la vez (no batch).

Sin embargo, puesto que cada operación en el lote puede tener un URI diferente y así sucesivamente, hay una cantidad enorme de sobrecarga. "Oh, una nueva operación, me pregunto qué mesa va a entrar … Aquí, voy a insertar una sola fila … Oh, una nueva operación, me pregunto qué mesa va en …" ad infinitium. Dado que la mayoría del trabajo de convertir URIs en tablas implica muchas comparaciones de cadenas, obviamente es muy lento.

Por el contrario, bulkInsert aplica una pila entera de valores a la misma tabla. Va, "Bulk insertar … encontrar la mesa, bien, insertar! Insertar! Insertar! Insertar! Insertar!" Mucho mas rápido.

Por supuesto, requerirá que su ContentResolver implemente eficientemente bulkInsert. La mayoría lo hace, a menos que lo escribió usted mismo, en cuyo caso tomará un poco de codificación.

BulkInsert: Para los interesados, aquí está el código con el que pude experimentar. Preste atención a cómo podemos evitar algunas asignaciones para int / long / floats 🙂 esto podría ahorrar más tiempo.

 private int doBulkInsertOptimised(Uri uri, ContentValues values[]) { long startTime = System.currentTimeMillis(); long endTime = 0; //TimingInfo timingInfo = new TimingInfo(startTime); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); DatabaseUtils.InsertHelper inserter = new DatabaseUtils.InsertHelper(db, Tables.GUYS); // Get the numeric indexes for each of the columns that we're updating final int guiStrColumn = inserter.getColumnIndex(Guys.STRINGCOLUMNTYPE); final int guyDoubleColumn = inserter.getColumnIndex(Guys.DOUBLECOLUMNTYPE); //... final int guyIntColumn = inserter.getColumnIndex(Guys.INTEGERCOLUMUNTYPE); db.beginTransaction(); int numInserted = 0; try { int len = values.length; for (int i = 0; i < len; i++) { inserter.prepareForInsert(); String guyID = (String)(values[i].get(Guys.GUY_ID)); inserter.bind(guiStrColumn, guyID); // convert to double ourselves to save an allocation. double d = ((Number)(values[i].get(Guys.DOUBLECOLUMNTYPE))).doubleValue(); inserter.bind(guyDoubleColumn, lat); // getting the raw Object and converting it int ourselves saves // an allocation (the alternative is ContentValues.getAsInt, which // returns a Integer object) int status = ((Number) values[i].get(Guys.INTEGERCOLUMUNTYPE)).intValue(); inserter.bind(guyIntColumn, status); inserter.execute(); } numInserted = len; db.setTransactionSuccessful(); } finally { db.endTransaction(); inserter.close(); endTime = System.currentTimeMillis(); if (LOGV) { long timeTaken = (endTime - startTime); Log.v(TAG, "Time taken to insert " + values.length + " records was " + timeTaken + " milliseconds " + " or " + (timeTaken/1000) + "seconds"); } } getContext().getContentResolver().notifyChange(uri, null); return numInserted; } 

Un ejemplo de cómo sobreescribir el bulkInsert() , con el fin de acelerar múltiples insertar, se puede encontrar aquí

@jcwenger Al principio, después de leer su mensaje, creo que esa es la razón de bulkInsert es más rápido que ApplyBatch, pero después de leer el código de Contact Provider, no lo creo. 1.Usted dijo que ApplyBatch utiliza transacciones, sí, pero bulkInsert también usa transacciones. Aquí está el código de la misma:

 public int bulkInsert(Uri uri, ContentValues[] values) { int numValues = values.length; mDb = mOpenHelper.getWritableDatabase(); mDb.beginTransactionWithListener(this); try { for (int i = 0; i < numValues; i++) { Uri result = insertInTransaction(uri, values[i]); if (result != null) { mNotifyChange = true; } mDb.yieldIfContendedSafely(); } mDb.setTransactionSuccessful(); } finally { mDb.endTransaction(); } onEndTransaction(); return numValues; } 

Es decir, bulkInsert también utilizan transations.So no creo que esa es la razón. 2.You dijo bulkInsert aplica una pila entera de valores a la misma tabla.Lo siento no puedo encontrar código relacionado en el código fuente de froyo.And quiero saber cómo podría encontrar eso? ¿Podría decirme?

La razón por la que pienso es que:

BulkInsert use mDb.yieldIfContendedSafely () mientras se aplicaBatch use mDb.yieldIfContendedSafely (SLEEP_AFTER_YIELD_DELAY) / * SLEEP_AFTER_YIELD_DELAY = 4000 * /

Después de leer el código de SQLiteDatabase.java, encuentro que, si se establece un tiempo en yieldIfContendedSafely, se hará un sueño, pero si no se fija la hora, no sleep.You puede referirse al código de abajo que es Un pedazo de código de SQLiteDatabase.java

 private boolean yieldIfContendedHelper(boolean checkFullyYielded, long sleepAfterYieldDelay) { if (mLock.getQueueLength() == 0) { // Reset the lock acquire time since we know that the thread was willing to yield // the lock at this time. mLockAcquiredWallTime = SystemClock.elapsedRealtime(); mLockAcquiredThreadTime = Debug.threadCpuTimeNanos(); return false; } setTransactionSuccessful(); SQLiteTransactionListener transactionListener = mTransactionListener; endTransaction(); if (checkFullyYielded) { if (this.isDbLockedByCurrentThread()) { throw new IllegalStateException( "Db locked more than once. yielfIfContended cannot yield"); } } if (sleepAfterYieldDelay > 0) { // Sleep for up to sleepAfterYieldDelay milliseconds, waking up periodically to // check if anyone is using the database. If the database is not contended, // retake the lock and return. long remainingDelay = sleepAfterYieldDelay; while (remainingDelay > 0) { try { Thread.sleep(remainingDelay < SLEEP_AFTER_YIELD_QUANTUM ? remainingDelay : SLEEP_AFTER_YIELD_QUANTUM); } catch (InterruptedException e) { Thread.interrupted(); } remainingDelay -= SLEEP_AFTER_YIELD_QUANTUM; if (mLock.getQueueLength() == 0) { break; } } } beginTransactionWithListener(transactionListener); return true; } 

Creo que esa es la razón de bulkInsert es más rápido que applyBatch.

Cualquier pregunta me entra en contacto con por favor.

Obtengo la solución básica para usted, use "yield points" en la operación por lotes .

La otra cara de la utilización de las operaciones por lotes es que un lote grande puede bloquear la base de datos durante un largo tiempo, evitando que otras aplicaciones accedan a datos y potencialmente causan ANRs (los cuadros de diálogo "Aplicación no responde").

Para evitar estos bloqueos de la base de datos, asegúrese de insertar " puntos de límite de rendimiento " en el lote. Un límite de rendimiento indica al proveedor de contenido que antes de ejecutar la siguiente operación puede comprometer los cambios que ya se han realizado, ceder a otras solicitudes, abrir otra transacción y continuar las operaciones de procesamiento.

Un límite de rendimiento no compromete automáticamente la transacción, pero sólo si hay otra solicitud en espera en la base de datos. Normalmente, un adaptador de sincronización debe insertar un límite de fluencia al principio de cada secuencia de operación de contacto sin procesar en el lote. Ver withYieldAllowed (booleano) .

Espero que sea útil para usted.

Aquí es un ejemplo de insertar la misma cantidad de datos dentro de 30 segundos.

  public void testBatchInsertion() throws RemoteException, OperationApplicationException { final SimpleDateFormat FORMATTER = new SimpleDateFormat("mm:ss.SSS"); long startTime = System.currentTimeMillis(); Log.d("BatchInsertionTest", "Starting batch insertion on: " + new Date(startTime)); final int MAX_OPERATIONS_FOR_INSERTION = 200; ArrayList<ContentProviderOperation> ops = new ArrayList<>(); for(int i = 0; i < 600; i++){ generateSampleProviderOperation(ops); if(ops.size() >= MAX_OPERATIONS_FOR_INSERTION){ getContext().getContentResolver().applyBatch(ContactsContract.AUTHORITY,ops); ops.clear(); } } if(ops.size() > 0) getContext().getContentResolver().applyBatch(ContactsContract.AUTHORITY,ops); Log.d("BatchInsertionTest", "End of batch insertion, elapsed: " + FORMATTER.format(new Date(System.currentTimeMillis() - startTime))); } private void generateSampleProviderOperation(ArrayList<ContentProviderOperation> ops){ int backReference = ops.size(); ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null) .withValue(ContactsContract.RawContacts.AGGREGATION_MODE, ContactsContract.RawContacts.AGGREGATION_MODE_DISABLED) .build() ); ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, backReference) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, "GIVEN_NAME " + (backReference + 1)) .withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, "FAMILY_NAME") .build() ); for(int i = 0; i < 10; i++) ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, backReference) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MAIN) .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, Integer.toString((backReference + 1) * 10 + i)) .build() ); } 

El registro: 02-17 12: 48: 45.496 2073-2090 / com.vayosoft.mlab D / BatchInsertionTest: Iniciando la inserción del lote el: Miércoles 17 de febrero 12:48:45 GMT + 02: 00 2016 02-17 12:49: 16.446 2073-2090 / com.vayosoft.mlab D / BatchInsertionTest: Finalización de la inserción del lote, transcurrido: 00: 30.951

Sólo para la información de los lectores de este hilo.

Yo estaba enfrentando el problema de rendimiento, incluso si se utiliza applyBatch (). En mi caso había desencadenadores de base de datos escritos en uno de la tabla. He eliminado los disparadores de la mesa y su boom. Ahora mi aplicación insertar filas con la bendición de velocidad rápida.

  • Cuáles son uri, contentValues
  • Cómo cargar una imagen de Picasa desde URI?
  • Una respuesta final sobre cómo obtener datos Exif de URI
  • Mostrar los contactos en orden de clasificación ContactsContract.Contacts de Content Resolver
  • Android - forma rápida de obtener imágenes y carpetas ocultas
  • Android Gallery en Android 4.4 (KitKat) devuelve un URI diferente para Intent.ACTION_GET_CONTENT
  • Telephony.Sms.Inbox.PERSON utiliza Contacts.People._ID obsoletos
  • Cómo actualizar una foto de contacto nativa en android?
  • Eliminar un archivo de contentResolver sólo eliminar la entrada de la base de datos (no archivo)
  • Coloque varios parámetros en ContentResolver.requestSync
  • Android: transacciones SQLite al usar ContentResolver
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.