I am just a small Android dish, write technical documents just to summarize their knowledge learned in the recent, never dare to be a teacher, if there are some incorrect places please point out, thank you!
1. An overview of the
IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService: IntentService They are similar in that they can execute time-consuming tasks in a new thread without blocking the main thread. The difference is that AsyncTask can track the execution process and results of tasks and display them in the main thread.
We will use a simple example to demonstrate how to use AsyncTask, and then analyze its internal implementation through source code.
2. How to use AsyncTask
AsyncTask: AsyncTask: AsyncTask: AsyncTask: AsyncTask
/**
* <p>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.</p>
*
* <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler}
* and does not constitute a generic threading framework. AsyncTasks should ideally be
* used for short operations (a few seconds at the most.) If you need to keep threads
* running for long periods of time, it is highly recommended you use the various APIs
* provided by the <code>java.util.concurrent</code> package such as {@link Executor},
* {@link ThreadPoolExecutor} and {@link FutureTask}.</p>
*/
public abstract class AsyncTask<Params.Progress.Result> {... }Copy the code
AsyncTask is a helper class designed based on threads and handlers that allows background threads to execute time-consuming tasks and publish progress and results to the main Thread, but is generally suitable for relatively short tasks that take no more than a few seconds to execute.
2.1 Usage Example
Let’s start with some sample code:
private class DownloadAsyncTask extends AsyncTask<String.Integer.Long> {
@Override
public void onPreExecute(a) {
mProgress.setVisibility(View.VISIBLE);
mProgress.setMax(100);
mProgress.setProgress(0);
}
@Override
public Long doInBackground(String... uris) {
int count = uris.length;
long size = 0;
for (int i = 1; i <= count; i ++) {
try {
// Hibernate for 5 seconds to simulate the download process
Thread.sleep(5 * 1000);
// Assume that each downloaded file is (serial number *100).
size += i * 100;
// Publish progress updates
publishProgress( (100* i )/count);
} catch(InterruptedException ie) { ie.printStackTrace(); }}return size;
}
@Override
public void onProgressUpdate(Integer... progress) {
mProgress.setProgress(progress[0]);
}
@Override
public void onPostExecute(Long result) { mText.setText(String.valueOf(result)); }}Copy the code
This code is mainly to simulate the file download process, update the progress in real time during the download process, and display the total size of the downloaded file on the interface after the download is completed.
This code shows how easy it is to implement asynchronous tasks using AsyncTask. You only need to do two things:
- Determine the types of parameters required throughout the process, including
Params
.Progress
andResult
, corresponding to input parameter, schedule parameter and result parameter respectively. - Implement the necessary callback methods, which must be implemented as
doInBackground
This is where time-consuming tasks are processed, as you can imaginedoInBackground
It must be in the child thread; Other alternative implementations includeonPreExecute
.onProgressUpdate
andonPostExecute
These are all involved in the UI update in the example, so they must be in the main thread.
2.2 Parameter Description
Let’s start with the AsyncTask parameter declaration:
public abstract class AsyncTask<Params.Progress.Result> {... }Copy the code
It can be found that AsyncTask uses generic parameters, and appropriate parameter types should be selected according to requirements. The parameter types used in the example are String,Integer and Long respectively. If a parameter is not needed, Void can be used to represent it. Each parameter is illustrated in a table below:
Parameter declarations | meaning | role | Where it is generated/invoked | Matters needing attention |
---|---|---|---|---|
Params | The input parameters | The client sends start parameters when the task starts | Send in execute() and call in doInBackground(). | Variable parameter type |
Progress | Process parameters | Indicates the current execution progress published by the server during the background execution of a task | Generated in doInBackground() and sent via publishProgess(), called on onProgressUpdate(). | Variable parameter type |
Result | The results of parameter | Execution result sent by the server after a task is executed | Generated in doInBackground() and called in onPostExecute(). |
The parameter types cannot be basic data types and use corresponding wrapper types, such as Integer and Long instead of int and Long for the example Progress and Result parameters.
2.3 Callback Interface
AsyncTask has several important callback interfaces, which are described below:
onPreExecute()
: Run in the main thread, mainly before the background thread starts to execute tasks for some UI initialization, such as the progress bar display, optional implementation, its declaration is as follows:
/**
* Runs on the UI thread before {@link #doInBackground}.
*/
@MainThread
protected void onPreExecute(a) {}Copy the code
doInBackground
: Runs in the background thread. It receives parameters sent by the client, performs time-consuming tasks in the background, and releases the execution progress and results, such as file download tasks. It is an interface that must be implemented during use, and is declared as follows:
/**
* Override this method to perform a computation on a background thread. The
* specified parameters are the parameters passed to {@link #execute}
* by the caller of this task.
*
* This method can call {@link #publishProgress} to publish updates
* on the UI thread.
*
* @param params The parameters of the task.
*
* @return A result, defined by the subclass of this task.
*
*/
@WorkerThread
protected abstract Result doInBackground(Params... params);
Copy the code
publishProgress
: Run in the background thread, mainly to publish the current execution progress of the task, to facilitate the display in the main thread, do not need to re-implement the direct call, its declaration is as follows:
/**
* This method can be invoked from {@link#doInBackground} to * publish updates on the UI thread while the background computation is * still running. Each call to this method will trigger the execution of * {@link #onProgressUpdate} on the UI thread.
*
* {@link #onProgressUpdate} will not be called if the task has been
* canceled.
*
* @param values The progress values to update the UI with.
*/
@WorkerThread
protected final void publishProgress(Progress... values) {
if(! isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS,new AsyncTaskResult<Progress>(this, values)).sendToTarget(); }}Copy the code
onProgressUpdate
: Runs in the main thread, mainly to update the current execution progress, such as updating the progress bar progress, can be implemented, its declaration is as follows:
/**
* Runs on the UI thread after {@link #publishProgress} is invoked.
* The specified values are the values passed to {@link #publishProgress}.
*
* @param values The values indicating progress.
*/
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onProgressUpdate(Progress... values) {}Copy the code
onPostExecute
: Runs in the main thread, mainly to receivedoInBackground
The return execution result is displayed in the main thread, such as the size of the downloaded file, optionally implemented, which is declared as follows:
/**
* <p>Runs on the UI thread after {@link #doInBackground}. The
* specified result is the value returned by {@link #doInBackground}.</p>
*
* <p>This method won't be invoked if the task was cancelled.</p>
*
* @param result The result of the operation computed by {@link #doInBackground}.
*/
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onPostExecute(Result result) {}Copy the code
Here’s a table summarizing the important callbacks:
The callback method | Running threads | role | Execution order | Whether it needs to be reimplemented |
---|---|---|---|---|
onPreExecute | The main thread | Perform initialization before starting background tasks | Start executing first | optional |
doInBackground | A background thread | The result is displayed after the time-consuming background task is complete | OnPreExecute Is executed after the onPreExecute command is executed | Must be implemented |
publishProgress | A background thread | Release the execution progress during task execution | Execute in doInBackground | No implementation, direct call. |
onProgressUpdate | The main thread | Receives progress and processes it in the main thread | Execute after publishProgress | optional |
onPostExecute | The main thread | Receive execution results and process them in the main thread | Execute after doInBackground execution is complete | optional |
3. Principle of AsyncTask
Now that we’ve looked briefly at how AsyncTask is used and the important callback methods, we’ll look at how they are implemented internally, focusing on two aspects: how threads are switched and how callback methods are organized.
Execute () : asynctask.execute () : asynctask.execute () : asynctask.execute () : asynctask.execute () : asynctask.execute () : asynctask.execute () : asynctask.execute () : asynctask.execute () : asynctask.execute ()
/**
* Executes the task with the specified parameters. The task returns
* itself (this) so that the caller can keep a reference to it.
*
* <p>Note: this function schedules the task on a queue for a single background
* thread or pool of threads depending on the platform version. When first
* introduced, AsyncTasks were executed serially on a single background thread.
* Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
* to a pool of threads allowing multiple tasks to operate in parallel. Starting
* {@linkandroid.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being * executed on a single thread to avoid common application errors caused * by parallel execution. If you truly want parallel execution, you can use * the {@link #executeOnExecutor} version of this method
* with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
* on its use.
*
* <p>This method must be invoked on the UI thread.
*/
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
Copy the code
Execute () directly calls executeOnExecutor() to start asynchronous tasks, which we’ll look at later, with a bit of notice in the declaration: Whether the tasks are executed sequentially in a separate background thread or in parallel in a thread pool, depending on the platform. When asyncTasks were first introduced, they were executed sequentially in a single background thread, and the subsequent changes are now executed sequentially by default. If you really want to execute in parallel, you need to call executeOnExecutor() directly and pass in the appropriate Executor. Now back to execute(), since it calls executeOnExecutor(sDefaultExecutor, params) internally, what does this function do?
/**
* <p>This method must be invoked on the UI thread.
*
* @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a
* convenient process-wide thread pool for tasks that are loosely coupled.
* @param params The parameters of the task.
*
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
*
*/
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
If the task is already in the "executing state" or "already completed", throw an exception. This ensures that the task is executed only once.
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)"); }}// Update task status to "executing"
mStatus = Status.RUNNING;
// The method called first in the callback method, since "execute()" is executed in the main thread,
// There is no thread switch so far, so "onPreExecute" is also executed in the main thread.
onPreExecute();
// Pass parameters inside the Executor for execution through several "wraps".
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
Copy the code
The first half of the code is fairly straightforward, doing state checks and updates and then calling the onPreExecute interface. The next two lines of code are pretty confusing at first glance. Don’t worry, don’t panic, step by step analysis, first to understand what mWorker is, take a look at the relevant code:
private final WorkerRunnable<Params, Result> mWorker;
mWorker = new WorkerRunnable<Params, Result>() {
public Result call(a) 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);
}
returnresult; }};private static abstract class WorkerRunnable<Params.Result> implements Callable<Result> {
Params[] mParams;
}
Copy the code
MWorker is a WorkerRunnable object, and WorkerRunnable extends the Callable interface.
/**
* A task that returns a result and may throw an exception.
* Implementors define a single method with no arguments called
* {@code call}.
*
* <p>The {@code Callable} interface is similar to {@link
* java.lang.Runnable}, in that both are designed for classes whose
* instances are potentially executed by another thread. A
* {@code Runnable}, however, does not return a result and cannot
* throw a checked exception.
*
* <p>The {@link Executors} class contains utility methods to
* convert from other common forms to {@code Callable} classes.
*
* @see Executor
* @since 1.5
* @author Doug Lea
* @param <V> the result type of method {@code call}
*/
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Copy the code
The call() method in Callable can return results and check for exceptions, while the run method in Runnable does not. So you can simply think of a Callable as a Runnable, except that the call() method is executed and returns the result. Since WorkerRunnable inherits Callable and adds parameters, you can think of mWorker as a Runnable object that accepts both parameters and returns results upon execution. Now look at what an mFuture is, and the code is as follows:
private final FutureTask<Result> mFuture;
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done(a) {
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); }}};Copy the code
You can see that the mFuture is a FutureTask object and is constructed using mWorker as a parameter.
/**
* A cancellable asynchronous computation. This class provides a base
* implementation of {@link Future}, with methods to start and cancel
* a computation, query to see if the computation is complete, and
* retrieve the result of the computation. The result can only be
* retrieved when the computation has completed; the {@code get}
* methods will block if the computation has not yet completed. Once
* the computation has completed, the computation cannot be restarted
* or cancelled (unless the computation is invoked using
* {@link #runAndReset}).
*
* <p>A {@code FutureTask} can be used to wrap a {@link Callable} or
* {@link Runnable} object. Because {@code FutureTask} implements
* {@code Runnable}, a {@code FutureTask} can be submitted to an
* {@link Executor} for execution.
*/
public class FutureTask<V> implements RunnableFuture<V> {
// Core methods, other methods omitted.
public void run(a) {
if(state ! = NEW || ! U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if(c ! =null && state == NEW) {
V result;
boolean ran;
try {
// Execute the encapsulated callable.call () method and get the calculated result
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if(ran) set(result); }}finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if(s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); }}}Copy the code
FutureTask is used for asynchronous computations that can be started, cancelled, queried, and so on, The core method in run() is to call the encapsulated callable.call () to calculate and get the result.
The mFuture encapsulates the mWorker, so the end result is to call the mworkder.call () method. Now look at the lines of code you didn’t understand before:
mWorker.mParams = params;
exec.execute(mFuture);
Copy the code
The mFuture is executed in a background thread. The mFuture will eventually call the mworker.call () method. Let’s look at what mworker.call () does:
mWorker = new WorkerRunnable<Params, Result>() {
public Result call(a) throws Exception {
// Set task call status to "called"
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
// Perform a time-consuming task in a new thread.
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
// Distribute execution results
postResult(result);
}
returnresult; }};Copy the code
Finally, doInBackground() is executed in a new thread, and the result is distributed via postResult() :
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 AsyncTaskResult<Data> {
final AsyncTask mTask;
finalData[] mData; AsyncTaskResult(AsyncTask task, Data... data) { mTask = task; mData = data; }}Copy the code
PostResult () builds the Message object with getHandler(), wrapping the result as AsyncTaskResult for distribution. Which Handler does getHandler() return?
private Handler getHandler(a) {
return mHandler;
}
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: newHandler(callbackLooper); . }// Call this constructor to create an AsyncTask instance
public AsyncTask(a) {
this((Looper) null);
}
Copy the code
GetHandler () returns the mHandler object, which is created in AsyncTask. Since the AsyncTask instance is created without a callbackLooper, The end result is that mHandler is instantiated with getMainHandler(), which gives the main thread Handler as follows:
private static Handler getMainHandler(a) {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
returnsHandler; }}Copy the code
In a roundabout way, the result of doInBackground() is passed to InternalHandler:
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked"."RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
// Get the execution resultAsyncTaskResult<? > result = (AsyncTaskResult<? >) msg.obj;switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
// Process the result of doInBackground()
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
// Process the execution progress of publishProgress() publishing
result.mTask.onProgressUpdate(result.mData);
break; }}}Copy the code
When InternalHandler receives the result of doInBackground(), it calls result.mtask.finish () to continue processing the result, which is asynctask.finish ().
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
// Call onPostExecute() to process the result of the execution
onPostExecute(result);
}
// Update status to "done"
mStatus = Status.FINISHED;
}
Copy the code
This is where onPostExecute() is finally called, and since mHandler is using the main thread Handler, onPostExecute() is executed on the main thread. Execute()-> onPreExecute()->doInBackground()->onPostExecute().
Careful readers will notice that onProgressUpdate() does not specify where it is called. I will leave it to you to solve this problem.
4. To summarize
This paper first shows the use method of AsyncTask and analyzes its parameter types and important callback methods through a simple example of simulated download task. Finally, it analyzes the source code step by step to understand the internal principle of AsyncTask and how to switch threads in the process of executing tasks. Of course, there are a lot of details in the article is not mentioned, but this will not affect the understanding of its principle, “ignore irrelevant details” is often used in the process of learning source code.