擷取聯絡人名單

本課程將說明如何使用下列技術,擷取資料與搜尋字串完全或部分相符的聯絡人清單:

比對聯絡人姓名
將搜尋字串與聯絡人名稱資料進行比對,以擷取聯絡人清單。聯絡人供應程式允許多個相同名稱的例項,因此這項技術可以傳回比對結果清單。
比對特定類型的資料,例如電話號碼
比對搜尋字串和特定類型的詳細資料資料 (例如電子郵件地址),擷取聯絡人清單。舉例來說,這項技術可讓您列出電子郵件地址與搜尋字串相符的所有聯絡人。
比對任何類型的資料
將搜尋字串與任何類型的詳細資料 (包括姓名、電話號碼、街道地址、電子郵件地址等) 進行比對,以擷取聯絡人名單。舉例來說,您可以使用這項技術接受搜尋字串的任何類型資料,然後列出資料與該字串相符的聯絡人。

注意:本課程的所有範例都會使用 CursorLoader 從聯絡人供應程式擷取資料。CursorLoader 會在與 UI 執行緒分開的執行緒上執行查詢。這可確保查詢不會減慢 UI 回應時間,並導致使用者體驗不佳。詳情請參閱 Android 訓練課程「 在背景載入資料」。

要求讀取供應器的權限

如要搜尋聯絡人供應程式的任何類型,應用程式必須具備 READ_CONTACTS 權限。如要提出這項要求,請將此 <uses-permission> 元素新增至資訊清單檔案,做為 <manifest> 的子項:

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

依名稱比對聯絡人並列出結果

這項技術會嘗試將搜尋字串與聯絡供應器的 ContactsContract.Contacts 表格中聯絡人或聯絡人的名稱比對。您通常會希望在 ListView 中顯示結果,讓使用者在相符的聯絡人之間選擇。

定義 ListView 和項目版面配置

如要在 ListView 中顯示搜尋結果,您需要一個主要版面配置檔案,定義整個 UI (包括 ListView),以及一個項目版面配置檔案,定義 ListView 的一行。舉例來說,您可以使用下列 XML 建立主要版面配置檔案 res/layout/contacts_list_view.xml

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@android:id/list"
          android:layout_width="match_parent"
          android:layout_height="match_parent"/>

此 XML 使用內建的 Android ListView 小工具 android:id/list

使用下列 XML 定義項目版面配置檔案 contacts_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@android:id/text1"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:clickable="true"/>

這個 XML 會使用內建的 Android TextView 小工具 android:text1

注意:本課程不會說明從使用者取得搜尋字串的 UI,因為您可能會間接取得字串。舉例來說,您可以讓使用者選擇搜尋與傳入簡訊中字串相符的聯絡人。

您編寫的兩個版面配置檔案定義了顯示 ListView 的使用者介面。下一步是編寫使用此 UI 的程式碼,以便顯示聯絡人清單。

定義顯示聯絡人清單的 Fragment

如要顯示聯絡人清單,請先定義 Activity 載入的 Fragment。使用 Fragment 是更具彈性的技巧,因為您可以使用一個 Fragment 顯示清單,並使用另一個 Fragment 顯示使用者從清單中選擇的聯絡人詳細資料。使用這個方法時,您可以將本課程介紹的其中一項技術與 擷取聯絡人的詳細資料課程結合使用。

如要瞭解如何使用 Activity 中的一或多個 Fragment 物件,請參閱訓練類別 使用 Fragment 建構動態 UI

為協助您針對聯絡人供應程式編寫查詢,Android 架構提供名為 ContactsContract 的合約類別,其中定義了用於存取供應程式的實用常數和方法。使用這個類別時,您不必為內容 URI、資料表名稱或資料欄定義專屬常數。如要使用這個類別,請加入下列陳述式:

import android.provider.ContactsContract
import android.provider.ContactsContract;

由於程式碼會使用 CursorLoader 從提供者擷取資料,因此您必須指定該程式碼實作載入器介面 LoaderManager.LoaderCallbacks。此外,為了協助偵測使用者從搜尋結果清單中選取的聯絡人,請實作 AdapterView.OnItemClickListener 轉接介面。例如:

