로더

로더는 Android 9 (API 수준 28)부터 지원 중단됩니다. ActivityFragment 수명 주기를 처리하는 동안 데이터 로드를 처리하는 데 권장되는 옵션은 ViewModel 객체와 LiveData의 조합을 사용하는 것입니다. 뷰 모델은 로더와 같이 구성을 변경할 때도 유지되지만 상용구 코드는 줄어듭니다. LiveData는 여러 뷰 모델에서 재사용할 수 있는 수명 주기 인식 데이터 로드 방법을 제공합니다. MediatorLiveData를 사용하여 LiveData를 결합할 수도 있습니다. Room 데이터베이스의 쿼리와 같이 관찰 가능한 모든 쿼리를 사용하여 데이터의 변경사항을 관찰할 수 있습니다.

ViewModelLiveDataService와 같이 LoaderManager에 액세스할 수 없는 상황에서도 사용할 수 있습니다. 이 두 가지를 함께 사용하면 UI 수명 주기를 처리하지 않고도 앱에서 필요한 데이터에 쉽게 액세스할 수 있습니다. LiveData에 관한 자세한 내용은 LiveData 개요를 참고하세요. ViewModel에 관한 자세한 내용은 ViewModel 개요를 참고하세요.

Loader API를 사용하면 콘텐츠 제공업체 또는 기타 데이터 소스에서 FragmentActivity 또는 Fragment에 표시할 데이터를 로드할 수 있습니다.

로더가 없으면 다음과 같은 문제가 발생할 수 있습니다.

  • 활동이나 프래그먼트에서 직접 데이터를 가져오면 UI 스레드에서 잠재적으로 느린 쿼리를 실행하여 사용자가 응답하지 않는 문제가 발생합니다.
  • AsyncTask 등을 사용해 다른 스레드에서 데이터를 가져오면 다양한 활동 또는 프래그먼트 수명 주기 이벤트(예: onDestroy(), 구성 변경)를 통해 이 스레드와 UI 스레드를 모두 관리해야 합니다.

로더는 이러한 문제를 해결하며 다음과 같은 이점이 있습니다.

  • 로더는 별도의 스레드에서 실행되어 UI가 느리거나 응답하지 않는 것을 방지합니다.
  • 로더는 이벤트가 발생할 때 콜백 메서드를 제공하여 스레드 관리를 간소화합니다.
  • 로더가 유지되고 구성 변경 시 결과를 캐시하여 중복 쿼리를 방지합니다.
  • 로더는 관찰자를 구현하여 기본 데이터 소스의 변경사항을 모니터링할 수 있습니다. 예를 들어 CursorLoader는 자동으로 ContentObserver를 등록하여 데이터가 변경될 때 새로고침을 트리거합니다.

Loader API 요약

앱에서 로더를 사용할 때 관련될 수 있는 여러 클래스와 인터페이스가 있습니다. 다음 표에 이러한 클래스가 요약되어 있습니다.

클래스/인터페이스 설명
LoaderManager 하나 이상의 Loader 인스턴스를 관리하기 위한 FragmentActivity 또는 Fragment와 연결된 추상 클래스입니다. 활동 또는 프래그먼트당 하나의 LoaderManager만 있지만 LoaderManager는 여러 로더를 관리할 수 있습니다.

LoaderManager를 가져오려면 활동 또는 프래그먼트에서 getSupportLoaderManager()를 호출하세요.

로더에서 데이터 로드를 시작하려면 initLoader() 또는 restartLoader()를 호출합니다. 시스템은 동일한 정수 ID를 가진 로더가 이미 존재하는지 자동으로 확인하고 새 로더를 만들거나 기존 로더를 재사용합니다.

LoaderManager.LoaderCallbacks 이 인터페이스에는 로더 이벤트가 발생할 때 호출되는 콜백 메서드가 포함됩니다. 인터페이스는 세 가지 콜백 메서드를 정의합니다.
  • onCreateLoader(int, Bundle): 시스템에 새 로더를 생성해야 할 때 호출됩니다. 코드에서 Loader 객체를 만들어 시스템에 반환합니다.
  • onLoadFinished(Loader<D>, D): 로더가 데이터 로드를 완료하면 호출됩니다. 일반적으로 코드에서 사용자에게 데이터를 표시합니다.
  • onLoaderReset(Loader<D>): 이전에 생성된 로더가 재설정 중이거나 destroyLoader(int)를 호출할 때 또는 활동이나 프래그먼트가 소멸되어 데이터를 사용할 수 없을 때 호출됩니다. 코드에서 로더의 데이터에 대한 참조를 삭제합니다.
