L'insertion de milliers d'entrées de contact à l'aide de applyBatch est lente

Je développe une application où j'ai besoin d'insérer beaucoup d'entrées de contact. À l'heure actuelle, environ 600 contacts avec un total de 6000 numéros de téléphone. Le plus grand contact a 1800 numéros de téléphone.

L'état à partir d'aujourd'hui est que j'ai créé un Compte personnalisé pour contenir les Contacts, de sorte que l'utilisateur peut choisir de voir le contact dans la vue Contacts.

  • Devrais-je appeler super () lors du remplacement du constructeur AsyncTask?
  • Changement d'activité avec la glissade du doigt
  • Des requêtes avec des instructions préparées dans Android?
  • Android Transparent TextView?
  • Android Google Maps API v2 - comment changer l'icône du marqueur
  • Glisser l'animation xml sur l'activité de modification dans Android
  • Mais l'insertion des contacts est douloureusement lente. J'insère les contacts en utilisant ContentResolver.applyBatch. J'ai essayé avec différentes tailles de la liste ContentProviderOperation (100, 200, 400), mais le temps de fonctionnement total est d'env. le même. Pour insérer tous les contacts et les numéros, il faut environ 30 minutes!

    La plupart des problèmes que j'ai trouvés concernant l'insertion lente dans SQlite font apparaître les transactions. Mais puisque j'utilise la méthode ContentResolver.applyBatch, je ne contrôle pas cela, et je suppose que ContentResolver prend soin de la gestion des transactions pour moi.

    Donc, à ma question: Est-ce que je fais quelque chose de mal, ou est-ce que je peux faire pour accélérer cela?

    Anders

    Edit: @jcwenger: Oh, je vois. Bonne explication!

    Ensuite, je devrai d'abord insérer dans la table raw_contacts, puis la base de données avec le nom et les chiffres. Ce que je vais perdre, c'est la référence de retour au raw_id que j'utilise dans applybatch.

    Donc, je devrais obtenir tous les identifiants des lignes raw_contacts nouvellement insérées à utiliser comme clés étrangères dans la table de données?

  • Android Paint setShadowLayer () ignore la couleur de sa peinture
  • Facebook Sdk n'a pas été intégré
  • Carte bitmap Android
  • Comment récupérer l'ID xml d'EditText
  • Je reçois cette erreur: les données dépassent UNCOMPRESS_DATA_MAX sur Android 2.2 mais pas sur 2.3
  • Android - fragment .replace () ne remplace pas le contenu - le place sur le dessus
  • 7 Solutions collect form web for “L'insertion de milliers d'entrées de contact à l'aide de applyBatch est lente”

    Utilisez ContentResolver.bulkInsert (Uri url, ContentValues[] values) au lieu de ApplyBatch()

    ApplyBatch (1) utilise des transactions et (2) verrouille le ContentProvider une fois pour le lot entier à la place de verrouillage / déverrouillage une fois par opération. En raison de cela, il est légèrement plus rapide que de les faire un à la fois (non-groupé).

    Cependant, comme chaque opération dans le lot peut avoir un URI différent et ainsi de suite, il y a énormément de frais généraux. "Oh, une nouvelle opération! Je me demande dans quelle table ça va … Ici, je vais insérer une seule rangée … Oh, une nouvelle opération! Je me demande dans quelle table ça se passe …" ad infinitium. Étant donné que la plupart des travaux consistant à transformer les URI en tables impliquent de nombreuses comparaisons de chaînes, il est évidemment très lent.

    En revanche, bulkInsert applique une pile entière de valeurs à la même table. Il va, "Insérer en vrac … trouver la table, ok, insérer! Insérer! Insérer! Insérer! Insérer!" Plus vite.

    Bien entendu, il faudra que votre ContentResolver implémente le système bulkInsert efficacement. La plupart font, sauf si vous l'avez écrit vous-même, auquel cas il faudra un peu de codage.

    BulkInsert: pour ceux qui s'intéressent, voici le code avec lequel j'ai pu expérimenter. Faites attention à la façon dont nous pouvons éviter certaines allocations pour int / long / floats 🙂 cela pourrait gagner plus de temps.

     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 exemple de la façon de remplacer le bulkInsert() , afin d'accélérer l'insertion multiple, peut être trouvé ici

    @jcwenger Au début, après avoir lu votre publication, je pense que c'est la raison du fait que bulkInsert est plus rapide que ApplyBatch, mais après avoir lu le code de Contact Provider, je ne le pense pas. 1.Vous avez déclaré que ApplyBatch utilise des transactions, oui, mais bulkInsert utilise également les transactions. Voici le code de celui-ci:

     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; } 

    C'est-à-dire, bulkInsert utilise également des transitions. Donc, je ne pense pas que ce soit la raison. 2. Vous avez dit que bulkInsert applique une pile entière de valeurs à la même table. Je suis désolé de ne pas trouver le code associé dans le code source de froyo. Et je veux savoir comment pourriez-vous trouver cela? Pourriez-vous me le dire?

    La raison pour laquelle je pense est que:

    BulkInsert utilise mDb.yieldIfContendedSafely () tandis que applyBatch utilise mDb.yieldIfContendedSafely (SLEEP_AFTER_YIELD_DELAY) / * SLEEP_AFTER_YIELD_DELAY = 4000 * /

    Après avoir lu le code de SQLiteDatabase.java, je trouve que, si on définit un temps de yieldIfContendedSafely, cela va durer, mais si vous ne définissez pas l'heure, il ne dormira pas. Vous pouvez vous référer au code ci-dessous qui est Un code 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; } 

    Je pense que c'est la raison pour laquelle bulkInsert est plus rapide que applyBatch.

    Toute question, s'il vous plaît, contactez-moi.

    Je reçois la solution de base pour vous, utilisez des "points de rendement" dans le fonctionnement par lots .

    Le revers de l'utilisation des opérations en lots est qu'un grand lot peut bloquer la base de données pendant longtemps empêchant d'autres applications d'accéder aux données et provoquant potentiellement des ANR (boîtes de dialogue "Application Not Responding").

    Pour éviter de tels blocages de la base de données, assurez-vous d'insérer des " points de rendement " dans le lot. Un point de rendement indique au fournisseur de contenu que, avant d'exécuter la prochaine opération, il peut engager les modifications qui ont déjà été réalisées, céder à d'autres demandes, ouvrir une autre transaction et poursuivre les opérations de traitement.

    Un point de rendement n'engagera pas automatiquement la transaction, mais seulement si une autre requête est en attente sur la base de données. Normalement, un adaptateur de synchronisation devrait insérer un point d'écoulement au début de chaque séquence d'opération de contact brut dans le lot. Voir withYieldAllowed (boolean) .

    J'espère que cela peut vous être utile.

    Voici un exemple d'insertion de la même quantité de données dans les 30 secondes.

      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() ); } 

    Le journal: 02-17 12: 48: 45.496 2073-2090 / com.vayosoft.mlab D / BatchInsertionTest: Démarrage de l'insertion du lot le: mer Feb 17 12:48:45 GMT + 02: 00 2016 02-17 12:49: 16.446 2073-2090 / com.vayosoft.mlab D / BatchInsertionTest: fin de l'insertion du lot, écoulé: 00: 30.951

    Juste pour l'information des lecteurs de ce fil.

    Je faisais face à un problème de performance même si j'utilise applyBatch (). Dans mon cas, il y avait des déclencheurs de base de données écrits sur l'un des tableaux. J'ai supprimé les déclencheurs de la table et son boom. Maintenant, mon application insère des lignes avec une vitesse rapide de bénédiction.

    coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.