...
import android.support.v4.app.Fragment
import android.support.v4.app.LoaderManager
import android.widget.AdapterView
...
class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {
...
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.widget.AdapterView;
...
public class ContactsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {

定義全域變數

定義在程式碼其他部分中使用的全域變數:

...
/*
 * Defines an array that contains column names to move from
 * the Cursor to the ListView.
 */
@SuppressLint("InlinedApi")
private val FROM_COLUMNS: Array<String> = arrayOf(
        if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)) {
            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
        } else {
            ContactsContract.Contacts.DISPLAY_NAME
        }
)
/*
 * Defines an array that contains resource ids for the layout views
 * that get the Cursor column contents. The id is pre-defined in
 * the Android framework, so it is prefaced with "android.R.id"
 */
private val TO_IDS: IntArray = intArrayOf(android.R.id.text1)
...
class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor>,
        AdapterView.OnItemClickListener {
    ...
    // Define global mutable variables
    // Define a ListView object
    lateinit var contactsList: ListView
    // Define variables for the contact the user selects
    // The contact's _ID value
    var contactId: Long = 0
    // The contact's LOOKUP_KEY
    var contactKey: String? = null
    // A content URI for the selected contact
    var contactUri: Uri? = null
    // An adapter that binds the result Cursor to the ListView
    private val cursorAdapter: SimpleCursorAdapter? = null
    ...
    /*
     * Defines an array that contains column names to move from
     * the Cursor to the ListView.
     */
    @SuppressLint("InlinedApi")
    private final static String[] FROM_COLUMNS = {
            Build.VERSION.SDK_INT
                    >= Build.VERSION_CODES.HONEYCOMB ?
                    ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
                    ContactsContract.Contacts.DISPLAY_NAME
    };
    /*
     * Defines an array that contains resource ids for the layout views
     * that get the Cursor column contents. The id is pre-defined in
     * the Android framework, so it is prefaced with "android.R.id"
     */
    private final static int[] TO_IDS = {
           android.R.id.text1
    };
    // Define global mutable variables
    // Define a ListView object
    ListView contactsList;
    // Define variables for the contact the user selects
    // The contact's _ID value
    long contactId;
    // The contact's LOOKUP_KEY
    String contactKey;
    // A content URI for the selected contact
    Uri contactUri;
    // An adapter that binds the result Cursor to the ListView
    private SimpleCursorAdapter cursorAdapter;
    ...

注意:由於 Contacts.DISPLAY_NAME_PRIMARY 需要 Android 3.0 (API 級別 11) 以上版本,因此將應用程式的 minSdkVersion 設為 10 以下,會在 Android Studio 中產生 Android Lint 警告。如要關閉這項警示,請在 FROM_COLUMNS 定義前加上註解 @SuppressLint("InlinedApi")

初始化 Fragment

初始化 Fragment。新增 Android 系統所需的空白公開建構函式,並在回呼方法 onCreateView() 中加載 Fragment 物件的 UI。例如:

    // A UI Fragment must inflate its View
    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        // Inflate the fragment layout
        return inflater.inflate(R.layout.contact_list_fragment, container, false)
    }
    // Empty public constructor, required by the system
    public ContactsFragment() {}

    // A UI Fragment must inflate its View
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // Inflate the fragment layout
        return inflater.inflate(R.layout.contact_list_fragment,
            container, false);
    }

為 ListView 設定 CursorAdapter

設定 SimpleCursorAdapter,將搜尋結果繫結至 ListView。如要取得顯示聯絡人的 ListView 物件,您必須使用 Fragment 的父項活動呼叫 Activity.findViewById()。呼叫 setAdapter() 時,請使用父項活動的 Context。例如:

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        ...
        // Gets the ListView from the View list of the parent activity
        activity?.also {
            contactsList = it.findViewById<ListView>(R.id.contact_list_view)
            // Gets a CursorAdapter
            cursorAdapter = SimpleCursorAdapter(
                    it,
                    R.layout.contact_list_item,
                    null,
                    FROM_COLUMNS, TO_IDS,
                    0
            )
            // Sets the adapter for the ListView
            contactsList.adapter = cursorAdapter
        }
    }
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ...
        // Gets the ListView from the View list of the parent activity
        contactsList =
            (ListView) getActivity().findViewById(R.layout.contact_list_view);
        // Gets a CursorAdapter
        cursorAdapter = new SimpleCursorAdapter(
                getActivity(),
                R.layout.contact_list_item,
                null,
                FROM_COLUMNS, TO_IDS,
                0);
        // Sets the adapter for the ListView
        contactsList.setAdapter(cursorAdapter);
    }

設定所選聯絡人監聽器

當您顯示搜尋結果時,通常會想讓使用者選取單一聯絡人進行後續處理。舉例來說,當使用者點選聯絡人時,您可以在地圖上顯示聯絡人的地址。為了提供這項功能,您首先將目前的 Fragment 定義為點擊事件監聽器,方法是指定該類別實作 AdapterView.OnItemClickListener,如「定義顯示聯絡人清單的 Fragment」一節所示。

如要繼續設定事件監聽器,請呼叫 onActivityCreated() 中的 setOnItemClickListener() 方法,將事件監聽器繫結至 ListView。例如:

    fun onActivityCreated(savedInstanceState:Bundle) {
        ...
        // Set the item click listener to be the current fragment.
        contactsList.onItemClickListener = this
        ...
    }
    public void onActivityCreated(Bundle savedInstanceState) {
        ...
        // Set the item click listener to be the current fragment.
        contactsList.setOnItemClickListener(this);
        ...
    }

由於您已指定目前的 FragmentListViewOnItemClickListener,因此現在需要實作其所需的 onItemClick() 方法,以便處理點擊事件。我們會在成功的章節中說明。

定義投影

定義常數,該常數包含您要從查詢中傳回的資料欄。ListView 中的每個項目都會顯示聯絡人的顯示名稱,其中包含聯絡人名稱的主要形式。在 Android 3.0 (API 級別 11) 以上版本中,這個欄的名稱為 Contacts.DISPLAY_NAME_PRIMARY;在先前版本中,其名稱為 Contacts.DISPLAY_NAME

SimpleCursorAdapter 繫結程序會使用資料欄 Contacts._IDContacts._IDLOOKUP_KEY 可搭配使用,為使用者選取的聯絡人建構內容 URI。

...
@SuppressLint("InlinedApi")
private val PROJECTION: Array<out String> = arrayOf(
        ContactsContract.Contacts._ID,
        ContactsContract.Contacts.LOOKUP_KEY,
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
        else
            ContactsContract.Contacts.DISPLAY_NAME
)
...
@SuppressLint("InlinedApi")
private static final String[] PROJECTION =
        {
            Contacts._ID,
            Contacts.LOOKUP_KEY,
            Build.VERSION.SDK_INT
                    >= Build.VERSION_CODES.HONEYCOMB ?
                    ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
                    ContactsContract.Contacts.DISPLAY_NAME

        };

定義遊標資料欄索引的常數

如要從 Cursor 中的個別資料欄取得資料,您需要 Cursor 中的資料欄索引。您可以為 Cursor 資料欄的索引定義常數,因為索引與投影中的資料欄名稱順序相同。例如:

// The column index for the _ID column
private const val CONTACT_ID_INDEX: Int = 0
// The column index for the CONTACT_KEY column
private const val CONTACT_KEY_INDEX: Int = 1
// The column index for the _ID column
private static final int CONTACT_ID_INDEX = 0;
// The column index for the CONTACT_KEY column
private static final int CONTACT_KEY_INDEX = 1;

指定選取條件

如要指定所需資料,請建立文字運算式和變數的組合,告訴供應器要搜尋的資料欄和要尋找的值。

針對文字運算式,定義列出搜尋欄的常數。雖然這個運算式也可以包含值,但建議做法是使用「?」預留位置來表示值。在擷取期間,預留位置會替換為陣列的值。使用「?」做為預留位置可確保搜尋規格是透過繫結 (而非 SQL 編譯) 產生。這種做法可降低惡意 SQL 插入的可能性。例如:

