que - update content provider android
La inserciĆ³n de miles de entradas de contactos con applyBatch es lenta (7)
@jcwenger Al principio, después de leer su publicación, creo que esa es la razón de que bulkInsert sea más rápido que ApplyBatch, pero después de leer el código de Contact Provider, no lo creo. 1.Dijo que las transacciones de uso de ApplyBatch sí, pero bulkInsert también usa transacciones. Aquí está el código:
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 usa transacciones. Así que no creo que esa sea la razón. 2.Dijiste que bulkInsert aplica una pila completa de valores a la misma tabla. Lamento no poder encontrar el código relacionado en el código fuente de froyo. Y quiero saber cómo podrías encontrarlo. ¿Podrías decirme?
La razón por la que pienso es eso:
bulkInsert use mDb.yieldIfContendedSafely () while applyBatch use mDb.yieldIfContendedSafely (SLEEP_AFTER_YIELD_DELAY) / * SLEEP_AFTER_YIELD_DELAY = 4000 * /
después de leer el código de SQLiteDatabase.java, me parece que, si se establece un tiempo en yieldIfContendedSafely, se dormirá, pero si no se establece la hora, no se dormirá. Puede consultar el siguiente código que es un fragmento 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 que bulkInsert sea más rápido que applyBatch.
Cualquier pregunta por favor contáctame.
Estoy desarrollando una aplicación en la que necesito insertar muchas entradas de contactos. En el momento 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 guardar los contactos, por lo que el usuario puede seleccionar ver el contacto en la vista de 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 total de ejecución es de aprox. lo mismo. ¡Insertar todos los contactos y números toma alrededor de 30 minutos!
La mayoría de los problemas que he encontrado con respecto a la inserción lenta en SQlite muestran las transacciones. Pero como utilizo ContentResolver.applyBatch-method, no controlo esto, y supongo que ContentResolver se ocupa de la gestión de transacciones por mí.
Entonces, a mi pregunta: ¿estoy haciendo algo mal, o hay algo que pueda hacer para acelerar esto?
Anders
Editar: @jcwenger: Oh, ya veo. ¡Buena explicación!
Entonces, primero tendré que insertar en la tabla raw_contacts y luego en la datatable con el nombre y los números. Lo que perderé es la referencia anterior al raw_id que uso en applyBatch.
¿Entonces tendré que obtener todas las identificaciones de las filas raw_contacts recientemente insertadas para usarlas como claves externas en la tabla de datos?
Aquí hay un ejemplo de cómo insertar la misma cantidad de datos en 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: Inicio de 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: fin de la inserción del lote, transcurrido: 00: 30.951
Obtengo la solución básica para usted, use "puntos de rendimiento" en la operación por lotes .
La otra cara del uso de las operaciones por lotes es que un lote grande puede bloquear la base de datos durante mucho tiempo, lo que impide que otras aplicaciones accedan a los datos y provoquen ANR (cuadros de diálogo de "Aplicación no responde").
Para evitar dichos bloqueos de la base de datos, asegúrese de insertar " puntos de rendimiento " en el lote. Un límite de rendimiento indica al proveedor de contenido que antes de ejecutar la siguiente operación puede confirmar 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 confirmará automáticamente la transacción, sino solo si hay otra solicitud esperando en la base de datos. Normalmente, un adaptador de sincronización debe insertar un punto de fluencia al comienzo de cada secuencia de operación de contacto sin procesar en el lote. Consulte withYieldAllowed(boolean) .
Espero que te sea útil.
Solo para la información de los lectores de este hilo.
Estaba enfrentando problemas de rendimiento incluso si usaba applyBatch (). En mi caso, hubo desencadenantes de base de datos escritos en una de las tablas. Eliminé los factores desencadenantes de la tabla y su auge. Ahora mi aplicación inserta filas con bendición de velocidad rápida.
Un ejemplo de cómo anular el bulkInsert()
, para acelerar la inserción de múltiplos, se puede encontrar here
Utilice 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 bloqueo / desbloqueo una vez por operación. debido a esto, es un poco más rápido que hacerlo de uno en uno (sin lotes).
Sin embargo, dado que cada Operación en el Lote puede tener un URI diferente, y así sucesivamente, hay una gran cantidad de sobrecarga. "¡Oh, una nueva operación! Me pregunto a qué mesa va ... Aquí, insertaré una sola fila ... ¡Oh, una nueva operación! Me pregunto a qué mesa va ..." ad infinitium. Dado que la mayor parte del trabajo de convertir los URI en tablas implica muchas comparaciones de cadenas, obviamente es muy lento.
Por el contrario, bulkInsert aplica una pila completa de valores a la misma tabla. Va, "Inserción masiva ... encuentra la tabla, está bien, inserta! Inserta! Inserta! Inserta! Inserta!" Mucho mas rápido.
Por supuesto, requerirá que ContentResolver implemente bulkInsert de manera eficiente. La mayoría lo hace, a menos que lo haya escrito usted mismo, en cuyo caso le llevará un poco de codificación.
bulkInsert: para los interesados, este es 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;
}