티스토리 뷰

이번시간은 AsyncQueryHandler 를 사용하여 콘텐트 프로바이더에서 비동기적 CRUD 작업 처리를 전문으로 하는 유틸리티 클래스이다. 클래스는 UI스레드에서 콘텐트 프로바이드 작업을 떠넘기는 데 사용되고, UI 스레드는 백그라운드 태스크가 완료되면 수신한다.


다음 주제에 대해 배워보도록하자

  • 콘텐트 프로바이더의 기본과 동시 접근
  • AsyncQueryHandler를 구현하고 사용하는 방법
  • 백그라운드 실행에 대한 이해해
13.1 콘텐트 프로바이더에 대한 간략한 소개
콘텐트 프로바이더는 데이터베이스 중심의 네 가지 접근 메서드인 CRUD 접근 방식을 통해 데이터를 읽고, 추가하고, 변경하고, 삭제할 수 있는 인터페이스를 제공한다.

커스텀 프로바이더
public class EatContentProvider extends ContentProvider {
    private final static String STRING_URI = "content://com.eat.provider/resource";
    public final static Uri CONTENT_URI = Uri.parse(STRING_URI);

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selections, String sortOrder) {
         // 데이터 소스 읽기
         return null;
     }

     @Override
    public Uri insert(Uri uri, ContentValues values) {
        //데이터 추가
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        //데이터 제거
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        //데이터 변경
        return 0;
    }

}

SQLite 데이터베이스는 응용 프로그램 전용이지만, ContentProvider 클래스를 통해 다른 응용프로그램에도 노출할 수 있다.

콘텐트 프로바이더의 정의는 AndroidManifest.xml 에서 수행된다. 여기서 다른 응용프로그램에서 접근하는 여부는 exported 속성으로 결정한다.


<provider

    android:name="EatContentProvider"

    android:authorities="com.eat.provider"

    android:exported="true" />


ContentResolver는 프로바이더와 동일한 범위의 데이터 접근 메서드(query, insert, delete, update)를 포함한다.

해당 메서드를 호출하기 위해선 Uri의 리졸버로 query 메서드를 호출하면 된다.


public final static Uri CONTENT_URI = Uri.parse("content://com.eat.provider/resource");


ContentResolver cr = getContentResolver();

Cursor c = cr.query(CONTENT_URI, null, null, null, null);


13.2 콘텐트 프로바이더의 백그라운드 처리에 대한 정당성

콘텐트 프로바이더는 데이터에 접근하는 클라이언트의 개수 또는 얼마나 많은 클라이언트를 동시에 처리할 수 있는지 제어할 수 없다. 스레드로부터 안전하지 않으면 여러 프로바이더에 의해서 동시 접근이 데이터 불일치로 이어질 수 있다.

그러나 SQLite 데이터 베이스 접근은 데이터베이스의 트렌잭션 모델이 순차적이기 때문에 그 자체로 스레드에 안전하다. 즉, 데이터 동시 접근으로 손상될 수 없다.


미리 쓰기 로깅을 이용해서 뼝렬로 데이터 베이스 접근이 가능하다.

SQLiteDatabase db = SQLiteDatabase.openDatabase(...);

db.enableWriteAheadLogging();

즉, 쓰기는 활성화된 읽기 트랜젝션이 있을 때 원본 데이터베이스에 기록되지 않고 데이터베이스의 복사본에 수행된다.


콘텐트 프로바이더에 대한 접근은 일반적으로 저장소와의 상호작용을 한다. 이 작업은 긴 태스크이기 때문에, UI스레드에서 실행하면 안되고, 백그라운드 스레드가 실행시켜야 한다.

ContentResolver를 호출하는 응용프로그램처럼 프로바이더의 사용자에 의해 백그라운드 스레드에서 생성해야 한다. 다른 프로세스에서 호출 시 바인더 스레드에서 호출한다.


콘텐트 프로바이더에 저장된 데이터는 대부분 UI 스레드에서 처리된다. 그러나 프로바이더는 UI스레드에 직접 접근할 수 없기 때문에 비동기 메커니즘을 이용한다. 안드로이드 플랫폼에서는 프로바이더를 위한 두 가지 특별한 매커니즘이 있는데 AsyncQueryHandler와 CursorLoader가 있다. 


13.3 AsyncQueryHandler 사용

AsyncQueryHandler는 ContentResolver, 백그라운드 실행, 스레드간 메시지 전달을 처리함으로써 콘텐트 프로바이더에 대한 비동기 접근을 간소해 주는 추상 클래스이다.


AsyncQueryHandler의 네 가지 메서드

  • final void startDelete(int token, Object cookie, Uri uri, String selection, String[] selectionArgs)
  • final void startInsert(int token, Object cookie, Uri uri, ContentValues initialValues)
  • final void startQuery(int token, Object cookie, Uri uri, String[] projection, String selection, String orderBy)
  • final void startUpdate(int token, Object cookie, Uri uri, ContentValues values, String selection, String[] selectionArgs)
프로바이더 동작이 완료되면, AsyncQueryHandler로 결과를 반환한다.

Token : 요청 유형, 요청이 생성될 수 있다면 그 유형을 정의한다. 미처리 요청이 취소될 수 있도록 요청을 식별하기도 한다.

Cookie : 요청 식별자 및 임의 객체 유형의 데이터 컨테이너