활동이나 프래그먼트는 일반적으로 이 인터페이스를 구현하며 initLoader() 또는 restartLoader()를 호출할 때 등록됩니다.
Loader 로더가 데이터 로딩을 수행합니다. 이 클래스는 추상 클래스이며 모든 로더의 기본 클래스 역할을 합니다. Loader를 직접 서브클래스로 분류하거나 다음과 같은 기본 제공 서브클래스 중 하나를 사용하여 구현을 간소화할 수 있습니다.

다음 섹션에서는 애플리케이션에서 이러한 클래스와 인터페이스를 사용하는 방법을 보여줍니다.

애플리케이션에서 로더 사용

이 섹션에서는 Android 애플리케이션 내에서 로더를 사용하는 방법을 설명합니다. 로더를 사용하는 애플리케이션에는 일반적으로 다음이 포함됩니다.

로더 시작

LoaderManagerFragmentActivity 또는 Fragment 내에서 하나 이상의 Loader 인스턴스를 관리합니다. 활동 또는 프래그먼트당 LoaderManager가 하나만 있습니다.

일반적으로 활동의 onCreate() 메서드 또는 프래그먼트의 onCreate() 메서드 내에서 Loader를 초기화합니다. 방법은 다음과 같습니다.

Kotlin

supportLoaderManager.initLoader(0, null, this)

Java

// Prepare the loader.  Either re-connect with an existing one,
// or start a new one.
getSupportLoaderManager().initLoader(0, null, this);

initLoader() 메서드는 다음 매개변수를 사용합니다.

  • 로더를 식별하는 고유한 ID. 이 예에서 ID는 0입니다.
  • 생성 시 로더에 제공할 선택적 인수 (이 예에서는 null).
  • LoaderManager.LoaderCallbacks 구현. LoaderManager가 호출하여 로더 이벤트를 보고합니다. 이 예에서 로컬 클래스는 LoaderManager.LoaderCallbacks 인터페이스를 구현하므로 자체 참조인 this를 전달합니다.

initLoader() 호출은 로더가 초기화되고 활성 상태인지 확인합니다. 이로써 발생할 수 있는 결과가 두 가지 있습니다.

  • ID가 지정한 로더가 이미 존재하는 경우 마지막으로 생성된 로더가 재사용됩니다.
  • ID로 지정된 로더가 없는 경우 initLoader()LoaderManager.LoaderCallbacks 메서드 onCreateLoader()를 트리거합니다. 여기에서 코드를 구현하여 새 로더를 인스턴스화하고 반환합니다. 자세한 내용은 onCreateLoader 섹션을 참고하세요.

두 경우 모두 지정된 LoaderManager.LoaderCallbacks 구현은 로더와 연결되어 있으며 로더 상태가 변경될 때 호출됩니다. 이 호출 시점에 호출자가 시작된 상태이고 요청된 로더가 이미 존재하고 데이터를 생성했다면 시스템은 initLoader() 중에 즉시 onLoadFinished()를 호출합니다. 이를 위해서는 준비가 되어 있어야 합니다. 이 콜백에 관한 자세한 내용은 onLoadFinished 섹션을 참고하세요.

initLoader() 메서드는 생성된 Loader를 반환하지만 이에 대한 참조를 캡처할 필요는 없습니다. LoaderManager는 로더의 수명을 자동으로 관리합니다. LoaderManager는 필요한 경우 로드를 시작 및 중지하고 로더 및 관련 콘텐츠의 상태를 유지합니다.

즉, 로더와 직접 상호작용하는 경우는 거의 없습니다. 가장 일반적으로는 특정 이벤트가 발생할 때 LoaderManager.LoaderCallbacks 메서드를 사용하여 로드 프로세스에 개입합니다. 이 주제에 대한 자세한 설명은 LoaderManager 콜백 사용 섹션을 참고하세요.

로더 다시 시작

위 섹션에서 본 것처럼 initLoader()를 사용할 때는 지정된 ID를 가진 기존 로더가 있으면 이 로더를 사용합니다. 없으면 새로 만듭니다. 그러나 때로는 오래된 데이터를 버리고 다시 시작하고 싶을 때가 있습니다.

이전 데이터를 삭제하려면 restartLoader()를 사용하세요. 예를 들어 다음 SearchView.OnQueryTextListener 구현은 사용자의 쿼리가 변경되면 로더를 다시 시작합니다. 로더를 다시 시작해야 수정된 검색 필터를 사용하여 새 쿼리를 실행할 수 있습니다.

Kotlin

