web-dev-qa-db-ja.com

1つのSQLiteクエリで連絡先ID、電子メール、電話番号を取得する方法は?連絡先Android最適化

少なくとも1つの電話番号ですべての連絡先を取得したい。また、すべての連絡先のすべての電話番号とすべての電子メールが必要です。

現在のコード:

// To get All Contacts having atleast one phone number.

Uri uri = ContactsContract.Contacts.CONTENT_URI;
String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " > ?";
String[] selectionArgs = new String[] {"0"};
Cursor cu = applicationContext.getContentResolver().query(uri, 
                null, selection, selectionArgs, null);

// For getting All Phone Numbers and Emails further queries : 
while(cu.moveToNext()){
String id = cu.getString(cu.getColumnIndex(ContactsContract.Contacts._ID));


 // To get Phone Numbers of Contact
    Cursor pCur = context.getContentResolver().query(
    ContactsContract.CommonDataKinds.Phone.CONTENT_URI,  null,ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?",
 new String[]{id}, null);

// To get Email ids of Contact
Cursor emailCur = context.getContentResolver().query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?",
new String[]{id}, null); 

// Iterate through these cursors to get Phone numbers and Emails
}

デバイスに1000を超える連絡先がある場合は、時間がかかりすぎます。連絡先ごとに2つの追加クエリを実行するのではなく、1つのクエリですべてのデータを取得するにはどうすればよいですか?

または、最適化する他の方法はありますか?

前もって感謝します。

20
Sagar

ICS:Data.CONTENT_URIからクエリを実行すると、関連付けられたContactのすべての行がすでに結合されています。つまり、これは機能します。

ContentResolver resolver = getContentResolver();
Cursor c = resolver.query(
        Data.CONTENT_URI, 
        null, 
        Data.HAS_PHONE_NUMBER + "!=0 AND (" + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?)", 
        new String[]{Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE},
        Data.CONTACT_ID);

while (c.moveToNext()) {
    long id = c.getLong(c.getColumnIndex(Data.CONTACT_ID));
    String name = c.getString(c.getColumnIndex(Data.DISPLAY_NAME));
    String data1 = c.getString(c.getColumnIndex(Data.DATA1));

    System.out.println(id + ", name=" + name + ", data1=" + data1);
}

2.3をターゲットにしている場合は、Dataのクエリ時に使用される結合ではHAS_PHONE_NUMBERが使用できないという事実を考慮する必要があります。

楽しい

これは、たとえば、連絡先必須に電話番号があるという要件をスキップし、代わりに「少なくとも電話番号または電子メールアドレスを持つ連絡先」を選択することで解決できます。

Cursor c = resolver.query(
        Data.CONTENT_URI, 
        null, 
        Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?", 
        new String[]{Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE},
        Data.CONTACT_ID);

それが選択肢ではない場合、あなたはいつでも恐ろしく行くことができます ハッキー 副選択:

Cursor c = resolver.query(
        Data.CONTENT_URI, 
        null, 
        "(" + Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?) AND " + 
        Data.CONTACT_ID + " IN (SELECT " + Contacts._ID + " FROM contacts WHERE " + Contacts.HAS_PHONE_NUMBER + "!=0)", 
        new String[]{Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE}, Data.CONTACT_ID);

またはを使用してそれを解決します Cursors:

Cursor contacts = resolver.query(Contacts.CONTENT_URI, 
        null, Contacts.HAS_PHONE_NUMBER + " != 0", null, Contacts._ID + " ASC");
Cursor data = resolver.query(Data.CONTENT_URI, null, 
        Data.MIMETYPE + "=? OR " + Data.MIMETYPE + "=?", 
        new String[]{Email.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE}, 
        Data.CONTACT_ID + " ASC");

int idIndex = contacts.getColumnIndexOrThrow(Contacts._ID);
int nameIndex = contacts.getColumnIndexOrThrow(Contacts.DISPLAY_NAME);
int cidIndex = data.getColumnIndexOrThrow(Data.CONTACT_ID);
int data1Index = data.getColumnIndexOrThrow(Data.DATA1);
boolean hasData = data.moveToNext();

while (contacts.moveToNext()) {
    long id = contacts.getLong(idIndex);
    System.out.println("Contact(" + id + "): " + contacts.getString(nameIndex));
    if (hasData) {
        long cid = data.getLong(cidIndex);
        while (cid <= id && hasData) {
            if (cid == id) {
                System.out.println("\t(" + cid + "/" + id + ").data1:" + 
                        data.getString(data1Index));
            }
            hasData = data.moveToNext();
            if (hasData) {
                cid = data.getLong(cidIndex);
            }
        }
    }
}
46
Jens

私はまったく同じ問題を経験しました。それ以来、私はこの投稿からインスピレーションを得た独自のソリューションを構築していますが、少し異なります。今、私はそれを私の最初のStackOverFlowの答えとして共有したいと思います:-)

Jensが提案したダブルカーソルアプローチと非常によく似ています。アイデアは

