Working principle of AsyncTask in Android
In the previous blog "AsyncTask usage in Android", we mentioned that AsyncTask is a combination package for Thread and Handler. This article will explain how AsyncTask works.
Source code link https://github.com/android/platform_frameworks_base/blob/master/core/java/android/ OS /AsyncTask.java for AsyncTask
AsyncTask defines some fields at the beginning, as shown below:
Private static final String LOG_TAG = "AsyncTask"; // CPU_COUNT indicates the number of CPU cores in the mobile phone. private static final int CPU_COUNT = Runtime. getRuntime (). availableProcessors (); // Add the number of CPU cores in the mobile phone to 1 as the size of the core thread count of the thread pool used by AsyncTask private static final int CORE_POOL_SIZE = CPU_COUNT + 1; // use CPU_COUNT * 2 + 1 as the maximum number of threads in the thread pool used by AsyncTask. private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE = 1; // instantiate the thread factory ThreadFactory. sThreadFactory is used to create the thread pool private static final ThreadFactory sThreadFactory = new ThreadFactory () {// mCount is of the AtomicInteger type, atomicInteger is an Integer class that provides atomic operations. // ensure that the getAndIncrement method is thread-safe private final AtomicInteger mCount = new AtomicInteger (1 ); // rewrite the newThread method to mark the name of the newly added Thread as "AsyncTask #" and Mark public Thread newThread (Runnable r) {return new Thread (r, "AsyncTask #" + mCount. getAndIncrement () ;}}; // instantiate the blocking queue BlockingQueue, which stores Runnable in the queue and has a capacity of 128 private static final BlockingQueue.
SPoolWorkQueue = new LinkedBlockingQueue
(128); // instantiate the thread pool public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor (CORE_POOL_SIZE, expiration, KEEP_ALIVE, TimeUnit. SECONDS, sPoolWorkQueue, sThreadFactory) according to the parameter defined above );
Through the above Code and comments, we can know that AsyncTask initializes some parameters and instantiates a thread pool with these parameters.THREAD_POOL_EXECUTOR
Note that the thread pool is definedpublic static final
We can see that AsyncTask maintains a static thread pool internally. By default, the actual work of AsyncTask is through thisTHREAD_POOL_EXECUTOR
Completed.
After the above code is executed, AsyncTask has the following statement:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
The above code instantiates a SerialExecutor instance.SERIAL_EXECUTOR
, It is alsopublic static final
. SerialExecutor is an internal class of AsyncTask. The Code is as follows:
// SerialExecutor implements the execute method in the Executor interface, which is used to execute tasks in a serial manner. // execute tasks one by one, instead of performing parallel tasks in private static class SerialExecutor implements Executor {// mTasks is a dual-end queue that maintains Runnable. ArrayDeque has no capacity limit and its capacity can be increased by final ArrayDeque.
MTasks = new ArrayDeque
(); // MActive indicates the currently running task Runnable mActive; public synchronized void execute (final Runnable r) {// The execute method will pass in a Runnable variable r // then we will instantiate an anonymous internal class of Runnable type to encapsulate r, // use the offline method of the queue to add the encapsulated Runnable to the end of the mTasks. offer (new Runnable () {public void run () {try {// execute the r run method, start to execute the task // here the r run method is executed in the thread pool r. run ();} finally {// when the task is completed, call scheduleNext to execute the next Runnable task scheduleNext () ;}}); // only the currently unavailable If (mActive = null) {scheduleNext () ;}} protected synchronized void scheduleNext () {// exit the queue using the poll method of mTasks, delete and return the Runnable in the queue header, // assign the returned Runnable to mActive, // if not empty, then let it be passed as a parameter to the execute method of THREAD_POOL_EXECUTOR to execute if (mActive = mTasks. poll ())! = Null) {THREAD_POOL_EXECUTOR.execute (mActive );}}}
Through the above Code and comments, we can know:
SerialExecutor implements the execute method in the Executor interface, which is used to execute tasks in a serial way, that is, to execute tasks one by one, rather than executing tasks in parallel.
SerialExecutor internally maintains a dual-end queue mTasks that stores Runnable. When the execute method of SerialExecutor is executed, a Runnable variable r is passed in, But mTasks does not directly store r, but a new anonymous Runnable object is created, which calls r internally, in this way, r is encapsulated, And the encapsulated Runnable object is queued through the offer method of the queue and added to the end of mTasks.
SerialExecutor internally stores the currently running task Runnable through mActive. When the execute method of SerialExecutor is executed, a Runnable is first added to the end of the mTasks team. Then, if mActive is null, that is, if no Runnable task is running, the scheduleNext () method is executed. When the scheduleNext method is executed, the system first queues through the poll method in mTasks, deletes and returns the Runnable in the queue header, and assigns the returned Runnable to mActive. If it is not empty, then let it be passed as a parameter to the execute method of THREAD_POOL_EXECUTOR for execution. As a result, we can see that the SerialExecutor actually uses the previously defined Thread PoolTHREAD_POOL_EXECUTOR
For actual processing.
When the Runnable in mTasks is passed as a parameter to THREAD_POOL_EXECUTOR to execute the execute method, the try-finally code segment in the Runnable of the anonymous internal class is executed in the thread pool, that is, execute r. run () method to execute the task. The scheduleNext method is executed in finally no matter whether the task r is normal or an exception is thrown. It is used to execute the next task in mTasks. As a result, we can see that SerialExecutor is a serial execution task, rather than a parallel execution task.
AsyncTask internally defines a Status Enumeration type, as shown below:
Public enum Status {// PENDING indicates that the task PENDING has not been started, // RUNNING indicates that the task has been started RUNNING, // FINISHED indicates that the task has been completed or canceled, in short, the onPostExecute method has been called FINISHED ,}
Under normal circumstances, an AsyncTask will go through three statuses: PENDING-> RUNNING-> FINISHED.
AsyncTask also defines the following fields:
// Message Code private static final int MESSAGE_POST_RESULT = 0x1 for result publishing through Handler; // Message Code private static final int MESSAGE_POST_PROGRESS = 0x2 for progress publishing through Handler; // sDefaultExecutor indicates that AsyncTask uses SERIAL_EXECUTOR as Executor by default. // by default, AsyncTask is a serial execution task rather than a parallel execution task private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; // InternalHandler is a static internal class defined in AsyncTask. It is bound to the Logoff of the main thread and the private static InternalHandler sHandler of the Message Queue; // mWorker is an object that implements the Callable interface, it implements the call method private final WorkerRunnable Of The Callable interface.
MWorker; // A FutureTask object based on mFuture. mWorker must be used as the parameter to instantiate mFuture private final FutureTask.
MFuture; // initial state bit of AsyncTask PENDING private volatile Status mStatus = Status. PENDING; // mCancelled identifies whether the current task has been canceled private final AtomicBoolean mCancelled = new AtomicBoolean (); // mTaskInvoked identifies whether the current task has actually started executing private final AtomicBoolean mTaskInvoked = new AtomicBoolean ();
We will explain the above Code again:
SDefaultExecutor indicates the default thread pool used by AsyncTask to execute tasks. The initial value of sDefaultExecutor is SERIAL_EXECUTOR, indicating that AsyncTask is a serial execution task by default, rather than a parallel execution task.
InternalHandler is a static internal class defined in AsyncTask. Its source code is as follows:
Private static class InternalHandler extends Handler {public InternalHandler () {// The getmainlogoff method of the logoff class is a static method, this method returns the Logoff of the main thread // here, InternalHandler is initialized with the Logoff of the main thread, indicating that InternalHandler is bound to the main thread super (logoff. getMainLooper ();} @ SuppressWarnings ({"unchecked", "RawUseOfParameterizedType"}) @ Override public void handleMessage (Message msg) {AsyncTaskResult
Result = (AsyncTaskResult
) Msg. obj; switch (msg. what) {case MESSAGE_POST_RESULT: // publish the final result. mTask. finish (result. mData [0]); break; case MESSAGE_POST_PROGRESS: // publish the result of the phase processing. mTask. onProgressUpdate (result. mData); break ;}}}
Through the constructor of InternalHandler, we can find that InternalHandler is initialized with the Logoff of the main thread, indicating that InternalHandler is bound to the main thread. The handleMessage method of InternalHandler will be discussed later.
AsyncTask can cancel tasks, so AsyncTask uses FutureTask and its associated Callable. Here we will briefly introduce the two. FutureTask and Callable are common in Java concurrent programming. They can be used to obtain the returned values after a task is executed, or cancel a task in the thread pool. Callable is an interface that defines the call method internally. In the call method, you need to write code to execute specific tasks. At this point, the Callable interface is similar to the Runnable interface, however, the difference is that the Runnable run method does not return values. The Callable call method can specify the return values. The FutureTask class implements both the Callable interface and the Runnable interface. In the FutureTask constructor, You need to input a Callable object to instantiate it. Executor's execute method receives a Runnable object. Because FutureTask implements the Runnable interface, you can pass a FutureTask object to the Executor's execute Method for execution. When the task is completed, the done method of FutureTask will be executed. We can write some logic processing in this method. During task execution, you can call the cancel Method of FutureTask to cancel the task at any time. After the task is canceled, the done method of FutureTask is also executed. We can also use the get method of FutureTask to block the return value of the task (that is, the return value of the Callable call method). If the task is finished, the execution result is returned immediately, otherwise, the call method is blocked.
We will briefly introduce the usage and functions of Callable and FutureTask. Let's look back at the AsyncTask code. MFuture is a FutureTask object, mWorker is a WorkerRunnable object, and WorkerRunnable is an internal class in AsyncTask. The Code is as follows:
private static abstract class WorkerRunnable
implements Callable
{ Params[] mParams;}
It can be seen that WorkerRunnable is actually a Callable with a generic Result specified, so mWorker is also a Callable object. MWorker and mFuture instantiation will be explained later.
The mStatus value is PENDING, indicating that the initial status of AsyncTask is not executed. MCancelled identifies whether the current task is canceled and mTaskInvoked indicates whether the current task is actually started.
Let's take a look at the constructor of AsyncTask:
// The AsyncTask constructor needs to call public AsyncTask () {// instantiate mWorker on the UI thread to implement the call method mWorker = new WorkerRunnable Of The Callable Interface
() {Public Result call () throws Exception {// The call method is executed in a thread in the thread pool, instead of running the // call method in the main thread to start execution, set mTaskInvoked to true, indicating that the task starts to run mTaskInvoked. set (true); // set the thread that executes the call method to the background Thread level Process. setThreadPriority (Process. THREAD_PRIORITY_BACKGROUND); // execute the doInBackground method in the thread of the thread pool, execute the actual task, and return the Result result Result = doInBackground (mParams); Binder. flushPendingCommands (); // pass the execution result to the postResult method return postResult (result) ;}}; // use mWorker to instantiate mFuture = new FutureTask
(MWorker) {@ Override protected void done () {// After the task is completed or canceled, the done method will be executed. 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 );}}};}
MWorker and mFuture are instantiated in the constructor of AsyncTask. The constructor of AsyncTask needs to be called on the UI thread. The above code is described below.
MWorker
As we mentioned earlier, mWorker is actually a Callable object. MWorker is instantiated to implement the call method of the Callable interface. The call method is executed in a thread in the thread pool, rather than in the main thread. Execute the doInBackground method in the thread of the thread pool, execute the actual task, and return the result. After doInBackground is executed, the result is passed to the postResult method. The postResult method will be explained later.
MFuture
MFuture is an object of the FutureTask type and mWorker is used as a parameter to instantiate mFuture. Here, the done method of FutureTask is implemented. We mentioned earlier that when the task of FutureTask is completed or the task is canceled, the done method of FutureTask is executed. We will discuss the logic in the done method later.
After the AsyncTask object is instantiated, we can call the execute method of AsyncTask to execute the task. The execute code is as follows:
@MainThread public final AsyncTask
execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
The execute method has@MainThread
So this method should be executed on the main thread. Through the code above, we found that the execute method simply calls the executeOnExecutor method and passes the sDefaultExecutor as a parameter to the executeOnExecutor. here we can see that sDefaultExecutor is the Executor of the default task execution.
Let's take a look at the executeOnExecutor method. The Code is as follows:
@ MainThread public final AsyncTask
ExecuteOnExecutor (Executor exec, Params... params) {if (mStatus! = Status. PENDING) {switch (mStatus) {case RUNNING: // if the current AsyncTask is already RUNNING, an exception is thrown and a new task throw new IllegalStateException ("Cannot execute task: "+" the task is already running. "); case FINISHED: // if the current AsyncTask has completed running the previous task, an exception is thrown and the new task throw new IllegalStateException (" Cannot execute task: "+" the task has already been executed "+" (a task can be executed only once) ") ;}// set the AsyncTask Status to running Status mStatus = Status. RUNNING; // call the onPreExecute method onPreExecute (); mWorker before the task is actually executed. mParams = params; // The execute method of Executor receives the Runnable parameter. Because mFuture is an instance of FutureTask, // and FutureTask implements both the Callable and Runnable interfaces, therefore, exec can be executed to execute mFuture exec.exe cute (mFuture); // Finally, AsyncTask itself returns return this ;}
The above code is described below:
An AsyncTask instance executes a task and throws an exception when the second task is executed. The executeOnExecutor method checks whether the AsyncTask status is PENDING at the beginning. Only the PENDING status is executed. If it is in another status, it indicates that another existing task is being executed or the task has been completed, in this case, an exception is thrown.
If the task is in PENDING status, it indicates that the AsyncTask has not executed any tasks, the code can continue to execute, and then set the status to RUNNING, indicating that the task is started.
Call the onPreExecute method before executing a task. Because the executeOnExecutor method should run on the main thread, the onPreExecute method here will also run on the main thread. You can perform some UI processing operations in this method.
The execute method of Executor receives the Runnable parameter. Because mFuture is an instance of FutureTask, and FutureTask implements both the Callable and Runnable interfaces, exec can be executed through the execute method. After exec.exe cute (mFuture) is executed, the call method of mWorker will be executed in the exec workflow. We also introduced the internal execution process of the call method when introducing mWorker instantiation, the doInBackground method is executed in the work thread, the result is returned, and the result is passed to the postResult method.
Let's take a look at the postResult method. The Code is as follows:
Private Result postResult (Result result) {@ SuppressWarnings ("unchecked") // get InternalHandler through getHandler, internalHandler binds the main thread // creates a Message = getHandler () whose message Code is MESSAGE_POST_RESULT according to InternalHandler (). obtainMessage (MESSAGE_POST_RESULT, new AsyncTaskResult
(This, result); // send the message to InternalHandler message. sendToTarget (); return result ;}
In the postResult method, get InternalHandler through getHandler and bind InternalHandler to the main thread. The getHandler code is as follows:
private static Handler getHandler() { synchronized (AsyncTask.class) { if (sHandler == null) { sHandler = new InternalHandler(); } return sHandler; } }
After obtaining the InternalHandler object, a Message with the Message Code MESSAGE_POST_RESULT will be created based on InternalHandler. Here, the result returned by doInBackground is passednew AsyncTaskResult (this, result)
It is encapsulated into AsyncTaskResult and used as the obj attribute of message.
AsyncTaskResult is an internal class of AsyncTask. Its code is as follows:
// AsyncTaskResult is used to publish the result or to publish phased data @ SuppressWarnings ({"RawUseOfParameterizedType"}) private static class AsyncTaskResult{// MTask indicates the final AsyncTask mTask result of the current AsyncTaskResult; // mData indicates the stored Data final Data [] mData; AsyncTaskResult (AsyncTask task, Data... data) {// The data here is a variable array mTask = task; mData = data ;}}
After building the message objectmessage.sendToTarget()
Send the message to InternalHandler. Then, the handleMessage method of InternalHandler receives and processes the message as follows:
Public void handleMessage (Message msg) {// msg. obj is AsyncTaskResult type AsyncTaskResult
Result = (AsyncTaskResult
) Msg. obj; switch (msg. what) {case MESSAGE_POST_RESULT: // publish the final result. mTask. finish (result. mData [0]); break ;... result. mTask. onProgressUpdate (result. mData); break ;}}
Msg. obj is of the AsyncTaskResult type, and result. mTask indicates the AsyncTask bound to the current AsyncTaskResult. Result. mData [0] indicates the processing result returned by doInBackground. Pass the result to the finish method of AsyncTask. The finish code is as follows:
Private void finish (Result result) {if (isCancelled () {// if the task is canceled, execute the onCancelled method onCancelled (result );} else {// send the result to onPostExecute method onPostExecute (result);} // Finally, set the Status of AsyncTask to the completion Status mStatus = Status. FINISHED ;}
The finish method first checks whether the AsyncTask is canceled. If the AsyncTask is canceled, the onCancelled (result) method is executed; otherwise, the onPostExecute (result) method is executed. It should be noted that InternalHandler points to the main thread, so its handleMessage method is executed in the main thread, so the finish method here is also executed in the main thread, onPostExecute is also executed in the main thread.
We know the process from task execution to onPostExecute. We know that the doInBackground method is a time-consuming operation in the work thread. This operation may take a long time, and our task may be divided into multiple parts, every time I complete some of the tasks, we can call the publishProgress method of AsyncTask multiple times in doInBackground to publish the stage data.
The publishProgress method code is as follows:
@WorkerThread protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult
(this, values)).sendToTarget(); } }
We need to pass the results of the phase processing into publishProgress, where values is an array of indefinite lengths. if the task is not canceled, a Message object will be created through InternalHandler, the message Code of this Message is marked as MESSAGE_POST_PROGRESS, and AsyncTaskResult is created based on the passed values and used as the obj attribute of the message. Then send the message to InternalHandler, and InternalHandler will execute the handleMessage method to receive and process the message, as shown below:
Public void handleMessage (Message msg) {AsyncTaskResult
Result = (AsyncTaskResult
) Msg. obj; switch (msg. what) {... case MESSAGE_POST_PROGRESS: // publish the results of the staged processing result. mTask. onProgressUpdate (result. mData); break ;}}
In the handleMessage method, when message. what is MESSAGE_POST_PROGRESS, The onProgressUpdate method of AsyncTask is executed in the main thread.
AsyncTask: whether the task is completed or canceled, FutureTask will execute the done method, as shown below:
MFuture = new FutureTask
(MWorker) {@ Override protected void done () {// After the task is completed or canceled, the done method is executed. try {// The task is executed normally and postResultIfNotInvoked (get () is completed ());} catch (InterruptedException e) {// task interruption exception android. util. log. w (LOG_TAG, e);} catch (ExecutionException e) {// throw new RuntimeException ("An error occurred while executing doInBackground ()", e. getCause ();} catch (CancellationException e) {// cancel postResultIfNotInvoked (null );}}};
The postResultIfNotInvoked method is executed no matter whether the task is successfully executed or canceled. The postResultIfNotInvoked code is as follows:
Private void postResultIfNotInvoked (Result result Result) {final boolean wasTaskInvoked = mTaskInvoked. get (); if (! WasTaskInvoked) {// The postResult method postResult (result) is executed only when the call of mWorker is not called );}}
If the call method is complete when AsyncTask is executed normally, mTaskInvoked is set to true, and the postResult method is finally executed in the call method, and then the mFuture done method is entered, then enter the postResultIfNotInvoked method. Because mTaskInvoked has been executed, the postResult method will not be executed.
If the cancel Method of AsyncTask is executed immediately after the execute method of AsyncTask is called (the cancel Method of mFuture is actually executed), The done method is executed and CancellationException is caught, to execute the statementpostResultIfNotInvoked(null)
Because the call method of mWorker has not been executed yet, mTaskInvoked is not false, so null can be passed to the postResult method.
In this way, we will talk about the details in AsyncTask. I hope this article will help you understand the working principles of AsyncTask!