擷取聯絡人名單

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

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

注意:本課程的所有範例都會使用 CursorLoader 從「聯絡人」內容供應器擷取資料。CursorLoader 會在與 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。

注意:本課程不會說明從使用者取得搜尋字串的使用者介面,因為您可能需要間接取得字串。舉例來說,您可以讓使用者搜尋名稱與來電簡訊中字串相符的聯絡人。

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

定義顯示聯絡人清單的片段

如要顯示聯絡人清單,請先定義由 Activity 載入的 Fragment。使用 Fragment 是更靈活的技術,因為您可以使用一個 Fragment 顯示清單,並使用第二個 Fragment 顯示使用者從清單中選擇的聯絡人詳細資料。使用這種方法,您可以將本課程中介紹的其中一種技術與「 擷取聯絡人詳細資料」課程中的技術結合。

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

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

Kotlin

import android.provider.ContactsContract

Java

import android.provider.ContactsContract;

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

Kotlin

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

Java

...
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 {

定義全域變數

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

Kotlin

...
/*
 * 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

Java

    ...
    /*
     * 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。例如:

Kotlin

    // 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)
    }

Java

    // 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。例如:

Kotlin

    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
        }
    }

Java

    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,如「定義顯示聯絡人清單的片段」一節所示。

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

Kotlin

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

Java

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

由於您已指定目前的 Fragment 是 ListView 的 OnItemClickListener,因此現在需要實作必要方法 onItemClick(),用於處理點擊事件。後續章節會說明這項功能。

定義投影

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

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

Kotlin

...
@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
)

Java

...
@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 內的資料欄索引。您可以為 Cursor 資料欄的索引定義常數,因為索引與投影中資料欄名稱的順序相同。例如:

Kotlin

// 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

Java

// 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 注入。例如:

Kotlin

// 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)

Java

    // 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() 方法,為事件監聽器實作動作:

Kotlin

    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.
             */
        }
    }

Java

    @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() 中執行初始化,如以下範例所示:

Kotlin

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)

Java

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。請使用 Contacts.CONTENT_URI 做為內容 URI。這個 URI 參照整個資料表,如以下範例所示:

Kotlin

    ...
    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()
    }

Java

    ...
    @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,並顯示搜尋結果:

Kotlin

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

Java

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

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

Kotlin

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

Java

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

    }

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

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

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

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

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

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

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

    雖然您是從不同的資料表擷取資料,但投影中的資料欄順序相同,因此您可以對 Cursor 使用相同的索引。

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

下列步驟說明您需要哪些額外程式碼,才能將搜尋字串與特定類型的詳細資料相符,並顯示結果。

選擇資料類型和資料表

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

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

定義投影

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

Kotlin

@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
)

Java

    @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。在常數的開頭和結尾串連「'」(單引號) 字元,將值括在單引號中;否則,提供者會將值解讀為變數名稱,而不是字串值。您不需要使用這個值的預留位置,因為您使用的是常數,而不是使用者提供的值。

例如:

Kotlin

/*
 * 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}'"

Java

    /*
     * 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 + "'";

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

Kotlin

    private var searchString: String? = null
    private val selectionArgs: Array<String> = arrayOf("")

Java

    String searchString;
    String[] selectionArgs = { "" };

實作 onCreateLoader()

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

Kotlin

    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()
    }

Java

@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。
  • 設定所選聯絡人事件監聽器。
  • 定義投影。
  • 定義 Cursor 資料欄索引的常數。

    這類檢索作業使用的資料表,與「依名稱比對聯絡人並列出結果」一節使用的資料表相同。並使用相同的資料欄索引。

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

下列步驟說明您需要哪些額外程式碼,才能比對搜尋字串與任何類型的資料,並顯示結果。

移除選取條件

請勿定義 SELECTION 常數或 mSelectionArgs 變數。這類檢索不會使用這些項目。

實作 onCreateLoader()

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

Kotlin

    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()
    }

Java

    @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
        );
    }

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