// Defines the text expression
@SuppressLint("InlinedApi")
private val SELECTION: String =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE ?"
        else
            "${ContactsContract.Contacts.DISPLAY_NAME} LIKE ?"
...
    // Defines a variable for the search string
    private val searchString: String = ...
    // Defines the array to hold values that replace the ?
    private val selectionArgs = arrayOf<String>(searchString)
    // Defines the text expression
    @SuppressLint("InlinedApi")
    private static final String SELECTION =
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
            Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?" :
            Contacts.DISPLAY_NAME + " LIKE ?";
    // Defines a variable for the search string
    private String searchString;
    // Defines the array to hold values that replace the ?
    private String[] selectionArgs = { searchString };

定義 onItemClick() 方法

在上一節中,您已為 ListView 設定商品點擊事件監聽器。現在請定義 AdapterView.OnItemClickListener.onItemClick() 方法,為事件監聽器實作相關動作:

    override fun onItemClick(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
        // Get the Cursor
        val cursor: Cursor? = (parent.adapter as? CursorAdapter)?.cursor?.apply {
            // Move to the selected contact
            moveToPosition(position)
            // Get the _ID value
            contactId = getLong(CONTACT_ID_INDEX)
            // Get the selected LOOKUP KEY
            contactKey = getString(CONTACT_KEY_INDEX)
            // Create the contact's content Uri
            contactUri = ContactsContract.Contacts.getLookupUri(contactId, mContactKey)
            /*
             * You can use contactUri as the content URI for retrieving
             * the details for a contact.
             */
        }
    }
    @Override
    public void onItemClick(
        AdapterView<?> parent, View item, int position, long rowID) {
        // Get the Cursor
        Cursor cursor = parent.getAdapter().getCursor();
        // Move to the selected contact
        cursor.moveToPosition(position);
        // Get the _ID value
        contactId = cursor.getLong(CONTACT_ID_INDEX);
        // Get the selected LOOKUP KEY
        contactKey = cursor.getString(CONTACT_KEY_INDEX);
        // Create the contact's content Uri
        contactUri = Contacts.getLookupUri(contactId, mContactKey);
        /*
         * You can use contactUri as the content URI for retrieving
         * the details for a contact.
         */
    }

初始化載入器

您是使用 CursorLoader 擷取資料,因此必須初始化背景執行緒和其他控制非同步擷取作業的變數。在 onCreate() 中執行初始化作業,如以下範例所示:

class ContactsFragment :
        Fragment(),
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        // Always call the super method first
        super.onCreate(savedInstanceState)
        ...
        // Initializes the loader
        loaderManager.initLoader(0, null, this)
public class ContactsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    // Called just before the Fragment displays its UI
    @Override
    public void onCreate(Bundle savedInstanceState) {
        // Always call the super method first
        super.onCreate(savedInstanceState);
        ...
        // Initializes the loader
        getLoaderManager().initLoader(0, null, this);

實作 onCreateLoader()

實作 onCreateLoader() 方法,系統會在您呼叫 initLoader() 後立即呼叫此方法。

onCreateLoader() 中設定搜尋字串模式。如要將字串設為模式,請插入「%」(百分比) 字元,代表零個或多個字元的序列,或插入「_」(底線) 字元,代表單一字元,或同時插入這兩種字元。舉例來說,模式「%Jefferson%」會同時符合「Thomas Jefferson」和「Jefferson Davis」。

透過方法傳回新的 CursorLoader。如要使用內容 URI,請使用 Contacts.CONTENT_URI。這個 URI 會參照整個資料表,如以下範例所示:

    ...
    override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
        /*
         * Makes search string into pattern and
         * stores it in the selection array
         */
        selectionArgs[0] = "%$mSearchString%"
        // Starts the query
        return activity?.let {
            return CursorLoader(
                    it,
                    ContactsContract.Contacts.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    selectionArgs,
                    null
            )
        } ?: throw IllegalStateException()
    }
    ...
    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        /*
         * Makes search string into pattern and
         * stores it in the selection array
         */
        selectionArgs[0] = "%" + searchString + "%";
        // Starts the query
        return new CursorLoader(
                getActivity(),
                ContactsContract.Contacts.CONTENT_URI,
                PROJECTION,
                SELECTION,
                selectionArgs,
                null
        );
    }

