本課程說明如何擷取聯絡人的詳細資料,例如電子郵件地址、電話號碼等。使用者擷取聯絡人時,會尋找這類詳細資料。 您可以提供聯絡人的所有詳細資料,或只顯示特定類型的詳細資料,例如電子郵件地址。
本課程的步驟假設您已為使用者選取的聯絡人建立 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 類別定義或繼承的其他資料欄常數。不過請注意,SYNC1 到 SYNC4 欄是供同步轉換介面使用,因此資料不具實用性。
定義選取條件
為選取子句定義常數、用於保留選取引數的陣列,以及用於保留選取值的變數。使用「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 介面定義的載入器架構,在背景中擷取資料。
準備好擷取資料列時,請呼叫 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子類別中定義的資料欄名稱常數。 - Selection
-
修改選取文字,搜尋資料類型專屬的
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 ";