Issue
This was asked in one of the Android interviews. I was asked whether it's possible to start another async task (let it be Task2) from doInBackground() method of async task 1(let it be Task1). I had gone through the docs which say the following:
The task instance must be created on the UI thread.
execute(Params...) must be invoked on the UI thread.
As per these statements, I think that it shouldn't be possible to start a task from background method of another task. Also, async task has UI methods (which cannot be used on a background thread), so that strengthened my argument and I answered it as not possible.
On checking on a simple demo app, I saw that it's indeed possible to do so. Some demo code:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
init();
Log.v ("gaurav", "Thread is : " + Thread.currentThread().getName());
Task1 task = new Task1();
task.execute();
}
class Task1 extends AsyncTask {
@Override
protected Object doInBackground(Object... params) {
// TODO Auto-generated method stub
Log.v ("gaurav", "Thread task 1 is : " + Thread.currentThread().getName());
Task2 task = new Task2();
task.execute();
return null;
}
}
class Task2 extends AsyncTask {
@Override
protected Object doInBackground(Object... params) {
// TODO Auto-generated method stub
Log.v ("gaurav", "Thread task 2 is : " + Thread.currentThread().getName());
Log.v ("gaurav", "Task 2 started");
return null;
}
}
I get following logs indicating successful execution :
> 08-07 09:46:25.564: V/gaurav(2100): Thread is : main 08-07
> 09:46:25.564: V/gaurav(2100): Thread task 1 is : AsyncTask #3 08-07
> 09:46:25.564: V/gaurav(2100): Thread task 2 is : AsyncTask #4 08-07
> 09:46:25.564: V/gaurav(2100): Task 2 started
I have checked this on ICS, KK and L device and it works fine for all.
One reason I could think of is that I'm not overriding any UI methods and doing any UI updation in my second task, hence it doesn't cause any problems, but I'm not sure. Even if that's the case, it violates the threading rules mentioned in the developer guide.
As a reference, I checked out this link too : Start AsyncTask from another AsyncTask doInBackground() but the answer states to start the second task using runOnUiThread() method inside doInBackground(). I'd like some help on what's going on here. Thanks.
Solution
Let's change your code to the following:
class Task1 extends AsyncTask {
@Override
protected Object doInBackground(Object... params) {
// TODO Auto-generated method stub
Log.v ("gaurav", "Thread task 1 is : " + Thread.currentThread().getName());
Task2 task = new Task2();
task.execute();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.v ("gaurav", "Log after sleeping");
return null;
}
}
class Task2 extends AsyncTask {
@Override
protected Object doInBackground(Object... params) {
// TODO Auto-generated method stub
Log.v ("gaurav", "Thread task 2 is : " + Thread.currentThread().getName());
Log.v ("gaurav", "Task 2 Started");
return null;
}
}
Now the LogCat returns:
08-07 06:13:44.208 3073-3073/testapplication V/gaurav﹕ Thread is : main
08-07 06:13:44.209 3073-3091/testapplication V/gaurav﹕ Thread task 1 is : AsyncTask #1
08-07 06:13:49.211 3073-3091/testapplication V/gaurav﹕ Log after sleeping
08-07 06:13:49.213 3073-3095/testapplication V/gaurav﹕ Thread task 2 is : AsyncTask #2
08-07 06:13:49.213 3073-3095/testapplication V/gaurav﹕ Task 2 Started
As you can see the Task 2
is executed after the end of the Task 1
execution (even after sleeping for 5 seconds). It means the second task would not be started until the first one is done.
Why?
The reason is behind the source code of AsyncTask. Please consider the execute()
method:
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
and scheduleNext()
method:
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
The most important keyword in these methods is synchronized
which ensures these methods would be run only in one thread at the same time. When you call the execute
method, it offers a new Runnable
to mTask
which is an instance of ArrayDeque<Runnable>
class which works as a serializer of the different requests at different threads [more info]. If there was no executed Runnable
(i.e. if (mActive == null)
), the scheduleNext()
would be called, otherwise, the scheduleNext()
in the finally
block would be called after the (for any reason) end of current executed Runnable
. All Runnable
s are executed on a separate thread by THREAD_POOL_EXECUTOR
.
What's wrong with the execution of AsyncTask from other threads? Starting with Jelly Bean, an AsyncTask
is class-loaded at application start on the UI thread, so that the callbacks are guaranteed to occur on the UI thread, however, prior to the Jelly Bean release, if another thread creates the AsyncTask
the callbacks may not occur on the correct thread.
So, AsyncTask
implementations should be called from the UI thread only on platforms prior to Jelly Bean (+ and +).
Clarification: please consider the following example which simply clarifies the differences between different platform releases of Android:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
new Thread() {
@Override
public void run() {
Task1 task = new Task1();
task.execute();
}
}.start();
}
class Task1 extends AsyncTask {
@Override
protected Object doInBackground(Object... params) {
return null;
}
}
It works fine on Android 5.1, but crashes with the following exception on Android 2.3:
08-07 12:05:20.736 584-591/github.yaa110.testapplication E/AndroidRuntime﹕ FATAL EXCEPTION: Thread-8
java.lang.ExceptionInInitializerError
at github.yaa110.testapplication.Main2Activity$1.run(Main2Activity.java:21)
Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:421)
at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:421)
at android.os.AsyncTask.<clinit>(AsyncTask.java:152)
at github.yaa110.testapplication.Main2Activity$1.run(Main2Activity.java:21)
Answered By - user1922137
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.