안드로이드 Loader 활용하기

프로그래밍/Android 2013. 9. 2. 12:58

안드로이드 Loader 활용하기



Loader

- 안드로이드 3.0 에서 소개된 Loader 를 사용하면 액티비티와 프래그먼트에서의 비동기 데이터 로딩을 쉽게 처리할 수 있습니다.



특징

- 모든 액티비티와 프래그먼트에서 사용할 수 있습니다.

- 어플리케이션 UI를 Blocking 하지 않도록 비동기 데이터 로딩을 제공합니다.

- 데이터를 모니터 하기 때문에 데이터가 변경되었을때 변경사항을 확인할 수 있습니다.



API

LoaderManager 

- LoaderManager는 액티비티, 프래그먼트 와 1:1 의 관계를 갖습니다. 액티비티 하나당 하나의 LoaderManger 가 존재하는 셈이죠

그리고 하나의 LoaderManager 는 여러개의 Loader 를 관리하게 됩니다.


LoaderManager.LoaderCallbacks

- Loader 를 컨트롤하기 위해 제공되는 콜백 메소드입니다. 콜백 메소드를 통해서 Loader 를 생성하고 변경할 수 있습니다.


Loader

- 비동기 데이터 로딩을 수행하는 추상 클래스입니다.


AsyncTaskLoader

- AsyncTask 를 제공하는 추상 로더입니다.


CursorLoader

- AsyncTaskLoader 의 하위 클래스로서 ContentrResolver 에 쿼리를 하여 Cursor 를 리턴 받도록 합니다.

- AsyncTaskLoader 를 사용하기 때문에  처리하는 작업이 어플리케이션 UI 를 블락킹하지 않습니다.



Loader 적용하기


Loader 시작하기

- Loader 를 초기화하기 위해서 아래코드를 Activity 의 onCreate(), Fragment 의 onActivityCreated() 콜백함수내에 추가한다.

getLoaderManager().initLoader(

0, // Loader 를 구분하기 위한 ID 값

null, // 추가 인자

this // Loader 로 부터 콜백을 받기 위해서 LoaderManager.LoaderCallbacks 를 구현한 객체를 넘겨준다.

);

※ initLoader 메소드는 두가지의 결과를 갖게되는데

- initLoader 에 넘겨준 ID 값을 갖는 Loader 가 이미 존재하는 경우 이미 존재하는 LOader 를 재사용하게 됩니다.

- 존재하지 않는 경우에는 LoaderManager.LoaderCallbacks 인터페이스에 있는 onCreateLoader() 콜백함수를 호출하게 됩니다.


Loader 재시작하기

initLoader 메소드에서 ID 가 이미 존재하는 경우에는 기존 Loader 를 재사용 한다고 했는데 재사용 하더라도 데이터를 초기화하고 싶을 때가 있습니다. 그런 경우에 Loader 를 재사용하며 데이터는 초기화하고 싶을때 재시작하게 됩니다.

// 검색시 검색어가 변경되었을 경우 호출

public boolean onQueryTextChanged( String newText){

mCurFilter = !TextUtils.isEmpty( newText) ? newText : null;

getLoaderManager().restartLoader( 0, null, this);

return true;

}



LoaderManager 콜백 사용하기


LoaderManager.LoaderCallbacks

- onCreateLoader()

initLoader 에 넘겨준 ID 를 갖는 Loader 를 생성하여 넘겨준다. 직접 생성하여야 한다.


- onLoaderFinished()

Loader 가 로딩을 끝내을 때 호출한다.


- onLoaderReset()

Loader 가 리셋 됬을 때 호출한다.


onCreateLoader 예제

public Loader<Cursor> onCreateLoader( int id, Bundle args){

Uri baseUri;

if( mCurFilter != null){

baseUri = Uri.withAppendedPath( Contacts.CONTENT_FILTER_URI, 

Uri.encode( mCurFilter));

} else {

baseUri = Contacts.CONTENT_URI;

}

String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("

+ Contacts.HAS_PHONE_NUMBER + "=1) AND ("

+ Contacts.DISPLAY_NAME + " != '' ))";

return new CursorLoader(

getActivity(),

baseUri, // 가져올 컨텐트의 uri

CONTACTS_SUMMARY_PROJECTION,  // 가져올 컬럼의 정보, null 일 경우 모든 컬럼을 리턴한다.

select, // 가져올 데이터를 필터링 하는 정보 SQL 의 WHERE 절과 유사하다., 모든 데이터를 가져올 경우 null 을 설정한다.

null, // selectionArgs 

Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC" // 정렬 순서 sortOrder

);

}


onLoadFilnished 예제

로딩이 끝난후 adapter 에 Cursor를 설정하는 예제

SimpleCursorAdapter mAdapter;


public void onLoadFinished( Loader<Cursor> loader, Cursor data){

mAdapter.swapCursor( data);

}


onLoaderReset 예제

Loader 가 리셋 되었을 때 호출된다. 그러므로 기존 데이터를 해제해야 한다.

SimpleCursorAdapter mAdapter;


public void onLoaderReset( Loader<Cursor> loader){

mAdapter.swapCursor( null);

}


LoaderManager.LoaderCallbacks 를 구현한 Fragment 예제입니다.

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 mCurFilter;


    @Override public void onActivityCreated(Bundle savedInstanceState) {

        super.onActivityCreated(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);


        // LoaderManger 를 초기화합니다. ID 가 존재하는 경우는 존재하는 Loader 를 재사용합니다.

// Fragment 는 onActivityCreated 메소드내에서 초기화를 진행합니다.

        getLoaderManager().initLoader(0, null, this);

    }


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

// ActionBar 에서 검색시 검색어가 변경되었을 경우에 기존 로더를 재사용하기 위해 Loader의 데이터를 초기화해 줍니다.

        mCurFilter = !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);

    }


    // CursorLoader 에서 가져올 컬럼 정의

    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,

    };


    // Loader 를 생성하는 콜백 메소드. Loader 를 생성해서 넘겨주면 LoaderManager 에서 알아서 실행하게 됩니다.

    @Override 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 (mCurFilter != null) {

            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,

                    Uri.encode(mCurFilter));

        } 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 + " != '' ))";

        // CursorLoader 를 생성해서 넘겨줍니다.

        return new CursorLoader(getActivity(), baseUri, 

                CONTACTS_SUMMARY_PROJECTION, select, null,

                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");

    }

    

    // Loader 작업이 끝난후 결과 데이터를 처리하는 콜백 메소드

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

    }

    

    // Loader 가 리셋되었을때 기존 데이터를 해제하는 콜백 메소드

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

    }

}


: