android - ApplyBatch का उपयोग करके हजारों संपर्क प्रविष्टियों का सम्मिलन धीमा है




android-contentprovider android-contentresolver (5)

मैं एक एप्लिकेशन विकसित कर रहा हूं जहां मुझे बहुत सारी संपर्क प्रविष्टियां सम्मिलित करने की आवश्यकता है। वर्तमान समय में कुल मिलाकर 6000 फोन नंबरों के साथ 600 संपर्क। सबसे बड़े संपर्क में 1800 फोन नंबर हैं।

आज की स्थिति यह है कि मैंने संपर्कों को रखने के लिए एक कस्टम खाता बनाया है, इसलिए उपयोगकर्ता संपर्क दृश्य में संपर्क देखने का चयन कर सकता है।

लेकिन संपर्कों का सम्मिलन दर्दनाक रूप से धीमा है। मैं ContentResolver.applyBatch का उपयोग करके संपर्क सम्मिलित करता हूं। मैंने ContentProviderOperation सूची (100, 200, 400) के विभिन्न आकारों के साथ प्रयास किया है, लेकिन कुल चलने का समय लगभग है। वही। सभी संपर्कों और संख्याओं को सम्मिलित करने के लिए लगभग 30 मिनट लगते हैं!

SQlite में धीमे सम्मिलन के संबंध में मैंने पाया कि अधिकांश मुद्दे लेन-देन को बढ़ाते हैं। लेकिन जब से मैं ContentResolver.applyBatch- विधि का उपयोग करता हूं, तो मैं इसे नियंत्रित नहीं करता हूं, और मैं यह मानूंगा कि ContentResolver मेरे लिए लेनदेन प्रबंधन का ख्याल रखता है।

तो, मेरे प्रश्न के लिए: क्या मैं कुछ गलत कर रहा हूं, या क्या ऐसा कुछ है जो मैं इसे तेज कर सकता हूं?

ऐन्डर्स

संपादित करें: @jcwenger: ओह, मैं देख रहा हूं। अच्छे खर्च!

तो फिर मुझे पहले रॉ_ कॉन्टैक्ट्स टेबल में डालना होगा, और फिर नाम और नंबरों के साथ डेटाटेबल। मैं जो खोता हूं वह रॉ_ड का बैक रेफरेंस है जिसे मैं अप्लाईबेक में उपयोग करता हूं।

तो मुझे डेटा तालिका में विदेशी कुंजी के रूप में उपयोग करने के लिए सभी नए डाले गए रॉ_ कॉन्टैक्ट्स पंक्तियों की आईडी प्राप्त करनी होगी?


@jcwenger सबसे पहले, आपकी पोस्ट को पढ़ने के बाद, मुझे लगता है कि बल्कइन्टर का कारण ApplyBatch की तुलना में तेज है, लेकिन संपर्क प्रदाता के कोड को पढ़ने के बाद, मुझे ऐसा नहीं लगता। 1. आपने कहा कि ApplyBatch लेनदेन का उपयोग करें, हाँ, लेकिन बल्कइन्टर भी लेनदेन का उपयोग करते हैं। यहाँ इसका कोड है:

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

कहने का तात्पर्य यह है कि, बल्कइन्टर ट्रांसक्शन का भी उपयोग करते हैं। इसलिए मुझे नहीं लगता कि इसका कारण है। 2. आपने कहा कि थोक व्यापारी एक ही तालिका में मानों का एक पूरा ढेर लागू करता है। मुझे खेद है कि मैं संबंधित कोड froyo के स्रोत कोड में नहीं पा सकता हूं। और मैं जानना चाहता हूं कि आप इसे कैसे ढूंढ सकते हैं? क्या आप मुझे बता सकते हैं?

मुझे लगता है कि कारण है:

बल्क इनवर्टर mDb.yieldIfContendedSafely () का उपयोग करते हैं, जबकि लागू होते हैं mDb.yieldIfContendedSafely (SLEEP_AFTER_YIELD_DELAY) / * SLEEP_AFTER_YIELD_DELAY = 4000 * / का उपयोग करें

SQLiteDatabase.java के कोड को पढ़ने के बाद, मुझे पता चलता है कि अगर यील्ड में एक समय निर्धारित किया जाए, तो यह सो जाएगा, लेकिन यदि आप समय निर्धारित नहीं करते हैं, तो यह नहीं सोएगा। आप नीचे दिए गए कोड का उल्लेख कर सकते हैं 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;
}

मुझे लगता है कि बल्कइंटरेंट का कारण अप्लायबैक से तेज है।

किसी भी प्रश्न कृपया मुझसे संपर्क करें।


बल्क इनरवेयर: रुचि रखने वालों के लिए, यहां वह कोड है जिसे मैं प्रयोग करने में सक्षम था। ध्यान दें कि हम int / long / floats के लिए कुछ आवंटन से कैसे बच सकते हैं :) इससे अधिक समय बच सकता है।

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

मुझे आपके लिए मूल समाधान मिलता है, बैच ऑपरेशन में "उपज अंक" का उपयोग करें।

बैच किए गए संचालन का उपयोग करने का दूसरा पक्ष यह है कि एक बड़ा बैच डेटाबेस को लंबे समय तक अन्य एप्लिकेशन को डेटा तक पहुंचने से रोक सकता है और संभावित रूप से ANRs ("एप्लिकेशन नॉट रिस्पॉन्सिंग") डायलॉग के कारण हो सकता है।

डेटाबेस के ऐसे लॉकअप से बचने के लिए, बैच में " उपज अंक " डालना सुनिश्चित करें। एक उपज बिंदु सामग्री प्रदाता को इंगित करता है कि अगले ऑपरेशन को निष्पादित करने से पहले यह उन परिवर्तनों को कर सकता है जो पहले से किए गए हैं, अन्य अनुरोधों के लिए उपज, एक और लेनदेन खोलें और प्रसंस्करण संचालन जारी रखें।

एक उपज बिंदु स्वचालित रूप से लेनदेन नहीं करेगा, लेकिन केवल तभी जब डेटाबेस पर एक और अनुरोध हो। आम तौर पर एक सिंक एडाप्टर को बैच में प्रत्येक कच्चे संपर्क ऑपरेशन अनुक्रम की शुरुआत में एक उपज बिंदु डालना चाहिए। withYieldAllowed(boolean) देखें।

मुझे आशा है कि यह आपके लिए उपयोगी हो सकता है।


यहां 30 सेकंड के भीतर समान डेटा राशि सम्मिलित करने का उदाहरण दिया गया है।

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

लॉग: 02-17 12: 48: 45.496 2073-2090 / com.vayosoft.mlab D / BatchInsertionTest insert बैच सम्मिलन शुरू करने पर: बुध 17 फरवरी 12:48:45 GMT + 02: 00: 02-17 12:49: 16.446 2073-2090 / com.vayosoft.mlab D / BatchInsertionTest ﹕ बैच सम्मिलन का अंत, समाप्त: 00: 30.951


bulkInsert() को ओवरराइड करने के तरीके का एक उदाहरण, मल्टीपल इन्सर्ट को गति देने के लिए, here पाया जा सकता here





android-contentresolver