예제를 통해 사용을 알아보자
예제 : 연락처 확장 리스트
public class ExpandableContentListActivity extends ExpandableListActivity {
    private static final String[] CONTACT_PROJECTION = new String[] {
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME
    };
    private static final int GROUP_ID_COLUMN_INDEX = 0;
    private static final String[] PHONE_NUMBER_PROJECTION = new String[] {
            ContactsContract.CommonDataKinds.Phone._ID,
            ContactsContract.CommonDataKinds.Phone.NUMBER
    };

    private static final int TOKEN_GROUP = 0;
    private static final int TOKEN_CHILD = 1;

    private QueryHandler mQueryHandler;
    private CursorTreeAdapter mAdapter;

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

        //어뎁터 설정
        mAdapter = new MyExpandableListAdapter(this, android.R.layout.simple_expandable_list_item_1,
                android.R.layout.simple_expandable_list_item_1,
                new String[] {ContactsContract.Contacts.DISPLAY_NAME},
                new int[] {android.R.id.text1},
                new String[] {ContactsContract.CommonDataKinds.Phone.NUMBER},
                new int[] {android.R.id.text1});

        setListAdapter(mAdapter);

        mQueryHandler = new QueryHandler(this, mAdapter);

        //사람을 찾기 위한 쿼리
        mQueryHandler.startQuery(TOKEN_GROUP,
                null,
                ContactsContract.Contacts.CONTENT_URI,
                CONTACT_PROJECTION,
                ContactsContract.Contacts.HAS_PHONE_NUMBER,
                null,
                ContactsContract.Contacts.DISPLAY_NAME + "ASC");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mQueryHandler.cancelOperation(TOKEN_GROUP);
        mQueryHandler.cancelOperation(TOKEN_CHILD);
    }

    private static final class QueryHandler extends AsyncQueryHandler {
        private CursorTreeAdapter mAdapter;

        public QueryHandler(Context context, CursorTreeAdapter adapter) {
            super(context.getContentResolver());
            this.mAdapter = adapter;
        }

        @Override
        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
            switch (token) {
                case TOKEN_GROUP:
                    mAdapter.setGroupCursor(cursor);
                    break;
                case TOKEN_CHILD:
                    int groupPosition = (Integer) cookie;
                    mAdapter.setChildrenCursor(groupPosition, cursor);
                    break;
            }
        }
    }


    public class MyExpandableListAdapter extends SimpleCursorTreeAdapter {

        // 생성자가 커서를 인수로 받지 않음을 주목하라
        // 이는 메인 스레드에서 데이터베이스 쿼리를 피하기 위함이다.
        public MyExpandableListAdapter(Context context, int groupLayout, int childLayout, String[] groupFrom, int[] groupTo, String[] childrenFrom, int[] childrenTo) {
            super(context, null,groupLayout, groupFrom, groupTo, childLayout, childrenFrom, childrenTo);
        }


        @Override
        protected Cursor getChildrenCursor(Cursor cursor) {
            // 주어진 그룹에서 모든 하위 항목에 대한 커서를 반환한다.
            // 연락처의 전화번호를 가르키는 커서를 반환한다.
            Uri.Builder builder = ContactsContract.Contacts.CONTENT_URI.buildUpon();
            ContentUris.appendId(builder, cursor.getLong(GROUP_ID_COLUMN_INDEX));
            builder.appendEncodedPath(ContactsContract.Contacts.Data.CONTENT_DIRECTORY);
            Uri phoneNumbersUri = builder.build();

            mQueryHandler.startQuery(TOKEN_CHILD,
                    cursor.getPosition(),
                    phoneNumbersUri,
                    PHONE_NUMBER_PROJECTION,
                    ContactsContract.CommonDataKinds.Phone.MIMETYPE + "=?",
                    new String[] {ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE },
                    null);
            return null;
        }
    }
}


13.3.2 AsyncQueryHandler 이해

AsyncQueryHandler는 ContentResolver 를 보유하고 백그라운드 스레드 안팍으로 스레드 통신을 처리한다. 프로바이더 요청 중 하나가 호출 될때 백그라운드의 메시지 큐에 추가된다.


13.3.3 한계

AsyncQueryHandler의 장점은 간소함이다. 


그러나 다음과 같은 한계가 있다.

  • 일괄작업
    일괄작업 같은 경우는 이를 지원하기 위해 ContentProviderOperation 클래스를 API 레밸 5에 추가하였다. 

  • CancellationSignal
    API 레벨 16은 CancellationSignal 을 추가했다. 이를 사용하면, 쿼리를 취소할 수 있지만, AsyncQueryHandler를 지원하지 않으므로 여전히 cancelOperation(true) 를 사용해야 한다.
13.4 마치며
AsyncQueryHandler는 콘텐트 프로바이더에 대한 전체 CRUD 작업 세트에 접근할 떄 사용하기 쉬운 비동기 메커니즘으로 구성된다. 그러나 이것은 안드로이드 플랫폼 최근 버전에서 추가된 몇가지 최신 기능은 지원하지 않는다. 따라서 다음 장에서 설명 할 CursorLoader와 조합해서 사용하는 것이 좋다. 즉, 데이터 쿼리는 CursorLoader에 의해 처리하고, 삽입/업데이트/삭제는 AsyncQueryHandler에 의해 처리하면 좋다.



댓글
댓글쓰기 폼
공지사항
최근에 달린 댓글
Total
65,392
Today
3
Yesterday
1
TAG
more
«   2022/10   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
글 보관함