擷取聯絡人的詳細資料

本課程將說明如何擷取聯絡人的詳細資料,例如電子郵件地址、電話號碼等。這是使用者擷取聯絡人時所需的詳細資料。您可以提供聯絡人的所有詳細資料,或是只顯示特定類型的詳細資料,例如電子郵件地址。

本課程的步驟假設您已為使用者選擇的聯絡人有 ContactsContract.Contacts 列。「擷取聯絡人名稱」課程會說明如何擷取聯絡人清單。

擷取聯絡人的所有詳細資料

如要擷取聯絡人的所有詳細資料,請在 ContactsContract.Data 資料表中搜尋任何包含聯絡人 LOOKUP_KEY 的資料列。這個資料欄會顯示在 ContactsContract.Data 資料表中,因為聯絡人提供者會在 ContactsContract.Contacts 資料表和 ContactsContract.Data 資料表之間建立隱含的彙整作業。如要進一步瞭解 LOOKUP_KEY 欄,請參閱「擷取聯絡人姓名」課程。

注意:擷取聯絡人的所有詳細資料會降低裝置效能,因為裝置需要擷取 ContactsContract.Data 資料表中的所有資料欄。使用這項技巧前,請先考量效能影響。

要求權限

如要讀取聯絡人供應程式,應用程式必須具備 READ_CONTACTS 權限。如要要求這項權限,請在資訊清單檔案中加入下列 <manifest> 的子元素:

    <uses-permission android:name="android.permission.READ_CONTACTS" />

設定投影

視資料列所含資料類型而定,資料列可能只使用少數或大量資料欄。此外,資料會依資料類型而放在不同的欄中。為確保您取得所有可能資料類型的所有可能資料欄,請將所有資料欄名稱新增至投影。如果將結果 Cursor 繫結至 ListView,請一律擷取 Data._ID,否則繫結將無法運作。同時擷取 Data.MIMETYPE,以便識別所擷取每個資料列的資料類型。例如:

Kotlin

private val PROJECTION: Array<out String> = arrayOf(
        ContactsContract.Data._ID,
        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,
        ContactsContract.Data.DATA15
)

Java

    private static final String[] PROJECTION =
            {
                ContactsContract.Data._ID,
                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,
                ContactsContract.Data.DATA15
            };

這個投影會使用 ContactsContract.Data 類別中定義的資料欄名稱,擷取 ContactsContract.Data 資料表中資料列的所有資料欄。

您也可以選擇使用 ContactsContract.Data 類別中定義或繼承的任何其他資料欄常數。不過,請注意,從 SYNC1SYNC4 的資料欄是供同步轉換介面使用,因此其資料並無用處。

定義選取條件

定義選取子句的常數、用於儲存選取引數的陣列,以及用於儲存選取值的變數。使用 Contacts.LOOKUP_KEY 欄找出聯絡人。例如:

Kotlin

// Defines the selection clause
private const val SELECTION: String = "${ContactsContract.Data.LOOKUP_KEY} = ?"
...
// Defines the array to hold the search criteria
private val selectionArgs: Array<String> = arrayOf("")
/*
 * Defines a variable to contain the selection value. Once you
 * have the Cursor from the Contacts table, and you've selected
 * the desired row, move the row's LOOKUP_KEY value into this
 * variable.
 */
private var lookupKey: String? = null

Java

    // Defines the selection clause
    private static final String SELECTION = Data.LOOKUP_KEY + " = ?";
    // Defines the array to hold the search criteria
    private String[] selectionArgs = { "" };
    /*
     * Defines a variable to contain the selection value. Once you
     * have the Cursor from the Contacts table, and you've selected
     * the desired row, move the row's LOOKUP_KEY value into this
     * variable.
     */
    private lateinit var lookupKey: String

在選取文字運算式中使用「?」做為預留位置,可確保系統透過繫結而非 SQL 編譯產生搜尋結果。這種做法可降低惡意 SQL 插入的可能性。

定義排序順序

在產生的 Cursor 中定義所需的排序順序。如要將特定資料類型的所有資料列保留在一起,請依據 Data.MIMETYPE 排序。這個查詢引數會將所有電子郵件列組合在一起,所有電話列也一樣。例如:

Kotlin

/*
 * Defines a string that specifies a sort order of MIME type
 */
private const val SORT_ORDER = ContactsContract.Data.MIMETYPE

Java

    /*
     * Defines a string that specifies a sort order of MIME type
     */
    private static final String SORT_ORDER = ContactsContract.Data.MIMETYPE;

注意:部分資料類型不使用子類型,因此無法依子類型排序。您必須改為透過傳回的 Cursor 疊代、判斷目前資料列的資料類型,並為使用子類型的資料列儲存資料。遊標讀取完畢後,即可按照子類型將每種資料類型排序並顯示結果。

初始化載入器

請務必在背景執行緒中,從聯絡人提供者 (以及所有其他內容提供者) 進行擷取作業。使用 LoaderManager 類別和 LoaderManager.LoaderCallbacks 介面定義的 Loader 架構,執行背景擷取作業。

準備擷取資料列時,請呼叫 initLoader() 來初始化載入器架構。將整數 ID 傳遞至方法;這個 ID 會傳遞至 LoaderManager.LoaderCallbacks 方法。這個 ID 可讓您在應用程式中使用多個載入器,並用於區分不同載入器。

下列程式碼片段說明如何初始化載入器架構:

Kotlin

// Defines a constant that identifies the loader
private const val DETAILS_QUERY_ID: Int = 0

class DetailsFragment : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Initializes the loader framework
        loaderManager.initLoader(DETAILS_QUERY_ID, null, this)

Java

public class DetailsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    // Defines a constant that identifies the loader
    static int DETAILS_QUERY_ID = 0;
    ...
    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        // Initializes the loader framework
        getLoaderManager().initLoader(DETAILS_QUERY_ID, null, this);

實作 onCreateLoader()

實作 onCreateLoader() 方法,此方法會在您呼叫 initLoader() 後立即由載入器架構呼叫。透過這個方法傳回 CursorLoader。由於您要搜尋 ContactsContract.Data 資料表,請使用常數 Data.CONTENT_URI 做為內容 URI。例如:

Kotlin

override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
    // Choose the proper action
    mLoader = when(loaderId) {
        DETAILS_QUERY_ID -> {
            // Assigns the selection parameter
            selectionArgs[0] = lookupKey
            // Starts the query
            activity?.let {
                CursorLoader(
                        it,
                        ContactsContract.Data.CONTENT_URI,
                        PROJECTION,
                        SELECTION,
                        selectionArgs,
                        SORT_ORDER
                )
            }
        }
        ...
    }
    return mLoader
}

Java

@Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        // Choose the proper action
        switch (loaderId) {
            case DETAILS_QUERY_ID:
            // Assigns the selection parameter
            selectionArgs[0] = lookupKey;
            // Starts the query
            CursorLoader mLoader =
                    new CursorLoader(
                            getActivity(),
                            ContactsContract.Data.CONTENT_URI,
                            PROJECTION,
                            SELECTION,
                            selectionArgs,
                            SORT_ORDER
                    );
    }

實作 onLoadFinished() 和 onLoaderReset()

實作 onLoadFinished() 方法。當聯絡人提供者傳回查詢結果時,載入器架構會呼叫 onLoadFinished()。例如:

Kotlin

    override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) {
        when(loader.id) {
            DETAILS_QUERY_ID -> {
                /*
                 * Process the resulting Cursor here.
                 */
            }
            ...
        }
    }

Java

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        switch (loader.getId()) {
            case DETAILS_QUERY_ID:
                    /*
                     * Process the resulting Cursor here.
                     */
                }
                break;
            ...
        }
    }

當載入器架構偵測到結果 Cursor 的備份資料已變更時,系統會叫用 onLoaderReset() 方法。此時,只要將 Cursor 現有的任何參照設為空值,即可移除這些參照。如未移除,載入器架構不會刪除舊的 Cursor,而且您將收到記憶體流失。例如:

Kotlin

    override fun onLoaderReset(loader: Loader<Cursor>) {
        when (loader.id) {
            DETAILS_QUERY_ID -> {
                /*
                 * If you have current references to the Cursor,
                 * remove them here.
                 */
            }
            ...
        }
    }

