[Android Notes] AsyncTask source code analysis
When I was a beginner in AsyncTask, I wanted to study its implementation source code. I did not understand the source code several times, so I put it on hold. Recently, I have read some source code, such as HandlerThread, IntentService, and AsyncQueryHandler. I have learned a lot. So I want to study AsyncTask again. I didn't expect it to be easy to understand this time...Body:Note: 1. Before reading this article, you must understand the Handler mechanism of android and the thread pool in j. u. c. 2. The usage of AsyncTask is not described in detail. 3. Different Versions of AsyncTask have different content.
First, it is clear that AsyncTask is an abstract class and three generic parameters are accepted. Table sharding represents the parameter type, task progress type, and result type required by the task.
public abstract class AsyncTask
After the developer inherits AsyncTask, The doInbackground method must be rewritten. Other methods, such as onPostExecute, must be rewritten as needed.
There is a static global thread pool variable THREAD_POOL_EXECUTOR. The task in doInbackground of AsyncTask is executed by this thread pool.
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
ThreadPoolExecutor parameters have the following meanings: CORE_POOL_SIZE indicates the number of core threads, MAXIMUM_POOL_SIZE indicates the maximum number of threads in the thread pool, and KEEP_ALIVE indicates that when the number of threads in the thread pool is greater than the number of core threads, when the idle time exceeds KEEP_ALIVE, the excess threads will be recycled. sPoolWorkQueue is the task queue, stores Runnable, and sThreadFactory is the thread factory used to create threads in the thread pool. All these parameters have been defined in AsyncTask:
private static final int CORE_POOL_SIZE = 5; private static final int MAXIMUM_POOL_SIZE = 128; private static final int KEEP_ALIVE = 1; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue
sPoolWorkQueue = new LinkedBlockingQueue
(10);
AsyncTask does not directly use the above thread pool, but implements a layer of "packaging". This class is SerialExecutor, which is a serial thread pool.
/** * An {@link Executor} that executes tasks one at a time in serial * order. This serialization is global to a particular process. */ public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
See its specific implementation:
private static class SerialExecutor implements Executor { final ArrayDeque
mTasks = new ArrayDeque
(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } }
When the execute method is called, package Runnable (add try-finally block) and add it to the queue. Then, judge whether the current mActive is empty. This value is blank for the first call, therefore, the scheduleNext method is called to extract the task header from the queue and hand it to the thread pool THREAD_POOL_EXECUTOR for processing. The processing process is like this. First, execute the original Runnable run method, then execute scheduleNext to retrieve Runnable from the queue. This loop is made until the queue is empty and the mActive is empty again. We found that, after such processing,
All tasks are executed in serial mode..
Therefore, when both AsyncTask call execute, if one of the AsyncTask tasks has a long execution time, this will cause the other AsyncTask tasks to wait in queue and fail to be executed, because they share the same thread pool and the pool executes tasks in sequence.
Next, let's look at other members:
Private static final int MESSAGE_POST_RESULT = 0x1; // current Message Type ---> task completion message private static final int MESSAGE_POST_PROGRESS = 0x2; // current Message Type --> progress message private static final InternalHandler sHandler = new InternalHandler (); private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; // default thread pool private final WorkerRunnable
MWorker; private final FutureTask
MFuture; private volatile Status mStatus = Status. PENDING; // The Current Status private final AtomicBoolean mCancelled = new AtomicBoolean (); private final AtomicBoolean mTaskInvoked = new AtomicBoolean ();
SDefaultExecutor indicates that the default thread pool is a serial pool, mStatus indicates the Status of the current task, and Status indicates an enumeration type:
Public enum Status {/*** Indicates that the task has not been executed yet. */PENDING, // wait for execution/*** Indicates that the task is running. */RUNNING, // run/*** Indicates that {@ link AsyncTask # onPostExecute} has finished. */FINISHED, // execution ended}
Focus on InternalHandler implementation:
private static class InternalHandler extends Handler { @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; } } }
InternalHandler inherits from Handler and rewrites handleMessage. This method performs different processing based on the message type. if the task is completed, the finish method is called. The finish method is based on the task status (canceled or completed) call onCancelled or onPostExecute. These two are the callback methods. Normally, we update the UI Based on the task execution result in onPostExecute. This is why AsyncTask must be created in the UI thread in the document, our Handler must be bound to the loose of the UI thread to update the UI. By default, the loose of the subthread does not exist.
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
If the message is a progress update message (MESSAGE_POST_PROGRESS), The onProgressUpdate method is called.
Next we will pay attention to the following issues:
Where is the doInbackground method called?? When we create an AsyncTask instance, we will call its execute method. Therefore, the doInbackground method should be executed in the execute method to find its implementation:
public final AsyncTask
execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
Instead of calling doInbackground directly, the executeOnExecutor method is called:
public final AsyncTask
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; }
The executeOnExecutor method is used to execute tasks using the specified Thread Pool. Here, of course, sDefaultExecutor is used, that is, SerialExecutor, But we noticed that this method is public, that is
We can manually configure the thread pool to run AsyncTask in parallel. The simplest way is to use the internally defined THREAD_POOL_EXECUTOR.The executeOnExecutor method first checks the current status. If it is not the Pending status, an exception is thrown, and then the State is changed to the Running state. Then the onPreExecute method is called (preprocessing, I believe everyone is familiar with it ).
The critical code is exec.exe cute (mFuture). This line of code is used to execute the tasks defined in mFuture. mFuture is the FutureTask type, and FutureTask is the Runnable implementation class (j. u. c), so it can be used as a parameter of the thread pool execute method. We can find its definition:
mFuture = new FutureTask
(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 occured while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } };
We all know that a Callable implementation class must be passed in when the FutureTask is constructed. The thread finally executes the Callable call method (For details, refer to the java thread concurrency library ), therefore, mWorker must be the implementation class of Callable:
private static abstract class WorkerRunnable
implements Callable
{ Params[] mParams; } mWorker = new WorkerRunnable
() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked return postResult(doInBackground(mParams)); } };
Finally, we found the doInBackground method in the WorkRunnable method !! The last question is:
How is a message sent to Handler?
1. The message after the task is executed is sent using the postResult method:
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult
(this, result)); message.sendToTarget(); return result; }
This AsyncTaskResult encapsulates the data domain and object itself:
private static class AsyncTaskResult { final AsyncTask mTask; final Data[] mData; AsyncTaskResult(AsyncTask task, Data... data) { mTask = task; mData = data; } }2. The task progress update message is sent using the publishProgress method:
protected final void publishProgress(Progress... values) { if (!isCancelled()) { sHandler.obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult (This, values). sendToTarget ();}}
Now, the source code analysis of AsyncTask is complete!
Just do it
Summary:
1. asyncTask encapsulates the thread pool and Handler, but tasks are executed serially by default. Note that multiple AsyncTask instances share the same thread pool (the thread pool is static );
2. AsyncTask is not recommended for highly concurrent tasks. Instead, use the thread pool instead;
3. Be sure to create AsyncTask in the main thread, because the Handler inside AsyncTask must be bound to the Logoff of the main thread during creation;
4. Only time-consuming tasks can be executed in the doInBackground method. Other methods such as onPreExecute and onPostExecute run on the main thread.