fun onQueryTextChanged(newText: String?): Boolean {
    // Called when the action bar search text has changed.  Update
    // the search filter and restart the loader to do a new query
    // with this filter.
    curFilter = if (newText?.isNotEmpty() == true) newText else null
    supportLoaderManager.restartLoader(0, null, this)
    return true
}

Java

public boolean onQueryTextChanged(String newText) {
    // Called when the action bar search text has changed.  Update
    // the search filter, and restart the loader to do a new query
    // with this filter.
    curFilter = !TextUtils.isEmpty(newText) ? newText : null;
    getSupportLoaderManager().restartLoader(0, null, this);
    return true;
}

LoaderManager 콜백 사용

LoaderManager.LoaderCallbacks는 클라이언트가 LoaderManager와 상호작용할 수 있는 콜백 인터페이스입니다.

로더, 특히 CursorLoader는 중지된 후에도 데이터를 유지해야 합니다. 이를 통해 애플리케이션은 활동 또는 프래그먼트의 onStop()onStart() 메서드 전체에서 데이터를 유지할 수 있으므로 사용자가 애플리케이션으로 돌아올 때 데이터가 새로고침될 때까지 기다릴 필요가 없습니다.

LoaderManager.LoaderCallbacks 메서드를 사용하여 새 로더를 생성하는 시점을 파악하고 로더의 데이터 사용을 중지할 때가 되면 애플리케이션에 알립니다.

LoaderManager.LoaderCallbacks에는 다음 메서드가 포함됩니다.

  • onLoadFinished(): 이전에 생성된 로더가 로드를 완료하면 호출됩니다.
  • onLoaderReset(): 이전에 생성된 로더가 재설정되어 데이터를 사용할 수 없을 때 호출됩니다.

이러한 메서드는 다음 섹션에서 보다 자세하게 설명합니다.

onCreateLoader

initLoader() 등을 통해 로더에 액세스하려고 하면 ID로 지정된 로더가 있는지 확인합니다. 그렇지 않으면 LoaderManager.LoaderCallbacks 메서드 onCreateLoader()를 트리거합니다. 여기에서 새 로더를 만듭니다. 이는 일반적으로 CursorLoader이지만 자체 Loader 서브클래스를 구현할 수 있습니다.

다음 예에서 onCreateLoader() 콜백 메서드는 생성자 메서드를 사용하여 CursorLoader를 만듭니다. 이 메서드에는 ContentProvider 쿼리를 실행하는 데 필요한 전체 정보가 필요합니다. 특히 다음이 필요합니다.

  • uri: 검색할 콘텐츠의 URI입니다.
  • projection: 반환할 열의 목록입니다. null를 전달하면 모든 열이 반환되므로, 비효율적입니다.
  • selection: 반환할 행을 선언하는 필터로, SQL WHERE 절로 형식이 지정됩니다 (WHERE 자체 제외). null를 전달하면 지정된 URI의 모든 행이 반환됩니다.
  • selectionArgs: 선택 항목에 ?s를 포함하면 선택 항목에 표시되는 순서대로 selectionArgs의 값으로 대체됩니다. 값은 문자열로 제한됩니다.
  • sortOrder: SQL ORDER BY 절 형식의 행을 정렬하는 방법입니다 (ORDER BY 자체는 제외). null를 전달하면 기본 정렬 순서가 사용되며 이는 순서가 지정되지 않을 수 있습니다.

Kotlin

// If non-null, this is the current filter the user has provided.
private var curFilter: String? = null
...
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
    // This is called when a new Loader needs to be created.  This
    // sample only has one Loader, so we don't care about the ID.
    // First, pick the base URI to use depending on whether we are
    // currently filtering.
    val baseUri: Uri = if (curFilter != null) {
        Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, Uri.encode(curFilter))
    } else {
        ContactsContract.Contacts.CONTENT_URI
    }

    // Now create and return a CursorLoader that will take care of
    // creating a Cursor for the data being displayed.
    val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" +
            "${Contacts.HAS_PHONE_NUMBER}=1) AND (" +
            "${Contacts.DISPLAY_NAME} != ''))"
    return (activity as? Context)?.let { context ->
        CursorLoader(
                context,
                baseUri,
                CONTACTS_SUMMARY_PROJECTION,
                select,
                null,
                "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC"
        )
    } ?: throw Exception("Activity cannot be null")
}

Java