1-連絡先テーブルから関連する連絡先を取得します
2-関連する連絡先情報(メール、電話など)を取得します
3-これらの結果を組み合わせる

「関連性」はもちろんあなた次第ですが、重要なポイントはパフォーマンスです!その上、適切なSQLクエリを使用する他のソリューションでもうまくいくと確信していますが、ここではAndroid ContentProviderのみを使用します。コードは次のとおりです。

いくつかの定数

public static String CONTACT_ID_URI = ContactsContract.Contacts._ID;
public static String DATA_CONTACT_ID_URI = ContactsContract.Data.CONTACT_ID;
public static String MIMETYPE_URI = ContactsContract.Data.MIMETYPE;
public static String EMAIL_URI = ContactsContract.CommonDataKinds.Email.DATA;
public static String PHONE_URI = ContactsContract.CommonDataKinds.Phone.DATA;
public static String NAME_URI = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? ContactsContract.Data.DISPLAY_NAME_PRIMARY : ContactsContract.Data.DISPLAY_NAME;
public static String PICTURE_URI = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) ? ContactsContract.Contacts.PHOTO_THUMBNAIL_URI : ContactsContract.Contacts.PHOTO_ID;

public static String MAIL_TYPE = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
public static String PHONE_TYPE = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;

1連絡先

ここでは、連絡先に「@」のないDISPLAY_NAMEが必要であり、それらの情報が特定の文字列と一致する必要があります(これらの要件はもちろん変更できます)。次のメソッドの結果は最初のカーソルです:

public Cursor getContactCursor(String stringQuery, String sortOrder) {

    Logger.i(TAG, "+++++++++++++++++++++++++++++++++++++++++++++++++++");
    Logger.e(TAG, "ContactCursor search has started...");

    Long t0 = System.currentTimeMillis();

    Uri CONTENT_URI;

    if (stringQuery == null)
        CONTENT_URI = ContactsContract.Contacts.CONTENT_URI;
    else
        CONTENT_URI = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI, Uri.encode(stringQuery));

    String[] PROJECTION = new String[]{
            CONTACT_ID_URI,
            NAME_URI,
            PICTURE_URI
    };

    String SELECTION = NAME_URI + " NOT LIKE ?";
    String[] SELECTION_ARGS = new String[]{"%" + "@" + "%"};

    Cursor cursor = sContext.getContentResolver().query(CONTENT_URI, PROJECTION, SELECTION, SELECTION_ARGS, sortOrder);

    Long t1 = System.currentTimeMillis();

    Logger.e(TAG, "ContactCursor finished in " + (t1 - t0) / 1000 + " secs");
    Logger.e(TAG, "ContactCursor found " + cursor.getCount() + " contacts");
    Logger.i(TAG, "+++++++++++++++++++++++++++++++++++++++++++++++++++");

    return cursor;
}

ご覧のとおり、このクエリは非常にパフォーマンスが高いです。

2連絡先の詳細

それでは、連絡先情報を取得しましょう。この時点では、すでにフェッチされている連絡先と取得された情報の間にリンクは作成しません。データテーブルからすべての情報をフェッチするだけです...それでも、無駄な情報を避けるために、「@」を含まないDISPLAY_NAMESが必要です。電子メールと電話に興味がある場合、データMIMETYPEがMAIL_TYPEまたはPHONE_TYPEのいずれかである必要があります(定数を参照)。コードは次のとおりです。

public Cursor getContactDetailsCursor() {

    Logger.i(TAG, "+++++++++++++++++++++++++++++++++++++++++++++++++++");
    Logger.e(TAG, "ContactDetailsCursor search has started...");

    Long t0 = System.currentTimeMillis();

    String[] PROJECTION = new String[]{
            DATA_CONTACT_ID_URI,
            MIMETYPE_URI,
            EMAIL_URI,
            PHONE_URI
    };

    String SELECTION = ContactManager.NAME_URI + " NOT LIKE ?" + " AND " + "(" + MIMETYPE_URI + "=? " + " OR " + MIMETYPE_URI + "=? " + ")";

    String[] SELECTION_ARGS = new String[]{"%" + "@" + "%", ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE};

    Cursor cursor = sContext.getContentResolver().query(
            ContactsContract.Data.CONTENT_URI,
            PROJECTION,
            SELECTION,
            SELECTION_ARGS,
            null);

    Long t1 = System.currentTimeMillis();

    Logger.e(TAG, "ContactDetailsCursor finished in " + (t1 - t0) / 1000 + " secs");
    Logger.e(TAG, "ContactDetailsCursor found " + cursor.getCount() + " contacts");
    Logger.i(TAG, "+++++++++++++++++++++++++++++++++++++++++++++++++++");

    return cursor;
}

もう一度、このクエリが非常に高速であることがわかります...

3組み合わせる

それでは、連絡先とそれぞれの情報の両方を組み合わせてみましょう。アイデアは、HashMap(Key、String)を使用することです。ここで、Keyは連絡先IDであり、Stringは好きなもの(名前、電子メールなど)です。

