Wednesday, December 7, 2011

Android AsyncTask - Threading in its lifecycle.

    AsyncTask is one of the great tool in Android development. If you've read in Android document about AsyncTask I'm sure you've known that this class was designed to help you cooperate works between UI Thread and Background (Worker) Thread.


    Since it was born for helping UI Thread to working with Worker Thread, my question is "Is it possible to use AsyncTask to cooperate works between Worker Threads?"


    Yes, I mean start AsyncTask from other Thread (let say Caller Thread) which is not the UI Thread and also maintains all AsyncTask's lifecycle callbacks in Caller Thread.

    For example, if we implement Http component using AsyncTask and the caller of this component is some network interface components that has its own Thread.


    Well, I've done the experiment that was made up to answer my question already and this is source code from my experiment.


    package org.bd.asynctask;

    import android.app.Activity;

    import android.os.AsyncTask;

    import android.os.Bundle;

    import android.os.SystemClock;

    import android.util.Log;

    import android.widget.TextView;

    /**

    * @author Johnny Dew

    * The Goal of this testing is to check that not only the UI

    * Thread that can start and execute in each callback of AsyncTask but,

    * other Worker Thread can start and operates in each callback as well.

    *

    * First AsyncTask will be started by UI Thread directly Second

    * AsyncTask will be started by other Worker Thread.

    */

    public class AndroidAsyncTaskActivity extends Activity {

    private static final String TAG = "AndroidAsyncTaskActivity";

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    Log.v(TAG, String.format("> onCreate # Main Thread ID: %d", Thread

    .currentThread().getId()));

    // 1 use UI Thread

    Log.v(TAG, "> onCreate # Start BdAsyncTask using UI Thread ...");

    BdAsyncTask async1 = new BdAsyncTask();

    async1.execute();

    // 2 use Worker Thread

    new Thread() {

    @Override

    public void run() {

    SystemClock.sleep(3000);

    Log.v(TAG, "> run # Start BdAsyncTask using Worker Thread ...");

    Log.v(TAG, String.format("> run # Worker Thread ID: %d", Thread

    .currentThread().getId()));

    BdAsyncTask async2 = new BdAsyncTask();

    async2.execute();

    }

    }.start();

    }

    private class BdAsyncTask extends AsyncTask {

    private static final String TAG = "BdAsyncTask";

    @Override

    protected void onPreExecute() {

    Log.d(TAG, String.format("> onPreExecute # Thread ID: %d", Thread

    .currentThread().getId()));

    }

    @Override

    protected Void doInBackground(Void... params) {

    Log.d(TAG, String.format("> doInBackground # Thread ID: %d", Thread

    .currentThread().getId()));

    publishProgress(params);

    return null;

    }

    @Override

    protected void onProgressUpdate(Void... progress) {

    Log.d(TAG, String.format("> onProgressUpdate # Thread ID: %d",

    Thread.currentThread().getId()));

    }

    @Override

    protected void onPostExecute(Void result) {

    Log.d(TAG, String.format("> onPostExecute # Thread ID: %d", Thread

    .currentThread().getId()));

    }

    @Override

    protected void onCancelled() {

    Log.d(TAG, String.format("> onCancelled # Thread ID: %d", Thread

    .currentThread().getId()));

    }

    }

    }


    From this source code, I have two instances of BdAsyncTask - which is inherit from AsyncTask class, and first I start my first BdAsyncTask on the UI Thread and then I start my second BdAsyncTask on the Worker Thread. I also print out Thread ID in each lifecycle callbacks in BdAsyncTask to see which Thread is currently running. Here is the output from LogCat.


    Output when execute first BdAsyncTask that's started by UI Thread


    12-07 23:07:18.306: V/AndroidAsyncTaskActivity(16767): > onCreate # Main Thread ID: 1

    12-07 23:07:18.316: V/AndroidAsyncTaskActivity(16767): > onCreate # Start BdAsyncTask using UI Thread ...

    12-07 23:07:18.316: D/BdAsyncTask(16767): > onPreExecute # Thread ID: 1

    12-07 23:07:18.636: D/BdAsyncTask(16767): > doInBackground # Thread ID: 10

    12-07 23:07:19.527: D/BdAsyncTask(16767): > onProgressUpdate # Thread ID: 1

    12-07 23:07:19.537: D/BdAsyncTask(16767): > onPostExecute # Thread ID: 1


    From result above, you can see that the UI Thread got Thread ID 1 and the Worker Thread in BdAsyncTask got Thread ID 10, Worker Thread is running in doInBackground() and the Thread that running in each BdAsyncTask callback is UI Thread. Nothing serious, isn't it?


    Well, let's take a look at the output of the second BdAsyncTask which is started by Worker Thread (Let's call it "Caller Thread")


    12-07 23:07:21.348: V/AndroidAsyncTaskActivity(16767): > run # Start BdAsyncTask using Worker Thread ...

    12-07 23:07:21.348: V/AndroidAsyncTaskActivity(16767): > run # Worker Thread ID: 11

    12-07 23:07:21.348: D/BdAsyncTask(16767): > onPreExecute # Thread ID: 11

    12-07 23:07:21.358: D/BdAsyncTask(16767): > doInBackground # Thread ID: 12

    12-07 23:07:21.389: D/BdAsyncTask(16767): > onProgressUpdate # Thread ID: 1

    12-07 23:07:21.399: D/BdAsyncTask(16767): > onPostExecute # Thread ID: 1


    From the result, the Caller Thread got Thread ID 11 and the Worker Thread in BdAsyncTask got Thread ID 12. Similarly, the Worker Thread still executing in doInBackground() but, Caller Thread only running in onPreExecute() and another callbacks are executed by UI Thread !


    Interesting, isn't it?


    So how about onCancelled() ? Is it executed by UI Thread or Caller Thread?

    Then I modified my code to let Caller Thread cancel BdAsyncTask from executing, now my source code at the Caller Thread stuff should be like this


    // 2 use Worker Thread

    new Thread() {

    @Override

    public void run() {

    SystemClock.sleep(3000);

    Log.v(TAG, "> run # Start BdAsyncTask using Worker Thread ...");

    Log.v(TAG, String.format("> run # Worker Thread ID: %d", Thread

    .currentThread().getId()));

    BdAsyncTask async2 = new BdAsyncTask();

    async2.execute();

    async2.cancel(true);

    }

    }.start();


    From source code above, I've added async2.cancel(true);


    cancel(boolean) will cause AsyncTask to stop execution and invokes onCancelled() then we will see which Thread is going to execute in onCancelled()


    And here is the result from LogCat


    12-07 23:33:17.597: D/BdAsyncTask(17426): > onPreExecute # Thread ID: 11

    12-07 23:33:17.607: D/BdAsyncTask(17426): > onCancelled # Thread ID: 1


    From the result, you can see that Caller Thread still executing in onPreExecute() but onCancelled is executed by the UI Thread.


    According to AsyncTask document it indicate that each AsyncTask lifecycle callback will be executed on the UI Thread but, how about onPreExecute() ? Why it's a Caller Thread that running this method? - document describe that this method is executed by UI Thread ?!?


    Anyway, I've got the answer for my question.

    It doesn't matter which Thread you are using to start AsyncTask, its lifecycle still executed by the UI Thread - except onPreExecute() ?!?.


    So, AsyncTask does its works which is help us cooperate works between Worker Thread and UI Thread. This class is useful when you want to interact with background task from the UI.


    But this is not a good solution for my requirement since I want to interact each "background" components on background Threads - I don't want to use UI Thread to run my works, So instead of using AsyncTask, using other tools like HandlerThread and Handler might be fit with my requirement ;)


NOTE: This experiment was testing on hTC Nexus One running CM-7.1 (Android 2.3.7)

No comments:

Post a Comment