Как повысить производительность при запросе большого количества данных на ВСЕХ контактах из Android ContactsContract.Contacts? - PullRequest
0 голосов
/ 19 сентября 2019

OBJECTIVE

Я хочу получить для каждого контакта в телефоне пользователя следующие данные

StructuredName.GIVEN_NAME|Phone.NUMBER|Email.DATA|StructuredPostal.CITY

Я почти уверен, что запрашиваю таблицу ContactsContract.Data чисто SQL-запросомно нет четкой документации о том, как это сделать.Кажется, что вы можете внедрить SQL в contentResolver.query , но он не выглядит устойчивым.

ПРОБЛЕМА

Мой код в дальнейшем работает отлично, но очень медленно.

В основном,

  1. Я получаю идентификаторы ВСЕХ контактов из ContactsContract.Contacts и LOOP через него и для каждого,
  2. ВЫБЕРИ имяданные о CommonDataKinds.StructuredName,
  3. SELECT и LOOP телефонных данных на CommonDataKinds.Phone,
  4. SELECT и LOOP данных электронной почты на CommonDataKinds.Email,
  5. SELECT и LOOP адресные данные на CommonDataKinds.StructuredPostal

Однако многие циклы явно контрпродуктивны с точки зрения производительности.

С1000 контактов, делает около 3000 запросов.

КОД

// CREATE Content resolver
val resolver: ContentResolver = contentResolver
val cursor = resolver.query(
        ContactsContract.Contacts.CONTENT_URI,
        arrayOf(
                ContactsContract.Contacts._ID
        ),
        null,
        null,
        null
)