// If non-null, this is the current filter the user has provided.
String curFilter;
...
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    // This is called when a new Loader needs to be created.  This
    // sample only has one Loader, so we don't care about the ID.
    // First, pick the base URI to use depending on whether we are
    // currently filtering.
    Uri baseUri;
    if (curFilter != null) {
        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                  Uri.encode(curFilter));
    } else {
        baseUri = Contacts.CONTENT_URI;
    }

    // Now create and return a CursorLoader that will take care of
    // creating a Cursor for the data being displayed.
    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
            + Contacts.DISPLAY_NAME + " != '' ))";
    return new CursorLoader(getActivity(), baseUri,
            CONTACTS_SUMMARY_PROJECTION, select, null,
            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}

onLoadFinished

이 메서드는 이전에 생성된 로더가 로드를 완료하면 호출됩니다. 이 메서드는 이 로더에 제공된 마지막 데이터가 공개되기 전에 호출됩니다. 이 시점에서 이전 데이터가 해제될 것이므로 사용 중인 모든 데이터를 삭제합니다. 그러나 데이터를 직접 해제하지 마세요. 로더가 데이터를 소유하고 이를 처리합니다.

로더는 애플리케이션이 더 이상 데이터를 사용하지 않는다는 사실을 알게 되면 데이터를 해제합니다. 예를 들어 데이터가 CursorLoader의 커서인 경우 직접 close()를 호출하지 마세요. 커서가 CursorAdapter에 배치되어 있는 경우 다음 예와 같이 swapCursor() 메서드를 사용하여 이전 Cursor가 닫히지 않도록 합니다.

Kotlin

private lateinit var adapter: SimpleCursorAdapter
...
override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) {
    // Swap the new cursor in. (The framework will take care of closing the
    // old cursor once we return.)
    adapter.swapCursor(data)
}

Java

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter adapter;
...
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // Swap the new cursor in. (The framework will take care of closing the
    // old cursor once we return.)
    adapter.swapCursor(data);
}

onLoaderReset

이 메서드는 이전에 생성된 로더가 재설정되어 데이터를 사용할 수 없을 때 호출됩니다. 이 콜백을 사용하면 데이터가 언제 해제되는지 확인하여 이에 대한 참조를 삭제할 수 있습니다.

이 구현은 값이 nullswapCursor()를 호출합니다.

Kotlin

private lateinit var adapter: SimpleCursorAdapter
...
override fun onLoaderReset(loader: Loader<Cursor>) {
    // This is called when the last Cursor provided to onLoadFinished()
    // above is about to be closed.  We need to make sure we are no
    // longer using it.
    adapter.swapCursor(null)
}

Java

// This is the Adapter being used to display the list's data.
SimpleCursorAdapter adapter;
...
public void onLoaderReset(Loader<Cursor> loader) {
    // This is called when the last Cursor provided to onLoadFinished()
    // above is about to be closed.  We need to make sure we are no
    // longer using it.
    adapter.swapCursor(null);
}

예를 들어 다음은 연락처 콘텐츠 제공자에 관한 쿼리 결과가 포함된 ListView를 표시하는 Fragment의 전체 구현입니다. CursorLoader를 사용하여 제공자에 관한 쿼리를 관리합니다.

이 예는 사용자의 연락처에 액세스하는 애플리케이션에서 제공된 것이므로 매니페스트에 READ_CONTACTS 권한을 포함해야 합니다.

Kotlin

private val CONTACTS_SUMMARY_PROJECTION: Array<String> = arrayOf(
        Contacts._ID,
        Contacts.DISPLAY_NAME,
        Contacts.CONTACT_STATUS,
        Contacts.CONTACT_PRESENCE,
        Contacts.PHOTO_ID,
        Contacts.LOOKUP_KEY
)


