출처
: http://codetheory.in/using-asyncqueryhandler-to-access-content-providers-asynchronously-in-android/
: http://blog.justoneplanet.info/2011/10/11/asyncqueryhandlerを使ってsqliteのクエリを非同期処理する/
In our previous posts we discussed SQLite access and Content Providers.
Now access to a content provider will involve accessing some sort of persistent storage like a database or a file under the hood.
Accessing a storage can be a long task and hence this should not be executed on the UI thread which will delay the rendering
and could even lead to ANRs. Hence, background threads should be responsible for handling provider’s execution.
Spawning new threads in the ContentProvider
itself is not a good idea especially in the case of query()
as you’ll then have to block and wait for the background thread to return the result (cursor) making it not asynchronous.
Ideally execution must occur in a background thread and then the result communicated back to the UI thread.
Now this could be implemented with Runnables, Threads and Handlers but Android provides us with a special class called
AsyncQueryHandler
just for this purpose.
This abstract class exposes an excellent interface to deal with the situation.
Note: Ideally the background threads should be created by the application component that uses the provider by creating a
ContentResolver
object. If the caller and provider are in the same application process, then the provider methods are invoked
on the same thread as the ContentResolver
methods. However, if the processes are different then the provider implementation
is invoked on binder threads (processes incoming IPCs).
Using AsyncQueryHandler
AsyncQueryHandler
is basically an abstract class that wraps theContentResolver
object and handles background execution of
its operations (CRUD) as well as passing messages (result) from the between threads (background and main/UI).
It has four methods that wraps that of aContentResolver
:
startQuery(int token, Object cookie, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy)
startInsert(int token, Object cookie, Uri uri, ContentValues initialValues)
startUpdate(int token, Object cookie, Uri uri, ContentValues values, String selection, String[] selectionArgs)
startDelete(int token, Object cookie, Uri uri, String selection, String[] selectionArgs)
If you notice carefully, except the first two parameters, all others match their equivalent ContentResolver
methods.
Each of them, when called, execute the equivalent ContentResolver
method on a background thread.
When the provider is done with its operation, it sends the result back toAsyncQueryHandler
that invokes the following callbacks
that your implementation should override.
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 | class MyQueryHandler extends AsyncQueryHandler { public MyQueryHandler(ContentResolver cr) { super (cr); } @Override protected void onQueryComplete( int token, Object cookie, Cursor cursor) { // query() completed } @Override protected void onInsertComplete( int token, Object cookie, Uri uri) { // insert() completed } @Override protected void onUpdateComplete( int token, Object cookie, int result) { // update() completed } @Override protected void onDeleteComplete( int token, Object cookie, int result) { // delete() completed } } |
The last parameter for all the callbacks are results which has the same type as that of the return value of the underlying
ContentResolver
methods (query()
, insert()
, update()
, delete()
).
Let’s discuss the first two methods of the calls and callbacks:
- Token: It’s a user-defined request code that identifies what kind of a request this is. This can be used with
cancelOperation()
to cancel unprocessed requests submitted with that token. - Cookie: It’s a data container object that can be passed from the request to the response (callback) so that it can be used for some purpose like identifying the request if necessary.
AsyncQueryHandler
is mostly created and executes provider operations on the UI thread, but it can be done on any other thread.
The callbacks arealways called on the thread that creates the AsyncQueryHandler
.
Let’s get in action with some code now. We’ll convert the query()
code from our content provider article to make use of
AsyncQueryHandler
:
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 | // MainActivity.onCreate() // AsyncQueryHandler object AsyncQueryHandler queryHandler = new AsyncQueryHandler(getContentResolver()) { @Override protected void onQueryComplete( int token, Object cookie, Cursor cursor) { int idIndex = cursor.getColumnIndex(UserDictionary.Words._ID); int wordIndex = cursor.getColumnIndex(UserDictionary.Words.WORD); int localeIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE); if (cursor == null ) { // Some providers return null if an error occurs whereas others throw an exception } else if (cursor.getCount() < 1 ) { // No matches found } else { while (cursor.moveToNext()) { int id = cursor.getInt(idIndex); String word = cursor.getString(wordIndex); String locale = cursor.getString(localeIndex); // Dumps "ID: 1 Word: NewWord Locale: en_US" // I added this via Settings > Language & Input > Personal Dictionary Log.d(TAG, "ID: " + id + " Word: " + word + " Locale: " + locale); } } } }; // Construct query and execute // "projection" defines the columns that will be returned for each row String[] projection = { UserDictionary.Words._ID, // Contract class constant for the _ID column UserDictionary.Words.WORD, // Contract class constant for the word column UserDictionary.Words.LOCALE // Contract class constant for the locale column }; // Defines WHERE clause columns and placeholders String selectionClause = UserDictionary.Words._ID + " = ?" ; // Define the WHERE clause's placeholder values String[] selectionArgs = { "1" }; queryHandler.startQuery( 1 , null , UserDictionary.Words.CONTENT_URI, projection, selectionClause, selectionArgs, UserDictionary.Words.DEFAULT_SORT_ORDER // "frequency DESC" ); |
It’s all pretty straightforward. We create a AsyncQueryHandler
object that implements the onQueryComplete()
callback.
On the object we executestartQuery()
which is similar to ContentResolver.query()
except the first two arguments which are
token (1
) and cookie (null
). The entire query operation happens in the background and finally when the result set is ready,
onQueryComplete()
is called in the UI thread where you can use thecursor
however you want to (display results or just log).
Similarly you can also insert, update or delete records using relevant methods and callbacks.
Under the Hood
It’ll actually help us to understand how things work behidn the scenes with regards to AsyncQueryHandler
.
When one of these methods is called –startQuery()
, startInsert()
, startUpdate()
, startDelete()
– a Message
object is created that is populated with the ContentResolver
arguments,cookie
object data and the token
which
becomes the what
field of theMessage
(can be later used to cancelOperation()
).
This Message
object is added to the MessageQueue
of a single background thread. AllAsynQueryHandler
instances within an
application adds these provider requests messages to this same queue on a single background thread.
This is not really problematic as accessing providers (eventually data sources) are not very long tasks as a network operation.
Once the background thread processes a request, the result is passed back in a Message
object to the calling
AsyncQueryHandler
instance. Then the data is extracted and passed to the respective callbacks.
This also means all the provider requests are handled sequentially.
Wrapping Up
In this article we discussed how AsyncQueryHandler
can be used to so conveniently to execute our provider’s operations in a
background thread without blocking the UI thread. In the next article we’ll dive into another concept called CursorLoader
that
can be used for similar purpose.
AsyncQueryHandler 를 사용하여 SQLite 쿼리를 비동기적으로 처리
특히 무거운 쿼리는 사용하지 않는 것이지만 우선 AsyncQueryHandler 를 사용하여 SQLite 쿼리를 비동기 적으로 처리한다.
■ 구현
다음과 같이 manifest 파일의 application 요소 내에 provider를 추가한다.
<provider android:name=".Provider" android:authorities="info.justoneplanet.android.sample.provider" <!-- 액세스하기위한 URI --> android:exported="false"/> <!-- 비공개로하지 않으면 다른 응용 프로그램에서 접근 할 수있다 --> |
보안에 주의한다.
Helper.java
class Helper extends SQLiteOpenHelper { private static final int VERSION = 1; private static final String FILENAME = "info.justoneplanet.android.sample.db"; static final String TABLENAME = "tbl"; static final String ID = "_id"; static final String CREATED = "created"; static final String NAME = "name"; static Helper INSTANCE = null; private Helper(Context context, CursorFactory factory) { super(context, FILENAME, factory, VERSION); } public static Helper getInstance(Context context, CursorFactory factory) { if (INSTANCE == null) { INSTANCE = new Helper(context, factory); } return INSTANCE; } @Override public void onCreate(SQLiteDatabase db) { db.execSQL( "CREATE TABLE `" + TABLENAME + "`(" + " `" + ID + "` INTEGER PRIMARY KEY AUTOINCREMENT," + " `" + CREATED + "` INTEGER," + " `" + NAME + "` TEXT" + ");" ); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } |
Provider.java
다음과 같이 ContentProvider 클래스를 상속받은 Provider 클래스를 만든다.
public class Provider extends ContentProvider { @Override public String getType(Uri uri) { return null; } @Override public boolean onCreate() { return false; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Helper helper = Helper.getInstance(getContext(), null); SQLiteDatabase sdb = helper.getReadableDatabase(); Cursor cursor = sdb.query( Helper.TABLENAME, new String[]{Helper.ID, Helper.NAME, Helper.CREATED}, selection, selectionArgs, null, null, sortOrder, null ); return cursor; } @Override public Uri insert(Uri uri, ContentValues values) { Helper helper = Helper.getInstance(getContext(), null); SQLiteDatabase sdb = helper.getWritableDatabase(); sdb.insert(Helper.TABLENAME, null, values); getContext().getContentResolver().notifyChange(uri, null); return null; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Helper helper = Helper.getInstance(getContext(), null); SQLiteDatabase sdb = helper.getWritableDatabase(); int rows = sdb.update(Helper.TABLENAME, values, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return rows; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Helper helper = Helper.getInstance(getContext(), null); SQLiteDatabase sdb = helper.getWritableDatabase(); int rows = sdb.delete(Helper.TABLENAME, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return rows; } } |
Table.java
public class Table { private static final Uri URI = Uri.parse("content://info.justoneplanet.android.sample.provider/"); private Context mContext; public Table(Context context) { mContext = context; }
/**
* 저장하기
* @return
*/
public void add(String name) {
ContentValues contentValues = new ContentValues();
contentValues.put(Helper.NAME, name);
contentValues.put(Helper.CREATED, new Date().getTime());
AsyncQueryHandler handler = new AsyncQueryHandler(mContext.getContentResolver()) {
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
super.onQueryComplete(token, cookie, cursor);
}
};
handler.startInsert(0, null, URI, contentValues);
} /**
* 일정 시간 경과 한 것을 삭제
* @param created
* @return
*/
public void delete(long created) {
AsyncQueryHandler handler = new AsyncQueryHandler(mContext.getContentResolver()) {
};
handler.startDelete(0, null, URI, Helper.CREATED + " < ?", new String[]{String.valueOf(created)});
} /**
* helper를 통해 db를 close하기
*/
public void close() {
Helper.getInstance(mContext, null).close();
} public interface LoadObserver { public void onLoadCursor(Cursor cursor); } } |
'IT_Programming > Android_Java' 카테고리의 다른 글
[펌] GCM Architecture (0) | 2015.01.13 |
---|---|
[펌] Asynchronous Background Execution and Data Loading with Loaders (Framework) in Android (0) | 2015.01.10 |
[펌] HTML5 Canvas base64 데이타를 Android Bitmap으로 사용하기 (0) | 2015.01.02 |
Branding the EdgeEffect aka Hit the Wall with Your Own Color (0) | 2015.01.01 |
[펌] Android MediaCodec을 이용한 디코딩 예제! (0) | 2014.12.30 |