if ( cursor != null && cursor.count > 0) {
    // PROGRESSBAR Process
    myProgressBar?.progress = 0
    myProgressBarCircleText?.text = getString(R.string.processing_contacts)
    myProgressBar?.visibility = View.VISIBLE
    myProgressBarCircle?.visibility = View.VISIBLE
    myProgressBarCircleText?.visibility = View.VISIBLE



    // PUT BASIC REQUIRED INFO
    val jsonAllContacts = JSONObject()
    jsonAllContacts.put("source", "2")



    // EXECUTE CODE on another thread to prevent blocking UI
    Thread(Runnable {
        var cursorPosition = 0
        var currentProgress: Int


        Log.e("JSON", "cursor.count: ${cursor.count}")


        // CODE TO EXEC LOOP
        while (cursor.moveToNext()) {

            // Increment cursor for progressBar
            cursorPosition += 1
            currentProgress = ((cursorPosition.toFloat() / cursor.count.toFloat()) * 100).toInt()


            // INIT of jsonObjects
            val jsonEmail = JSONObject()
            val jsonPhone = JSONObject()
            val jsonAddress = JSONObject()
            val jsonCurrentContact = JSONObject()



            /**
             * NAME DETAILS
             */
            val contactID = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))



            val nameCur = contentResolver.query(
                    ContactsContract.Data.CONTENT_URI,
                    arrayOf(
                            ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
                            ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
                            ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME
                    ),
                    ContactsContract.Data.CONTACT_ID + " = ?" + " AND " + ContactsContract.Data.MIMETYPE + " = ?",
                    arrayOf(
                            contactID,
                            ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
                    ),
                    null
            )
            var givenName = ""
            var familyName: String
            var middleName: String
            var fullName = ""

            if ( nameCur != null ) {
                while (nameCur.moveToNext()) {

                    givenName = nameCur.getString(nameCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)) ?: ""
                    middleName = nameCur.getString(nameCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME)) ?: ""
                    familyName = nameCur.getString(nameCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)) ?: ""


                    fullName = if ( middleName != "" && (middleName != familyName) ) {
                        "$middleName $familyName"
                    } else {
                        familyName
                    }
                }

                jsonCurrentContact.put("given", givenName)
                jsonCurrentContact.put("family", fullName)
            }
            nameCur?.close()


            /**
             * PHONE NUMBER
             */
            val phoneCur = contentResolver.query(
                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                    arrayOf(
                            ContactsContract.CommonDataKinds.Phone.TYPE,
                            ContactsContract.CommonDataKinds.Phone.LABEL,
                            ContactsContract.CommonDataKinds.Phone.NUMBER
                    ),
                    ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?",
                    arrayOf( contactID ),
                    null
            )

            if ( phoneCur != null && phoneCur.count > 0 ) {
                while (phoneCur.moveToNext()) {
                    val phoneNumType = phoneCur.getString( phoneCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE) ) ?: ""
                    val phoneNumLabel = phoneCur.getString( phoneCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LABEL) ) ?: ""
                    var label: String
                    val phoneNumber = phoneCur.getString( phoneCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) ).replace(" ", "") ?: ""

                    //Log.e("JSON", "JSON phoneNum: $phoneNumLabel $phoneNumber")


                    // TRY to get label info
                    label = if ( phoneNumType == "" ) {
                        phoneNumLabel
                    } else {
                        phoneNumType
                    }

                    jsonPhone.put("label", label)
                    jsonPhone.put("number", phoneNumber)
                    jsonCurrentContact.accumulate("phone", jsonPhone)

                }


            }
            phoneCur?.close()


            /**
             * EMAIL
             */
            val emailCur = contentResolver.query(
                    ContactsContract.CommonDataKinds.Email.CONTENT_URI,
                    arrayOf(
                            ContactsContract.CommonDataKinds.Email.LABEL,
                            ContactsContract.CommonDataKinds.Email.DATA
                    ),
                    ContactsContract.CommonDataKinds.Email.CONTACT_ID + "=?",
                    arrayOf(contactID),
                    null
            )
            if ( emailCur != null ) {

                while (emailCur.moveToNext()) {
                    val emailLabel = emailCur.getString(emailCur.getColumnIndex(ContactsContract.CommonDataKinds.Email.LABEL)) ?: ""
                    val email = emailCur.getString(emailCur.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA)) ?: ""

                    jsonEmail.put("label", emailLabel)
                    jsonEmail.put("email", email)
                    jsonCurrentContact.accumulate("email", jsonEmail)

                }

            }
            emailCur?.close()




            /**
             * ADDRESS
             */
            var street: String
            var city: String
            var postalCode: String
            var state: String
            var country: String
            var label: String
            val addressCur = contentResolver.query(
                    ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI,
                    arrayOf(
                            ContactsContract.CommonDataKinds.StructuredPostal.TYPE,
                            ContactsContract.CommonDataKinds.StructuredPostal.STREET,
                            ContactsContract.CommonDataKinds.StructuredPostal.CITY,
                            ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE,
                            ContactsContract.CommonDataKinds.StructuredPostal.REGION,
                            ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY
                    ),
                    ContactsContract.CommonDataKinds.StructuredPostal.CONTACT_ID + "=" + contactID,
                    null,
                    null
            )

            if ( addressCur != null ) {

                while (addressCur.moveToNext()) {
                    label         = addressCur.getString(addressCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.TYPE)) ?: ""
                    street          = addressCur.getString(addressCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.STREET)) ?: ""
                    city            = addressCur.getString(addressCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.CITY)) ?: ""
                    postalCode      = addressCur.getString(addressCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE)) ?: ""
                    state           = addressCur.getString(addressCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.REGION)) ?: ""
                    country         = addressCur.getString(addressCur.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY)) ?: ""

                    jsonAddress.put("label", label)
                    jsonAddress.put("street", street)
                    jsonAddress.put("city", city)
                    jsonAddress.put("postalcode", postalCode)
                    jsonAddress.put("state", state)
                    jsonAddress.put("country", country)
                    jsonCurrentContact.accumulate("address", jsonAddress)

                }

            }
            addressCur?.close()


            Log.e("", "jsonCurrentContact: $jsonCurrentContact")



            // PUT the current JSON object info into an array
            jsonAllContacts.accumulate("contacts", jsonCurrentContact)
        }
        cursor.close()

    }).start()

} else {
    cursor?.close()
}        

Ответы [ 2 ]

1 голос
/ 19 сентября 2019

Те 3000 запросов, которые вы упомянули, могут быть сокращены до одного, и он должен завершиться довольно быстро.

При улучшении кода мы воспользуемся двумя преимуществами:

  1. Все данные, хранящиеся в таблицах ContactsContract.CommonDataKinds.XXX, на самом деле хранятся в одной большой таблице с именем Data.
  2. . В ContactsContract есть неявное соединение, которое позволяет нам выбирать столбцы из ContactsContract.Contacts при запросах через Data

Чтобы упростить код, я советую вам определить объект Contact для хранения в памяти информации, которую мы находим для одного контакта, и использовать HashMap для сопоставления идентификатора контакта сContact объект

Подробнее об этом здесь: https://developer.android.com/reference/android/provider/ContactsContract.Data.html

Вот некоторый код для начала работы:

Map<Long, Contact> contacts = new HashMap<>();