實作 onLoadFinished() 和 onLoaderReset()

實作 onLoadFinished() 方法。當聯絡人提供者傳回查詢結果時,載入器架構會呼叫 onLoadFinished()。使用這個方法時,請將結果 Cursor 放入 SimpleCursorAdapter 中。這會自動使用搜尋結果更新 ListView

    override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
        // Put the result Cursor in the adapter for the ListView
        cursorAdapter?.swapCursor(cursor)
    }
    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        // Put the result Cursor in the adapter for the ListView
        cursorAdapter.swapCursor(cursor);
    }

當載入器架構偵測到結果 Cursor 包含過時資料時,就會叫用 onLoaderReset() 方法。刪除 SimpleCursorAdapter 對現有 Cursor 的參照。如果您沒有這樣做,載入器架構就不會回收 Cursor,導致記憶體流失。例如:

    override fun onLoaderReset(loader: Loader<Cursor>) {
        // Delete the reference to the existing Cursor
        cursorAdapter?.swapCursor(null)
    }
    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // Delete the reference to the existing Cursor
        cursorAdapter.swapCursor(null);

    }

您現在已擁有應用程式的關鍵部分,可將搜尋字串與聯絡人名稱比對,並在 ListView 中傳回結果。使用者可以按一下聯絡人姓名來選取。這會觸發事件監聽器,讓您進一步處理聯絡人的資料。例如,您可以擷取聯絡人的詳細資料。如要瞭解如何執行這項操作,請繼續閱讀下一課「擷取聯絡人的詳細資料」。

如要進一步瞭解搜尋使用者介面,請參閱 API 指南的「建立搜尋介面」一節。

本課程的其餘部分會示範在聯絡人供應器中尋找聯絡人的其他方式。

依據特定類型的資料比對聯絡人

這項技巧可讓您指定要比對的資料類型。這種特定查詢類型是以名稱擷取,不過也能針對與聯絡人相關的任何類型詳細資料資料執行擷取作業。舉例來說,您可以擷取具有特定郵遞區號的聯絡人;在這種情況下,搜尋字串必須與郵遞區號資料列中儲存的資料相符。

如要實作這類擷取作業,請先實作下列程式碼,如前面各節所述:

  • 要求讀取提供者的權限。
  • 定義 ListView 和項目版面配置。
  • 定義顯示聯絡人清單的 Fragment。
  • 定義全域變數。
  • 初始化 Fragment。
  • 為 ListView 設定 CursorAdapter。
  • 設定所選聯絡人的監聽器。
  • 定義遊標資料欄索引的常數。

    雖然您是從其他資料表擷取資料,但投影中的資料欄順序相同,因此可以為游標使用相同的索引。

  • 定義 onItemClick() 方法。
  • 初始化載入器。
  • 實作 onLoadFinished() 和 onLoaderReset()。

以下步驟會顯示需要的額外程式碼,以便將搜尋字串與特定類型的詳細資料相符,並顯示結果。

選擇資料類型和資料表

如要搜尋特定類型的詳細資料,您必須先瞭解資料類型的自訂 MIME 類型值。每種資料類型都有專屬的 MIME 類型值,該值由與該資料類型相關聯的 ContactsContract.CommonDataKinds 子類別中的常數 CONTENT_ITEM_TYPE 定義。子類別的名稱會指出其資料類型;舉例來說,電子郵件資料的子類別為 ContactsContract.CommonDataKinds.Email,而電子郵件資料的自訂 MIME 類型則由常數 Email.CONTENT_ITEM_TYPE 定義。

請使用 ContactsContract.Data 表格進行搜尋。您在投影、選取子句和排序順序中所需的所有常數,都會在這個資料表中定義或繼承。

定義投影