Java

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        switch (loader.getId()) {
            case DETAILS_QUERY_ID:
                /*
                 * If you have current references to the Cursor,
                 * remove them here.
                 */
                }
                break;
    }

擷取聯絡人的特定詳細資料

擷取聯絡人的特定資料類型 (例如所有電子郵件) 時,模式與擷取所有詳細資料時相同。您只需要對「擷取聯絡人的所有詳細資料」一節中列出的程式碼,只需進行以下變更:

投影
修改投影,以便擷取特定資料類型的資料欄。同時修改投影,以便使用與資料類型相對應的 ContactsContract.CommonDataKinds 子類別中定義的資料欄名稱常數。
選取
修改所選文字,搜尋特定資料類型的 MIMETYPE 值。
排序順序
由於您只選取單一詳細資料類型,請勿依 Data.MIMETYPE 分組傳回的 Cursor

我們將在以下各節中說明這些修改。

定義投影

使用資料類型的 ContactsContract.CommonDataKinds 子類別中的資料欄名稱常數,定義要擷取的資料欄。如果打算將 Cursor 繫結至 ListView,請務必擷取 _ID 欄。例如,如要擷取電子郵件資料,請定義下列投影:

Kotlin

private val PROJECTION: Array<String> = arrayOf(
        ContactsContract.CommonDataKinds.Email._ID,
        ContactsContract.CommonDataKinds.Email.ADDRESS,
        ContactsContract.CommonDataKinds.Email.TYPE,
        ContactsContract.CommonDataKinds.Email.LABEL
)

Java

    private static final String[] PROJECTION =
            {
                ContactsContract.CommonDataKinds.Email._ID,
                ContactsContract.CommonDataKinds.Email.ADDRESS,
                ContactsContract.CommonDataKinds.Email.TYPE,
                ContactsContract.CommonDataKinds.Email.LABEL
            };

請注意,這個投影會使用 ContactsContract.CommonDataKinds.Email 類別中定義的資料欄名稱,而不是 ContactsContract.Data 類別中定義的資料欄名稱。使用電子郵件專屬的資料欄名稱可讓程式碼更易讀。

在投影中,您也可以使用 ContactsContract.CommonDataKinds 子類別中定義的任何其他欄。

定義選取條件

定義搜尋文字運算式,以便擷取特定聯絡人的 LOOKUP_KEY 資料列,以及所需詳細資料的 Data.MIMETYPE。將「'」(單引號) 字元串連在常數的開頭和結尾,以單引號括住 MIMETYPE 值。否則,提供者會將常數解讀為變數名稱,而非字串值。您不需要為這個值使用預留位置,因為您使用的是常數,而非使用者提供的值。例如:

Kotlin

/*
 * Defines the selection clause. Search for a lookup key
 * and the Email MIME type
 */
private const val SELECTION =
        "${ContactsContract.Data.LOOKUP_KEY} = ? AND " +
        "${ContactsContract.Data.MIMETYPE} = '${Email.CONTENT_ITEM_TYPE}'"
...
// Defines the array to hold the search criteria
private val selectionArgs: Array<String> = arrayOf("")

Java

    /*
     * Defines the selection clause. Search for a lookup key
     * and the Email MIME type
     */
    private static final String SELECTION =
            Data.LOOKUP_KEY + " = ?" +
            " AND " +
            Data.MIMETYPE + " = " +
            "'" + Email.CONTENT_ITEM_TYPE + "'";
    // Defines the array to hold the search criteria
    private String[] selectionArgs = { "" };

定義排序順序

定義傳回的 Cursor 排序順序。由於您要擷取特定資料類型,請略過 MIMETYPE 的排序。相反地,如果搜尋的詳細資料類型包含子類型,請依該子類型排序資料。舉例來說,您可以依據電子郵件資料排序 Email.TYPE

Kotlin

private const val SORT_ORDER: String = "${Email.TYPE} ASC"

Java

    private static final String SORT_ORDER = Email.TYPE + " ASC ";