Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

연락처 목록 검색

이 과정에서는 다음 기술을 사용하여 데이터가 전체 또는 일부 검색 문자열과 일치하는 연락처 목록을 검색하는 방법을 알려줍니다.

연락처 이름 일치
검색 문자열을 연락처 이름 데이터의 전체 또는 일부와 일치시켜서 연락처 목록을 검색합니다. 연락처 제공자를 사용하면 동일한 이름의 여러 가지 인스턴스가 허용되므로 이 기술을 통해 일치 항목 목록이 반환될 수 있습니다.
특정 데이터 유형 일치(예: 전화번호)
검색 문자열을 이메일 주소와 같은 세부적인 특정 데이터 유형과 일치시켜서 연락처 목록을 검색합니다. 예를 들어 이 기술을 사용하면 이메일 주소가 검색 문자열과 일치하는 모든 연락처 목록을 표시할 수 있습니다.
모든 데이터 유형 일치
검색 문자열을 이름, 전화번호, 상세 주소, 이메일 주소 등을 포함한 모든 세부적인 데이터 유형과 일치시켜서 연락처 목록을 검색합니다. 예를 들어 이 기술을 사용하면 검색 문자열을 모든 데이터 유형으로 수락할 수 있으며 데이터가 문자열과 일치하는 연락처 목록을 표시할 수 있습니다.

참고: 이 과정의 모든 예에서는 CursorLoader를 사용하여 연락처 제공자의 데이터를 검색합니다. CursorLoader는 UI 스레드와 별도의 스레드에서 쿼리를 실행합니다. 이렇게 하면 쿼리가 UI 응답 시간을 늦춰서 사용자 환경이 저하되는 일이 발생하지 않습니다. 자세한 내용은 Android 교육 과정 백그라운드에서 데이터 로드하기를 참조하세요.

제공자 읽기 권한 요청

연락처 제공자에서 검색 작업을 하려면 앱에 READ_CONTACTS 권한이 있어야 합니다. 권한을 요청하려면 <uses-permission> 요소를 manifest 파일에 <manifest>의 하위 요소로 추가하세요.

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

이름으로 연락처를 일치시킨 후 결과 표시

이 기술은 검색 문자열을 연락처 또는 연락처 제공자의 ContactsContract.Contacts 표에 있는 연락처의 이름과 일치시키려고 합니다. 일반적으로 개발자는 ListView에 결과를 표시하여 사용자가 일치된 연락처 중에 선택할 수 있도록 허용하려고 합니다.

ListView 및 항목 레이아웃 정의

ListView에 검색결과를 표시하려면 ListView를 포함한 전체 UI를 정의하는 기본 레이아웃 파일과 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 개체를 사용하는 방법을 알아보려면 프래그먼트로 동적 UI 빌드 교육 과정을 참조하세요.

연락처 제공자를 대상으로 쿼리를 작성하는 데 도움이 되도록 Android 프레임워크에서 제공자에게 액세스할 때 유용한 상수와 메서드를 정의하는 ContactsContract라는 계약 클래스를 제공합니다. 이 클래스를 사용하면 콘텐츠 URI, 표 이름, 열에 관한 고유 상수를 정의하지 않아도 됩니다. 이 클래스를 사용하려면 다음 명령문을 포함하세요.

Kotlin

    import android.provider.ContactsContract
    