// If you need item type / label, add Data.DATA2 & Data.DATA3 to the projection
String[] projection = {Data.CONTACT_ID, Data.DISPLAY_NAME, Data.MIMETYPE, Data.DATA1};
// Add more types to the selection if needed, e.g. StructuredName
String selection = Data.MIMETYPE + " IN ('" + Phone.CONTENT_ITEM_TYPE + "', '" + Email.CONTENT_ITEM_TYPE + "', '" + StructuredPostal.CONTENT_ITEM_TYPE + "')"; 
Cursor cur = cr.query(Data.CONTENT_URI, projection, selection, null, null);

// Loop through the data
while (cur.moveToNext()) {
    long id = cur.getLong(0);
    String name = cur.getString(1);
    String mime = cur.getString(2); // email / phone / postal
    String data = cur.getString(3); // the actual info, e.g. +1-212-555-1234

    // get the Contact class from the HashMap, or create a new one and add it to the Hash
    Contact contact;
    if (contacts.containsKey(id)) {
        contact = contacts.get(id);
    } else {
        contact = new Contact(id);
        contact.setDisplayName(name);
        // start with empty Sets for phones and emails
        // instead of HashSets you can use some object to retain more info about the data item (e.g. label)
        contact.setPhoneNumbers(new HashSet<>()); 
        contact.setEmails(new HashSet<>());
        contact.setAddresses(new HashSet<>());
        contacts.put(id, contact);
    } 

    switch (mime) {
        case Phone.CONTENT_ITEM_TYPE: 
            contact.getPhoneNumbers().add(data);
            break;
        case Email.CONTENT_ITEM_TYPE: 
            contact.getEmails().add(data);
            break;
        case StructuredPostal.CONTENT_ITEM_TYPE: 
            contact.getAddresses().add(data);
            break;
    }
}
cur.close();

СЛЕДУЙТЕ ЗА Большой прогресс в производительности!Теперь вот несколько небольших настроек, которые позволят еще больше продвинуть ваш новый код:

  1. Старайтесь избегать сортировки, передайте null для сортировки и настройте свой код так, чтобы он обрабатывал данные в любом порядке: сортировка SQLite.иногда может значительно замедлить запросы
  2. Сокращение вашей проекции только до тех полей, которые вам действительно нужны, чем больше материала вы добавите в свою проекцию, тем больше будет размер данных, который необходимо перемещать между процессами на устройстве, что приводит кбольше кусков с меньшим количеством строк в каждом
  3. Не getString во всех полях проекции, если некоторые поля используются только в StructuredPostal, читайте их только для StructuredPostal строк, а не для каждой итерации.

сообщите в комментариях, чего вам удалось достичь с помощью приведенных выше советов ...

0 голосов
/ 26 сентября 2019

Вот мой код Kotlin, основанный на логике ответа @ marmor .

Этот оптимизированный код извлекает 1500 контактов в S7 Samsung за 500 мс вместо 3 минут.в коде вопроса .

Ради полноты я оставил фрагмент кода, который объединяет данные в объект JSON, и все детали для запуска процесса в отдельном потоке.

