출처: http://androidyongyong.tistory.com/18
이번시간은 AsyncQueryHandler 를 사용하여 콘텐트 프로바이더에서 비동기적 CRUD 작업 처리를 전문으로 하는 유틸리티 클래스이다.
클래스는 UI스레드에서 콘텐트 프로바이드 작업을 떠넘기는 데 사용되고, UI 스레드는 백그라운드 태스크가 완료되면 수신한다.
다음 주제에 대해 배워보도록하자
- 콘텐트 프로바이더의 기본과 동시 접근
- AsyncQueryHandler를 구현하고 사용하는 방법
- 백그라운드 실행에 대한 이해해
콘텐트 프로바이더는 데이터베이스 중심의 네 가지 접근 메서드인 CRUD 접근 방식을 통해 데이터를 읽고, 추가하고, 변경하고,
삭제할 수 있는 인터페이스를 제공한다.
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 | public class EatContentProvider extends ContentProvider { 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 |
ContentResolver는 프로바이더와 동일한 범위의 데이터 접근 메서드(query, insert, delete, update)를 포함한다.
해당 메서드를 호출하기 위해선 Uri의 리졸버로 query 메서드를 호출하면 된다.
public final static Uri CONTENT_URI = Uri.parse("content://com.eat.provider/resource"); |
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)
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | 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) 를 사용해야 한다.
AsyncQueryHandler는 콘텐트 프로바이더에 대한 전체 CRUD 작업 세트에 접근할 떄 사용하기 쉬운 비동기 메커니즘으로 구성된다.
그러나 이것은 안드로이드 플랫폼 최근 버전에서 추가된 몇가지 최신 기능은 지원하지 않는다.
따라서 다음 장에서 설명 할 CursorLoader와 조합해서 사용하는 것이 좋다.
즉, 데이터 쿼리는 CursorLoader에 의해 처리하고, 삽입/업데이트/삭제는 AsyncQueryHandler에 의해 처리하면 좋다.
'IT_Programming > Android_Java' 카테고리의 다른 글
[펌] Android Support Library 24.2.0의 버그 (0) | 2016.09.28 |
---|---|
Android 7.0(Nougat)에서 DatePickerDialog 예외사항 (0) | 2016.09.01 |
[펌] Understanding VectorDrawable pathData commands in Android (0) | 2016.08.31 |
[펌] 개발자를 위한 안드로이드 누가(Nougat) 변경점 (0) | 2016.08.25 |
[Admob] Admob 배치시 Required XML attribute "adSize"was missing 오류 발생 (0) | 2016.08.16 |