자바

    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 {
    

자바

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

    

자바

        ...
        /*
         * 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 스튜디오에 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)
        }
    

자바

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

검색결과를 ListView에 바인딩하는 SimpleCursorAdapter를 설정합니다. 연락처를 표시하는 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
            }
        }
    

자바

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

특정 연락처 리스너 설정

검색결과를 표시할 때 일반적으로 사용자가 추가로 처리할 연락처를 한 개 선택할 수 있도록 허용하려고 합니다. 예를 들어 사용자가 연락처를 선택하면 그 연락처의 주소를 지도에 표시할 수 있습니다. 이 기능을 제공하려면 먼저 현재 FragmentAdapterView.OnItemClickListener를 구현(연락처 목록을 표시하는 Fragment 정의 섹션에 설명)하도록 지정하여 이 클래스를 클릭 리스너로 정의해야 합니다.

리스너 설정을 계속하려면 onActivityCreated()에서 setOnItemClickListener() 메서드를 호출하여 ListView에 바인딩하세요. 예를 들면 다음과 같습니다.

Kotlin

        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입니다.

Contacts._ID 열은 SimpleCursorAdapter 바인딩 과정에서 사용됩니다. Contacts._IDLOOKUP_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
    )
    

자바

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

자바

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

자바

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

자바

        @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를 사용하여 데이터를 검색하므로 비동기 검색을 제어하는 백그라운드 스레드 및 기타 변수를 초기화해야 합니다. 다음 예와 같이 Fragment UI가 표시되기 직전에 호출되는 onActivityCreated()에서 초기화를 실행하세요.

Kotlin

    class ContactsFragment :
            Fragment(),
            LoaderManager.LoaderCallbacks<Cursor> {
        ...
        // Called just before the Fragment displays its UI
        override fun onActivityCreated(savedInstanceState: Bundle?) {
            // Always call the super method first
            super.onActivityCreated(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 onActivityCreated(Bundle savedInstanceState) {
            // Always call the super method first
            super.onActivityCreated(savedInstanceState);
            ...
            // Initializes the loader
            getLoaderManager().initLoader(0, null, this);
    

onCreateLoader() 구현

initLoader()를 호출한 후 즉시 로더 프레임워크에 의해 호출되는 onCreateLoader() 메서드를 구현합니다.

onCreateLoader()에서 검색 문자열 패턴을 설정합니다. 문자열을 패턴으로 만들려면 '%'(퍼센트) 문자를 삽입하여 0개 이상의 문자 시퀀스를 표시하거나 '_'(밑줄) 문자를 사용하여 단일 문자를 표시하거나 또는 두 가지를 모두 표시할 수 있습니다. 예를 들어 '%Jefferson' 패턴은 'Thomas Jefferson' 및 'Jefferson Davis' 모두와 일치합니다.

메서드에서 새로운 CursorLoader를 반환합니다. 콘텐츠 URI의 경우 Contacts.CONTENT_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()
        }
    

자바

        ...
        @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()를 호출합니다. 이 메서드에서 결과 CursorSimpleCursorAdapter에 넣습니다. 이렇게 하면 ListView가 검색결과로 자동으로 업데이트됩니다.

Kotlin

        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() 메서드가 호출됩니다. 기존 CursorSimpleCursorAdapter 참조를 삭제합니다. 삭제하지 않으면 로더 프레임워크에서 Cursor를 재활용하지 않아서 메모리 누수가 발생합니다. 예를 들면 다음과 같습니다.

Kotlin

        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 설정
  • 특정 연락처 리스너 설정
  • Cursor 열 색인의 상수 정의

    다른 표에서 데이터를 검색하더라도 프로젝션에 포함된 열의 순서는 동일하므로 Cursor의 경우와 동일한 색인을 사용할 수 있습니다.

  • onItemClick() 메서드 정의
  • 로더 초기화
  • onLoadFinished() 및 onLoaderReset() 구현

다음 단계에서는 검색 문자열을 특정 세부 데이터 유형과 일치시키고 결과를 표시하기 위해 필요한 추가 코드를 보여줍니다.

데이터 유형 및 표 선택

특정 세부 데이터 유형을 검색하려면 데이터 유형의 맞춤 MIME 유형 값을 알아야 합니다. 각 데이터 유형에는 CONTENT_ITEM_TYPE 상수에 의해 정의된 고유 MIME 유형 값이 있으며 이는 데이터 유형과 관련된 ContactsContract.CommonDataKinds의 서브클래스에 있습니다. 서브클래스에는 자신의 데이터 유형을 나타내는 이름이 있습니다. 예를 들어 이메일 데이터의 서브클래스는 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
    )
    

자바

        @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 유형 값: 앞에서 설명한대로 이 값은 CONTENT_ITEM_TYPE 상수이며 ContactsContract.CommonDataKinds 서브클래스에 포함되어 있습니다. 예를 들어 이메일 데이터의 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}'"
    

자바

        /*
         * 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("")
    

자바

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

자바

    @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() 구현

새로운 CursorLoader를 반환하는 onCreateLoader() 메서드를 구현합니다. 연락처 제공자가 자동으로 변환하므로 검색 문자열을 패턴으로 변환하지 않아도 됩니다. Contacts.CONTENT_FILTER_URI를 기본 URI로 사용하고 Uri.withAppendedPath()를 호출하여 검색 문자열을 첨부합니다. 이 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()
        }
    

자바

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

이러한 코드 스니펫은 연락처 제공자를 광범위하게 검색하는 앱의 기반이 됩니다. 이 기술은 피플 앱의 연락처 목록 화면과 유사한 기능을 구현하려는 앱에 유용합니다.