Issue
AsyncTask has 5 threads rules:
There are a few threading rules that must be followed for this class to work properly:
The AsyncTask class must be loaded on the UI thread. This is done automatically as of Build.VERSION_CODES.JELLY_BEAN.
The task instance must be created on the UI thread.
execute(Params...) must be invoked on the UI thread.
Do not call onPreExecute(), onPostExecute(Result), doInBackground(Params...), onProgressUpdate(Progress...) manually.
The task can be executed only once (an exception will be thrown if a second execution is attempted.)
However, I didn't understand rules 2 and 3 very well. I've tried them on the following code:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
log(Thread.currentThread().getName());
new Thread(new Runnable() {
@Override
public void run() {
log(Thread.currentThread().getName());
Task task = new Task();
task.execute();
}
}).start();
}
public class Task extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
return null;
}
}
And this is the result:
09-15 21:27:10.179 3310-3310/com.xxx.test D/com.xxx.test.MainActivity: main
09-15 21:27:10.179 3310-3329/com.xxx.test D/com.xxx.test.MainActivity: Thread-264
I have a question:
Why can I create the task instance and call the execute()
method in another thread(Thread-264) besides the UI Thread (main)?
I read this post, but it don't explain why. Thanks so much!
Solution
An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread.
There are some points that we need to clarify.
- Our computation will be ran on a background thread
- Result of the computation will be published on the UI thread
- They do not prevent developers from creating or invoking
AsyncTask
from non-UI threads
Step 1: When you call
Task task = new Task();
Take a look into AsyncTask source code.
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
First they create a handler which refer to handler of the UI thread, then create a Runnable which call doInBackground
method (our computation here) and then returns a Future (will return result of the computation some point in the future).
Step 2: Then you call
task.execute();
Take a look into AsyncTask source code.
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
onPreExecute()
will be called on calling thread which invoke the AsyncTask (in this case your anonymous thread). Then it executes the Future in its executor.
After the computation is completed, it will call postResult
method.
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
getHandler
in this case refer to handler of the UI thread, so onPostExecute
will always be called on UI thread.
Conclusion:
AsyncTask enables proper and easy use of the UI thread. This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.
Answered By - Son Truong
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.