Thread(Runnable {
    val resolver: ContentResolver = contentResolver
    var jsonToSend = JSONObject()
    var jsonAllContacts = JSONObject()
    val jsonName = JSONObject()
    val jsonEmail = JSONObject()
    val jsonPhone = JSONObject()
    val jsonAddress = JSONObject()

    var cursorPosition = 0
    var currentProgress: Int

    val projection = arrayOf(
            ContactsContract.Data.CONTACT_ID,
            ContactsContract.Data.DISPLAY_NAME,
            ContactsContract.Data.MIMETYPE,
            ContactsContract.Data.DATA1,
            ContactsContract.Data.DATA2,
            ContactsContract.Data.DATA3,
            ContactsContract.Data.DATA4,
            ContactsContract.Data.DATA5,
            ContactsContract.Data.DATA6,
            ContactsContract.Data.DATA7,
            ContactsContract.Data.DATA8,
            ContactsContract.Data.DATA9,
            ContactsContract.Data.DATA10,
            ContactsContract.Data.DATA11,
            ContactsContract.Data.DATA12,
            ContactsContract.Data.DATA13,
            ContactsContract.Data.DATA14
    )
    val selection = ContactsContract.Data.MIMETYPE + " IN ('" + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "', '" + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "', '" + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE + "', '" + ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE + "')"

    val order = "CASE WHEN " + ContactsContract.Data.MIMETYPE + " = '" + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "' THEN 0 ELSE 1 END ASC, '" + ContactsContract.Data.CONTACT_ID + "'"
    val cursor = resolver.query(
            ContactsContract.Data.CONTENT_URI,
            projection,
            selection,
            null,
            order
    )

    Log.e("cursor", "cursor STARTED")

    if (cursor != null) {
        while (cursor.moveToNext())
        {
            cursorPosition++
            currentProgress = ((cursorPosition.toFloat() / cursor.count.toFloat()) * 100).toInt()


            // SELECT the data needed from the standardized table
            val id = cursor.getLong(0).toString()
            val name = cursor.getString(1)
            val mime = cursor.getString(2) // email / phone / postal
            val data1 = cursor.getString(3) // the actual info, e.g. +1-212-555-1234
            val data2 = cursor.getString(4)
            val data3 = cursor.getString(5) 
            val data4 = cursor.getString(6) 
            val data5 = cursor.getString(7) 
            val data6 = cursor.getString(8) 
            val data7 = cursor.getString(9) 
            val data8 = cursor.getString(10) 
            val data9 = cursor.getString(11) 
            val data10 = cursor.getString(12) 
            val data11 = cursor.getString(13) 
            val data12 = cursor.getString(14) 
            val data13 = cursor.getString(15) 
            val data14 = cursor.getString(16) 
            // get the Contact class from the HashMap, or create a new one and add it to the Hash




            when (mime) {
                ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> {
                    /**
                     * Type     Alias           Data column
                     * String   DISPLAY_NAME    DATA1
                     * String   GIVEN_NAME  DATA2
                     * String   FAMILY_NAME DATA3
                     * String   PREFIX  DATA4   Common prefixes in English names are "Mr", "Ms", "Dr" etc.
                     * String   MIDDLE_NAME DATA5
                     * String   SUFFIX  DATA6   Common suffixes in English names are "Sr", "Jr", "III" etc.
                     * String   PHONETIC_GIVEN_NAME DATA7   Used for phonetic spelling of the name, e.g. Pinyin, Katakana, Hiragana
                     * String   PHONETIC_MIDDLE_NAME    DATA8
                     * String   PHONETIC_FAMILY_NAME    DATA9
                     */
                    //Log.e("contact", "Name-- name:$name -- id:$id == 1:$data1 // 2:$data2 // 3:$data3 // 4: $data4 // 5:$data5 // 6:$data6 // 7:$data7 // 8:$data8 // 9:$data9 // 10:$data10 // 11:$data11 // 12:$data12 // 13:$data13 // 14:$data14")

                    val currentJSON = JSONObject()
                    val fullName = if ( data5 != null && (data5 != data3) ) {
                        "$data5 $data3"
                    } else {
                        data3 ?: data1 // PUT data1 as last resort because a contact with no names inputted will return something else (e.g. email address)
                    }

                    currentJSON.put("given", data2)
                    currentJSON.put("family", fullName)


                    jsonName.put( id, currentJSON)



                }
                ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE -> {
                    /**
                     * IMPROVE - check that valueForJSON / data1 is a properly formatted email. It sometimes saves in the email the name of the person instead of the email.
                     * e.g. "aze qsd" instead of "aze.qsd@gmail.com"
                     */
                    //Log.e("contact", "Email-- name:$name -- id:$id == 1:$data1 // 2:$data2 // 3:$data3 // 4: $data4 // 5:$data5 // 6:$data6 // 7:$data7 // 8:$data8 // 9:$data9 // 10:$data10 // 11:$data11 // 12:$data12 // 13:$data13 // 14:$data14")

                    val valueForJSON = data1


                    if ( jsonEmail.has(id) ) {
                        val indexString = jsonEmail[id].toString()
                        val indexArray = JSONObject(indexString)

                        if ( !indexArray.has(valueForJSON) ) {
                            jsonEmail.put( id, indexArray.put( valueForJSON, data2 ) )
                        }
                    } else {
                        jsonEmail.put( id, JSONObject().put( valueForJSON, data2 ) )
                    }



                }
                ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
                    /**
                     * Type Alias   Data column
                    String  NUMBER  ContactsContract.DataColumns.DATA1
                    int ContactsContract.CommonDataKinds.CommonColumns.TYPE ContactsContract.DataColumns.DATA2  Allowed values are:
                    ContactsContract.CommonDataKinds.BaseTypes.TYPE_CUSTOM. Put the actual type in ContactsContract.CommonDataKinds.CommonColumns.LABEL.
                    TYPE_HOME
                    TYPE_MOBILE
                    TYPE_WORK
                    etc..
                    String  ContactsContract.CommonDataKinds.CommonColumns.LABEL    ContactsContract.DataColumns.DATA3
                    String  ContactsContract.CommonDataKinds.CommonColumns.NORMALIZED_NUMBER    ContactsContract.DataColumns.DATA4

                     */
                    //Log.e("contact", "Phone-- name:$name -- id:$id == 1:$data1 // 2:$data2 // 3:$data3 // 4: $data4 // 5:$data5 // 6:$data6 // 7:$data7 // 8:$data8 // 9:$data9 // 10:$data10 // 11:$data11 // 12:$data12 // 13:$data13 // 14:$data14")

                    val valueForJSON = data4 ?: data1


                    if ( jsonPhone.has(id) ) {
                        val indexString = jsonPhone[id].toString()
                        val indexArray = JSONObject(indexString)

                        if ( !indexArray.has(valueForJSON) ) {
                            jsonPhone.put( id, indexArray.put( valueForJSON, data2 ) )
                        }
                    } else {
                        jsonPhone.put( id, JSONObject().put( valueForJSON, data2 ) )
                    }

                }
                ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE -> {
                    /**
                     * Type Alias   Data column
                    String  FORMATTED_ADDRESS   ContactsContract.DataColumns.DATA1
                    int ContactsContract.CommonDataKinds.CommonColumns.TYPE ContactsContract.DataColumns.DATA2  Allowed values are:
                    ContactsContract.CommonDataKinds.BaseTypes.TYPE_CUSTOM. Put the actual type in ContactsContract.CommonDataKinds.CommonColumns.LABEL.
                    TYPE_HOME
                    TYPE_WORK
                    TYPE_OTHER
                    String  ContactsContract.CommonDataKinds.CommonColumns.LABEL    ContactsContract.DataColumns.DATA3
                    String  STREET  ContactsContract.DataColumns.DATA4
                    String  POBOX   ContactsContract.DataColumns.DATA5  Post Office Box number
                    String  NEIGHBORHOOD    ContactsContract.DataColumns.DATA6
                    String  CITY    ContactsContract.DataColumns.DATA7
                    String  REGION  ContactsContract.DataColumns.DATA8
                    String  POSTCODE    ContactsContract.DataColumns.DATA9
                    String  COUNTRY ContactsContract.DataColumns.DATA10
                     */
                    //Log.e("contact", "Address-- name:$name -- id:$id == 1:$data1 // 2:$data2 // 3:$data3 // 4: $data4 // 5:$data5 // 6:$data6 // 7:$data7 // 8:$data8 // 9:$data9 // 10:$data10 // 11:$data11 // 12:$data12 // 13:$data13 // 14:$data14")


                    val currentJSON = JSONObject()
                    val valueForJSON = data1
                    currentJSON.put("street", data4)
                    currentJSON.put("city", data7)
                    currentJSON.put("postcode", data9)
                    currentJSON.put("state", data8)
                    currentJSON.put("country", data10)



                    if ( jsonAddress.has(id) ) {
                        val indexString = jsonAddress[id].toString()
                        val indexArray = JSONObject(indexString)

                        if ( !indexArray.has(valueForJSON) ) {
                            jsonAddress.put( id, indexArray.put( valueForJSON, currentJSON ) )
                        }
                    } else {
                        jsonAddress.put( id, JSONObject().put( valueForJSON, currentJSON ) )
                    }

                }
            }



            runOnUiThread {
                //PROGRESS HERE
                //myProgressBar?.visibility = View.VISIBLE
                myProgressBar?.progress = currentProgress
            }

        }

    }
    cursor?.close()



    // PUT some data to the first level of the nested JSON object
    jsonToSend.put("source", "2")



    /**
     * ADD the type of contact info to the main jsonAllContacts Object
     */
    jsonAllContacts = addNameToCurrentObject(jsonAllContacts, jsonName)
    jsonAllContacts = addNewKeyToCurrentObject(jsonAllContacts, jsonPhone, "email")
    jsonAllContacts = addNewKeyToCurrentObject(jsonAllContacts, jsonPhone, "phone")
    jsonAllContacts = addNewKeyToCurrentObject(jsonAllContacts, jsonAddress, "address")



    /**
     * Remove the IDs that was used for aggregation of data
     */
    jsonToSend = removeIDofObject(jsonToSend, jsonAllContacts)

}).start()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...