如要定義投影,請選擇 ContactsContract.Data 中定義的一或多個資料欄,或該欄繼承的類別。聯絡人供應程式會在傳回資料列之前,在 ContactsContract.Data 和其他資料表之間進行隱含彙整。例如:

@SuppressLint("InlinedApi")
private val PROJECTION: Array<out String> = arrayOf(
        /*
         * The detail data row ID. To make a ListView work,
         * this column is required.
         */
        ContactsContract.Data._ID,
        // The primary display name
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            ContactsContract.Data.DISPLAY_NAME_PRIMARY
        else
            ContactsContract.Data.DISPLAY_NAME,
        // The contact's _ID, to construct a content URI
        ContactsContract.Data.CONTACT_ID,
        // The contact's LOOKUP_KEY, to construct a content URI
        ContactsContract.Data.LOOKUP_KEY
)
    @SuppressLint("InlinedApi")
    private static final String[] PROJECTION =
        {
            /*
             * The detail data row ID. To make a ListView work,
             * this column is required.
             */
            ContactsContract.Data._ID,
            // The primary display name
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                    ContactsContract.Data.DISPLAY_NAME_PRIMARY :
                    ContactsContract.Data.DISPLAY_NAME,
            // The contact's _ID, to construct a content URI
            ContactsContract.Data.CONTACT_ID,
            // The contact's LOOKUP_KEY, to construct a content URI
            ContactsContract.Data.LOOKUP_KEY // A permanent link to the contact
        };

定義搜尋條件

如要在特定類型的資料中搜尋字串,請根據下列項目建立選取子句:

  • 包含搜尋字串的資料欄名稱。這個名稱會因資料類型而異,因此您必須找出與資料類型相對應的 ContactsContract.CommonDataKinds 子類別,然後從該子類別中選擇資料欄名稱。例如,如要搜尋電子郵件地址,請使用 Email.ADDRESS 資料欄。
  • 搜尋字串本身,在選取子句中以「?」字元表示。
  • 包含自訂 MIME 類型值的資料欄名稱。此名稱一律為 Data.MIMETYPE
  • 資料類型的自訂 MIME 類型值。如先前所述,這是 ContactsContract.CommonDataKinds 子類別中的常數 CONTENT_ITEM_TYPE。舉例來說,電子郵件資料的 MIME 類型值為 Email.CONTENT_ITEM_TYPE。將「'」(單引號) 字元連結至常數的開頭和結尾,以單引號括住值;否則,提供者會將值解讀為變數名稱,而非字串值。您不需要針對這個值使用預留位置,因為您使用的是常數 (而非使用者提供的值)。

例如:

/*
 * Constructs search criteria from the search string
 * and email MIME type
 */
private val SELECTION: String =
        /*
         * Searches for an email address
         * that matches the search string
         */
        "${Email.ADDRESS} LIKE ? AND " +
        /*
         * Searches for a MIME type that matches
         * the value of the constant
         * Email.CONTENT_ITEM_TYPE. Note the
         * single quotes surrounding Email.CONTENT_ITEM_TYPE.
         */
        "${ContactsContract.Data.MIMETYPE } = '${Email.CONTENT_ITEM_TYPE}'"
    /*
     * Constructs search criteria from the search string
     * and email MIME type
     */
    private static final String SELECTION =
            /*
             * Searches for an email address
             * that matches the search string
             */
            Email.ADDRESS + " LIKE ? " + "AND " +
            /*
             * Searches for a MIME type that matches
             * the value of the constant
             * Email.CONTENT_ITEM_TYPE. Note the
             * single quotes surrounding Email.CONTENT_ITEM_TYPE.
             */
            ContactsContract.Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'";

接著,定義變數來包含選取引數:

    private var searchString: String? = null
    private val selectionArgs: Array<String> = arrayOf("")
    String searchString;
    String[] selectionArgs = { "" };

實作 onCreateLoader()

