IT_Programming/Android_Java

[펌] Using AsyncQueryHandler to Access Content Providers Asynchronously in Android

JJun ™ 2015. 1. 8. 08:50



 출처

 : 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 ContentResolvermethods. 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:

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 Messageobject 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 ListActivity 의 adapter */ public void load(final LoadObserver observer) { AsyncQueryHandler handler = new AsyncQueryHandler(mContext.getContentResolver()) { @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { super.onQueryComplete(token, cookie, cursor); observer.onLoadCursor(cursor); } }; handler.startQuery(0, null, URI, null, null, null, Helper.CREATED + " DESC"); }

    /**
     * 저장하기
     * @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);
    }
 }