class CursorLoaderListFragment :
        ListFragment(),
        SearchView.OnQueryTextListener,
        LoaderManager.LoaderCallbacks<Cursor> {

    // This is the Adapter being used to display the list's data.
    private lateinit var mAdapter: SimpleCursorAdapter

    // If non-null, this is the current filter the user has provided.
    private var curFilter: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        loaderManager.initLoader(0, null, this)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Give some text to display if there is no data.  In a real
        // application, this would come from a resource.
        setEmptyText("No phone numbers")

        // We have a menu item to show in action bar.
        setHasOptionsMenu(true)

        // Create an empty adapter we will use to display the loaded data.
        mAdapter = SimpleCursorAdapter(activity,
                android.R.layout.simple_list_item_2,
                null,
                arrayOf(Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS),
                intArrayOf(android.R.id.text1, android.R.id.text2),
                0
        )
        listAdapter = mAdapter
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        // Place an action bar item for searching.
        menu.add("Search").apply {
            setIcon(android.R.drawable.ic_menu_search)
            setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
            actionView = SearchView(activity).apply {
                setOnQueryTextListener(this@CursorLoaderListFragment)
            }
        }
    }

    override fun onQueryTextChange(newText: String?): Boolean {
        // Called when the action bar search text has changed.  Update
        // the search filter, and restart the loader to do a new query
        // with this filter.
        curFilter = if (newText?.isNotEmpty() == true) newText else null
        loaderManager.restartLoader(0, null, this)
        return true
    }

    override fun onQueryTextSubmit(query: String): Boolean {
        // Don't care about this.
        return true
    }

    override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) {
        // Insert desired behavior here.
        Log.i("FragmentComplexList", "Item clicked: $id")
    }

    override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
        // This is called when a new Loader needs to be created.  This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        val baseUri: Uri = if (curFilter != null) {
            Uri.withAppendedPath(Contacts.CONTENT_URI, Uri.encode(curFilter))
        } else {
            Contacts.CONTENT_URI
        }

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" +
                "${Contacts.HAS_PHONE_NUMBER}=1) AND (" +
                "${Contacts.DISPLAY_NAME} != ''))"
        return (activity as? Context)?.let { context ->
            CursorLoader(
                    context,
                    baseUri,
                    CONTACTS_SUMMARY_PROJECTION,
                    select,
                    null,
                    "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC"
            )
        } ?: throw Exception("Activity cannot be null")
    }

    override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) {
        // Swap the new cursor in.  (The framework will take care of closing the
        // old cursor once we return.)
        mAdapter.swapCursor(data)
    }

    override fun onLoaderReset(loader: Loader<Cursor>) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed.  We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null)
    }
}

Java

public static class CursorLoaderListFragment extends ListFragment
        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {

    // This is the Adapter being used to display the list's data.
    SimpleCursorAdapter mAdapter;

    // If non-null, this is the current filter the user has provided.
    String curFilter;

    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        getLoaderManager().initLoader(0, null, this);
    }

    @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        // Give some text to display if there is no data.  In a real
        // application, this would come from a resource.
        setEmptyText("No phone numbers");

        // We have a menu item to show in action bar.
        setHasOptionsMenu(true);

        // Create an empty adapter we will use to display the loaded data.
        mAdapter = new SimpleCursorAdapter(getActivity(),
                android.R.layout.simple_list_item_2, null,
                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
        setListAdapter(mAdapter);
    }

    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Place an action bar item for searching.
        MenuItem item = menu.add("Search");
        item.setIcon(android.R.drawable.ic_menu_search);
        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        SearchView sv = new SearchView(getActivity());
        sv.setOnQueryTextListener(this);
        item.setActionView(sv);
    }

    public boolean onQueryTextChange(String newText) {
        // Called when the action bar search text has changed.  Update
        // the search filter, and restart the loader to do a new query
        // with this filter.
        curFilter = !TextUtils.isEmpty(newText) ? newText : null;
        getLoaderManager().restartLoader(0, null, this);
        return true;
    }

    @Override public boolean onQueryTextSubmit(String query) {
        // Don't care about this.
        return true;
    }

    @Override public void onListItemClick(ListView l, View v, int position, long id) {
        // Insert desired behavior here.
        Log.i("FragmentComplexList", "Item clicked: " + id);
    }

    // These are the Contacts rows that we will retrieve.
    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
        Contacts._ID,
        Contacts.DISPLAY_NAME,
        Contacts.CONTACT_STATUS,
        Contacts.CONTACT_PRESENCE,
        Contacts.PHOTO_ID,
        Contacts.LOOKUP_KEY,
    };
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created.  This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        Uri baseUri;
        if (curFilter != null) {
            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                    Uri.encode(curFilter));
        } else {
            baseUri = Contacts.CONTENT_URI;
        }

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + Contacts.DISPLAY_NAME + " != '' ))";
        return new CursorLoader(getActivity(), baseUri,
                CONTACTS_SUMMARY_PROJECTION, select, null,
                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in.  (The framework will take care of closing the
        // old cursor once we return.)
        mAdapter.swapCursor(data);
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed.  We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null);
    }
}

사례 더보기

다음 예시는 로더의 사용 방법을 나타냅니다.

  • LoaderCursor: 이전 스니펫의 전체 버전입니다.
  • 연락처 목록 검색: CursorLoader를 사용하여 연락처 제공자에서 데이터를 검색하는 방법을 둘러봅니다.
  • LoaderThrottle: 제한을 사용하여 데이터가 변경될 때 콘텐츠 제공자가 실행하는 쿼리 수를 줄이는 방법의 예입니다.