您已指定所需資料和查詢方式,請在 onCreateLoader() 的實作中定義查詢。使用投影、選取文字運算式和選取陣列做為引數,從這個方法傳回新的 CursorLoader。如為內容 URI,請使用 Data.CONTENT_URI。例如:

    override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
        // OPTIONAL: Makes search string into pattern
        searchString = "%$mSearchString%"

        searchString?.also {
            // Puts the search string into the selection criteria
            selectionArgs[0] = it
        }
        // Starts the query
        return activity?.let {
            CursorLoader(
                    it,
                    ContactsContract.Data.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    selectionArgs,
                    null
            )
        } ?: throw IllegalStateException()
    }
@Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        // OPTIONAL: Makes search string into pattern
        searchString = "%" + searchString + "%";
        // Puts the search string into the selection criteria
        selectionArgs[0] = searchString;
        // Starts the query
        return new CursorLoader(
                getActivity(),
                Data.CONTENT_URI,
                PROJECTION,
                SELECTION,
                selectionArgs,
                null
        );
    }

這些程式碼片段是根據特定類型的詳細資料,進行簡單的反向查詢的基礎。如果應用程式著重於特定類型的資料 (例如電子郵件),且您希望使用者能夠取得與資料相關聯的名稱,這就是最佳的做法。

根據任何類型的資料比對聯絡人

依據任何類型的資料擷取聯絡人時,只要聯絡人的資料與搜尋字串相符 (包括姓名、電子郵件地址、郵寄地址、電話號碼等),系統就會傳回聯絡人。這會產生廣泛的搜尋結果。舉例來說,如果搜尋字串是「Doe」,搜尋任何資料類型都會傳回「John Doe」聯絡人,也會傳回住在「Doe Street」的聯絡人。

如要實作這類擷取作業,請先實作下列程式碼,如前面各節所述:

  • 要求讀取提供者的權限。
  • 定義 ListView 和項目版面配置。
  • 定義顯示聯絡人清單的 Fragment。
  • 定義全域變數。
  • 初始化 Fragment。
  • 為 ListView 設定 CursorAdapter。
  • 設定所選聯絡人事件監聽器。
  • 定義投影。
  • 定義游標欄索引的常數。

    針對這類擷取作業,您會使用「依名稱比對聯絡人並列出結果」一節中使用的相同表格。並使用相同的欄索引。

  • 定義 onItemClick() 方法。
  • 初始化載入器。
  • 實作 onLoadFinished() 和 onLoaderReset()。

下列步驟會顯示您需要的額外程式碼,以便將搜尋字串與任何類型的資料配對,並顯示結果。

移除選取條件

請勿定義 SELECTION 常數或 mSelectionArgs 變數。但無法用於這類擷取。

實作 onCreateLoader()

實作 onCreateLoader() 方法,傳回新的 CursorLoader。您不需要將搜尋字串轉換為模式,因為聯絡資訊提供者會自動執行這項操作。使用 Contacts.CONTENT_FILTER_URI 做為基準 URI,並呼叫 Uri.withAppendedPath() 將搜尋字串附加到其中。使用這個 URI 會自動觸發任何資料類型的搜尋作業,如以下範例所示:

    override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
        /*
         * Appends the search string to the base URI. Always
         * encode search strings to ensure they're in proper
         * format.
         */
        val contentUri: Uri = Uri.withAppendedPath(
                ContactsContract.Contacts.CONTENT_FILTER_URI,
                Uri.encode(searchString)
        )
        // Starts the query
        return activity?.let {
            CursorLoader(
                    it,
                    contentUri,
                    PROJECTION2,
                    null,
                    null,
                    null
            )
        } ?: throw IllegalStateException()
    }
    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        /*
         * Appends the search string to the base URI. Always
         * encode search strings to ensure they're in proper
         * format.
         */
        Uri contentUri = Uri.withAppendedPath(
                Contacts.CONTENT_FILTER_URI,
                Uri.encode(searchString));
        // Starts the query
        return new CursorLoader(
                getActivity(),
                contentUri,
                PROJECTION,
                null,
                null,
                null
        );
    }

這些程式碼片段是應用程式對「聯絡人供應程式」進行廣泛搜尋的基礎, 如果應用程式想要實作與「使用者」應用程式聯絡人清單畫面類似的功能,這個技巧就非常實用。