This is the 11th day of my participation in Gwen Challenge

How to use AsyncTask

Use the sample

private class DownloadFileTask 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 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, includingParams.ProgressandResult, corresponding to input parameter, schedule parameter and result parameter respectively.
  • Implement the necessary callback methods, which must be implemented asdoInBackgroundThis is where time-consuming tasks are processed, as you can imaginedoInBackgroundIt must be in the child thread; Other alternative implementations includeonPreExecute.onProgressUpdateandonPostExecuteThese are all involved in the UI update in the example, so they must be in the main thread.

Parameter is introduced

public abstract class AsyncTask<Params, Progress, Result> { ... }

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.

Callback interface

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

AsyncTask source code analysis

Going back to the sample code we mentioned at the beginning, once we have defined our AsyncTask, it is very simple to start the task with just one line of code:

new DownloadFileTask().execute(url1, url2, url3);

So let’s start with that. What does execute() do

First, new DownloadFileTask(), which executes the constructor of DownloadFileTask, and therefore must execute the constructor of DownloadFileTask’s parent AsyncTask, that is, AsyncTask() :

/** * Creates a new asynchronous task. This constructor must be invoked on the UI thread. */
public AsyncTask(a) {
    this((Looper) null);
}
Copy the code
public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
        ? getMainHandler()
        : new Handler(callbackLooper);

    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call(a) throws Exception {
            // Set the current task to be executed
            mTaskInvoked.set(true);
            Result result = null;
            try {
                // Sets the priority of thread execution
                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; }}; 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

The constructor’s job is simply to initialize the mWorker and mFuture, the Callable and Future, and associate them so that the mFuture can get the result of the mWorker’s execution or stop the mWorker’s execution.

There are two methods called () and done(). In general, the call() method is executed when the mFuture is executed, and the done() method is executed when the task is finished.

So when will this mFuture be implemented? Execute (Params… params)

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}
Copy the code
@MainThread
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;
    // 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();

    mWorker.mParams = params;
    exec.execute(mFuture);

    return this;
}
Copy the code

This should make it clear that mStatus defaults to PENDING, so when the task starts to execute, it first changes its status to RUNNING; Meanwhile, we can also see from the exception judgment that the execute method of an AsyncTask cannot be executed twice at the same time.

Next, onPreExecute(), we’re starting AsyncTask in onCreate, so it’s still on the main thread, and onPreExecute() is going to work on the main thread, so we can do some preparatory things in this method, initialization.

MWorker, as mentioned earlier, implements the Callable interface and adds a parameter attribute to which we assign the parameters passed in from executor. Exec =sDefaultExecutor=SerialExecutor, where the task is actually executed; The mFuture task will be executed as described earlier, and therefore the mWorker’s call method will be executed.

mWorker = new WorkerRunnable<Params, Result>() {
    public Result call(a) throws Exception {
        // Set the current task to be executed
        mTaskInvoked.set(true);
        Result result = null;
        try {
            // Sets the priority of thread execution
            > > >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; }};Copy the code

At this point, we finally see the familiar doInBackground, which is a method we have to implement to do time-consuming operations and return results. Since the priority of Process has been set, this method will be in the background Process. In doInBackground, we can also return the current execution progress

@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;
    }
Copy the code

We called publishProgress to send the progress of the time-consuming task in doInBackground, which we know will be sent to the onProgressUpdate() method, On Progress Date we can easily make UI updates, such as progress bar progress updates, etc. So how did he do it? This depends on the implementation of the publishProgress method.

@WorkerThread
protected final void publishProgress(Progress... values) {
    if(! isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS,new AsyncTaskResult<Progress>(this, values)).sendToTarget(); }}Copy the code

AsyncTaskResult is the result of AsyncTask, which is a static inner class with two properties mTask and mData.

@SuppressWarnings({"RawUseOfParameterizedType"})
    private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        finalData[] mData; AsyncTaskResult(AsyncTask task, Data... data) { mTask = task; mData = data; }}}Copy the code

Therefore, new AsyncTaskResult in publishProgress creates an AsyncTaskResult whose two properties are the current AsyncTask and the task execution progress.

The logic is clear: if the current task has not been canceled, fetch an instance of Message from the Message pool, At the same time, set msg.what=MESSAGE_POST_PROGRESS of the Message object, msg.obj as an AsyncTaskResult object, and finally execute sendToTarget method. We know that the sendXXX method works the same way. All it does is insert the Message object into MessageQueue and wait for Looper’s loop method to retrieve it. Since we start AsyncTask execution on the main thread, once we insert a message into the queue, Handler’s handleMessage method is executed. So let’s look at your implementation of InternalHandler.

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; }}}Copy the code

The result is simply fetched in the handleMessage and cast to AsyncTaskResult. When msg.what=MESSAGE_POST_PROGRESS, The result should be executed. MTask. OnProgressUpdate (result. MData); MTask is the current AsyncTask, so the onProgressUpdate method declared in AsyncTask is executed. In this way, parameters are passed from a child thread to the UI thread, making it very easy for developers to use this for related business.

Back in mWorker’s call() method, postResult is finally executed after doInBackground is executed.

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}
Copy the code

This method, like the publishProgress logic, wraps result into an AsyncTaskResult object and inserts it into MessageQueue as an obJ property of the Message object. Just MSG. What = MESSAGE_POST_RESULT.

This brings us to InternalHandler’s handleMessage, this time msg.what=MESSAGE_POST_RESULT. When execution result. MTask. Finish (result. MData [0]).

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}
Copy the code

The onPostExecute(result) method is executed when the task is not cancelled. So onPostExecute(result) is the last method that we’re going to execute, and that’s where we’re going to get the final result; And mark the task status as FINISHED.

other

Serial or parallel?

In SimpleAsyncTask, we use private static final Executor Executor. = Executors newCachedThreadPool () as the thread pool, and, in fact, the source code of the default thread pool is the custom, This class is SerialExecutor, and Serial means Serial by name, so AsyncTask is Serial by default. In addition, AsyncTask has a thread pool, THREAD_POOL_EXECUTOR, which is used when parallelism is needed.

If none of these satisfy our requirements, we can also customize our own thread pool to meet our business requirements and change the default thread pool using setDefaultExecutor(Executor Exec).

In executeOnExecutor we can also pass in our own custom thread pool:

/ / the same as the default in order to execute asyncTask. ExecuteOnExecutor (Executors. NewSingleThreadExecutor ()); / / unlimited Executor asyncTask. ExecuteOnExecutor (Executors. NewCachedThreadPool ()); / / at the same time perform the Executor of the 10 asyncTask. ExecuteOnExecutor (Executors. NewFixedThreadPool (10));Copy the code

The postResultIfNotInvoked does what?

The AsyncTask has a lot of logic that interferes with the interpretation of the source code. The postResultIfNotInvoked is one of them. It’s actually a bug that Google fixed to ensure that onCancelled() will still execute smoothly if the cancel() method is called too early, see StackOverflow article.

If you use this thing to exit the page you have to cancel it otherwise you’ll know something’s wrong when you use the next page and you’ve screwed me up one more time

reference

Understand the principle of AsyncTask

An in-depth analysis of AsyncTask

AsyncTask: a star-crossed little sparrow