まず、連絡先カーソル(アルファベット順)を実行し、名前と画像uriを2つの異なるHashMapに格納します。連絡先がカーソルに表示されるのとまったく同じ順序で、すべての連絡先IDをリストに保存することにも注意してください。このリストをcontactListIdと呼びましょう

連絡先情報(メールとメール)についても同様です。しかし今、私は2つのカーソル間の相関関係を処理します。電子メールまたは電話のCONTACT_IDがcontactListIdに表示されない場合、それは脇に置かれます。メールがすでに届いているかどうかも確認します。この追加の選択により、名前/画像コンテンツと電子メール/電話HashMapコンテンツの間に非対称性が生じる可能性があることに注意してください。ただし、心配する必要はありません。

最終的に、contactListIdリストを調べて、Contactオブジェクトのリストを作成します。連絡先には情報が必要であり(keySet条件)、連絡先には少なくともメールまたはメールが必要です(mail = = null && phone ==連絡先がSkypeの連絡先などの場合、nullが表示されることがあります)。そしてここにコードがあります:

public List<Contact> getDetailedContactList(String queryString) {

    /**
     * First we fetch the contacts name and picture uri in alphabetical order for
     * display purpose and store these data in HashMap.
     */

    Cursor contactCursor = getContactCursor(queryString, NAME_URI);

    List<Integer> contactIds = new ArrayList<>();

    if (contactCursor.moveToFirst()) {
        do {
            contactIds.add(contactCursor.getInt(contactCursor.getColumnIndex(CONTACT_ID_URI)));
        } while (contactCursor.moveToNext());
    }

    HashMap<Integer, String> nameMap = new HashMap<>();
    HashMap<Integer, String> pictureMap = new HashMap<>();

    int idIdx = contactCursor.getColumnIndex(CONTACT_ID_URI);

    int nameIdx = contactCursor.getColumnIndex(NAME_URI);
    int pictureIdx = contactCursor.getColumnIndex(PICTURE_URI);

    if (contactCursor.moveToFirst()) {
        do {
            nameMap.put(contactCursor.getInt(idIdx), contactCursor.getString(nameIdx));
            pictureMap.put(contactCursor.getInt(idIdx), contactCursor.getString(pictureIdx));
        } while (contactCursor.moveToNext());
    }

    /**
     * Then we get the remaining contact information. Here email and phone
     */

    Cursor detailsCursor = getContactDetailsCursor();

    HashMap<Integer, String> emailMap = new HashMap<>();
    HashMap<Integer, String> phoneMap = new HashMap<>();

    idIdx = detailsCursor.getColumnIndex(DATA_CONTACT_ID_URI);
    int mimeIdx = detailsCursor.getColumnIndex(MIMETYPE_URI);
    int mailIdx = detailsCursor.getColumnIndex(EMAIL_URI);
    int phoneIdx = detailsCursor.getColumnIndex(PHONE_URI);

    String mailString;
    String phoneString;

    if (detailsCursor.moveToFirst()) {
        do {

            /**
             * We forget all details which are not correlated with the contact list
             */

            if (!contactIds.contains(detailsCursor.getInt(idIdx))) {
                continue;
            }

            if(detailsCursor.getString(mimeIdx).equals(MAIL_TYPE)){
                mailString = detailsCursor.getString(mailIdx);

                /**
                 * We remove all double contact having the same email address
                 */

                if(!emailMap.containsValue(mailString.toLowerCase()))
                    emailMap.put(detailsCursor.getInt(idIdx), mailString.toLowerCase());

            } else {
                phoneString = detailsCursor.getString(phoneIdx);
                phoneMap.put(detailsCursor.getInt(idIdx), phoneString);
            }

        } while (detailsCursor.moveToNext());
    }

    contactCursor.close();
    detailsCursor.close();

    /**
     * Finally the contact list is build up
     */

    List<Contact> contacts = new ArrayList<>();

    Set<Integer> detailsKeySet = emailMap.keySet();

    for (Integer key : contactIds) {

        if(!detailsKeySet.contains(key) || (emailMap.get(key) == null && phoneMap.get(key) == null))
            continue;

        contacts.add(new Contact(String.valueOf(key), pictureMap.get(key), nameMap.get(key), emailMap.get(key), phoneMap.get(key)));
    }

    return contacts;
}

Contactオブジェクトの定義はあなた次第です。

これがお役に立てば幸いです。前の投稿に感謝します。

修正/改善

電話のキーセットを確認するのを忘れました:それはむしろ次のように見えるはずです

!mailKeySet.contains(key)

と取り換える

 (!mailKeySet.contains(key) && !phoneKeySet.contains(key))

電話のkeySetで

Set<Integer> phoneKeySet = phoneMap.keySet();

次のような空の連絡先カーソルチェックを追加してみませんか?

if(contactCursor.getCount() == 0){
        contactCursor.close();
        return new ArrayList<>();
    }

getContactCursor呼び出しの直後

5
Phil