“This is my second day of the Novembermore Challenge.The final text challenge in 2021”


preface

The principle of AsyncTask is one of the most frequently asked questions in Android development interviews. What do you really want to hear when your interviewer asks you about AsyncTask? One is whether you understand the use of AsyncTask, the second is whether you have studied the source code of AsyncTask, we will analyze this problem through the following points!

  1. The use of AsyncTask
  2. AsyncTask source code parsing
  3. Summary of the principle of AsyncTask

The use of AsyncTask

AsyncTask is a lightweight asynchronous class encapsulated in Android. It mainly enables communication between the worker thread and the UI thread. That is, the worker thread performs time-consuming operations and passes the results to the main thread, which then performs related UI operations in the main thread. Use generally divided into three steps:

  • Implement AsyncTask subclasses, overriding corresponding methods as needed
  • Creates an instance object of the AsyncTask subclass
  • The execute method of AsyncTask is manually invoked to trigger the execution of the AsyncTask
/** * Create AsyncTask: Generally, by inheriting the AsyncTask abstract class, you can specify the types of three generic parameters: Params, Progress, Result */
private static class MyAsyncTask extends AsyncTask<Integer.Integer.String> {
  
    /** * Perform initialization operations before performing background tasks, such as resetting data and displaying the loading progress bar */
    @Override
    protected void onPreExecute(a) {
        super.onPreExecute();
    }
  
    /** * Perform time-consuming tasks in the background, such as downloading server resources **@paramParams Is the input parameter of the background task of the same type as the first AsyncTask generic *@return* /
    @Override
    protected String doInBackground(Integer[] params) {
        return null;
    }
  
    /** * Update background task execution progress, callback to UI thread, trigger update ** by manually calling publishProgress method@paramProgress Progress of execution, of the same type as AsyncTask's second generic */
    @Override
    protected void onProgressUpdate(Integer[] progress) {
        super.onProgressUpdate(progress);
    }
  
    /** * Background task execution result, callback to UI thread **@paramResult Execution result of the same type as AsyncTask's third generic */
    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);
    }
  
    /** * Background task cancelled, callback to UI thread */
    @Override
    protected void onCancelled(a) {
        super.onCancelled(); }}/** * The use of AsyncTask */
private void testAsyncTask(a) {
    // Create MyAsyncTask instance
    MyAsyncTask myAsyncTask = new MyAsyncTask();
    // Trigger the execution of asynchronous tasks. This method must be invoked on the UI thread
    myAsyncTask.execute();
}
Copy the code

The above code lists several important AsyncTask methods, and the annotations are clearly marked. The overall execution order is shown below.

Core method Call time The thread of execution
onPreExecute Automatically called before background tasks are executed The UI thread
doInBackground Automatically called when background tasks are executed The worker thread
onProgressUpdate When the publishProgress method is called manually The UI thread
onPostExecute Automatically called when the background task is finished The UI thread
onCanceled Automatically called when a background task is canceled The UI thread

In addition to the above four methods, AsyncTask also provides onCancelled(), which is executed in the main thread. OnPostExecute () is not called when the asynchronous task is cancelled, but onPostExecute() is not. The cancel() method in AsyncTask doesn’t actually cancel the task, it just sets the task to cancel, and we need to terminate the task in doInBackground(). To terminate a thread, call interrupt() only to mark it as an interrupt. You need to mark it inside the thread and then interrupt it.

Considerations for using AsyncTask

  1. Instances of asynchronous tasks must be created in the UI thread, that is, AsyncTask objects must be created in the UI thread.
  2. execute(Params... params)Methods must be called in the UI thread.
  3. Do not call manuallyonPreExecute().doInBackground(Params... params).onProgressUpdate(Progress... values).onPostExecute(Result result)These are the methods.
  4. Not in thedoInBackground(Params... params)Changes information about UI components in.
  5. A task instance can only be executed once, and a second execution will throw an exception.

AsyncTask source code parsing

Familiarity with AsyncTask is not enough to answer this question. Since asked is the principle, the inevitable need to enter the source layer, in order to find out.

Constructor of AsyncTask

public AsyncTask(@Nullable Looper callbackLooper) {
          // Create the main thread handler
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);
          // Initialize the mWorker variable
        //WorkerRunnable is the Callable
      
        interface, so this implements the call method
      
        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; }};// Initialize the mFuture variable
        //mFuture is a FutureTask object that can be used to cancel background tasks and get the results of their execution
        // Pass the above mWorker object as an argument in the constructor. When the mFuture is executed, mWorker's call method is called
        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 does two things. The first is to create a handler for the main thread. The handler for the main thread is important because it plays an important role in the AsyncTask thread switch. The second thing is to initialize the two member variables mWorker and mFuture. And pass mWorker as an argument when initializing the mFuture. MWorker is a Callable object and mFuture is a FutureTask object, and these two variables are held in memory for later use. FutureTask implements the Runnable interface.

The call() method in mWorker performs time-consuming operations, that is, result = doInBackground(mParams); PostResult (result); To the internal Handler to jump to the main thread. In this case, it instantiates two variables and does not enable the execution task.

Execute method of AsyncTask

  public final AsyncTask<Params, Progress, Result> execute(Params... params) {
       //execute will eventually call the executeOnExecutor method
       return executeOnExecutor(sDefaultExecutor, params);
  }
Copy the code

The execute method calls the executeOnExecutor method and passes the parameters sDefaultExecutor and params.

Now look at the executeOnExecutor method:

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
          // Check the status.
          // The execute method cannot be called on an AsyncTask that is executing a task or an AsyncTask that has finished executing a task
          // This is a drawback of AysncTask: an AsyncTask can only call execute 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)"); }}// Set the state to RUNNING
          mStatus = Status.RUNNING;
          // Attention!! This calls the onPreExecute method, which validates that the method we mentioned before will execute in front of the background task
          onPreExecute();
          // Assign the parameter value to mworker.mparams
          mWorker.mParams = params;
          // Key point!!
          // The exec method passes sDefaultExecutor, which means to call the execute method of sDefaultExecutor
          // The argument is the member variable mFuture mentioned in the constructor
          exec.execute(mFuture);
          return this;
  }
Copy the code

The executeOnExecutor method first determines the state, and if it is executable, sets the state to RUNNING. Then the onPreExecute method is called to prepare the task for the user. The core is exec. Execute (mFuture). The exec sDefaultExecutor namely.

SDefaultExecutor Serial thread pool

Check out the sDefaultExecutor definition:

//sDefaultExecutor is a thread pool with the default value SERIAL_EXECUTOR, which is static and globally unique
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
//SERIAL_EXECUTOR is the default implementation of thread pools in AsyncTask
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
Copy the code

Look again at the SerialExecutor class

// A thread pool implementation
private static class SerialExecutor implements Executor {
      //mTasks is an array-based bidirectional linked list that acts as a task cache queue for SerialExecutor
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        // Represents the task currently being executed, which must start empty
        Runnable mActive;
        // The synchronized keyword is used to ensure thread-safety, i.e. multiple asynctasks are queued in sequence
          // where r is the mFuture passed in above
        public synchronized void execute(final Runnable r) {
              // Add a task to the cache queue using the offer method
            mTasks.offer(new Runnable() {
                public void run(a) {
                    try {
                        r.run();
                    } finally{ scheduleNext(); }}});// If mActive is empty, the scheduleNext method is executed
            if (mActive == null) { scheduleNext(); }}protected synchronized void scheduleNext(a) {
            // If the cache queue mTasks is not empty, thread_pool_executor.execute is called
            if((mActive = mTasks.poll()) ! =null) {
                  // Another thread poolTHREAD_POOL_EXECUTOR.execute(mActive); }}}Copy the code

SDefaultExecutor is a serial thread pool for queueing tasks. As you can see from the SerialExecutor source code, an mFuture is an object inserted into the mTasks task queue. When there are no active AsyncTask tasks in mTasks, the scheduleNext method is called to execute the next task. If one AsyncTask task is complete, the next AsyncTask task is continued until all tasks are complete. Analysis shows that it is the thread pool THREAD_POOL_EXECUTOR that actually performs the background tasks.

In this approach, there are two main steps:

  1. Add a new task, the previously instantiated mFuture object, to the queue.
  2. callscheduleNext()Method that calls THREAD_POOL_EXECUTOR to perform tasks for the queue header.

The thread pool THREAD_POOL_EXECUTOR

// The thread pool that actually performs the task
public static final Executor THREAD_POOL_EXECUTOR;
// Number of core threads, Max 4, min 2 (note: SDK versions vary)
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1.4));
// The maximum number of threads is 2n+1, where n is the number of cpus
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
// Non-core threads can live for 30s
private static final int KEEP_ALIVE_SECONDS = 30;
// The blocking queue is LinkedBlockingQueue with a maximum size of 128
private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);
// The thread pool is initialized with a static code block and assigned to THREAD_POOL_EXECUTOR
static {
     ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
     threadPoolExecutor.allowCoreThreadTimeOut(true);
     THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
Copy the code

Analysis here, the train of thought is basically clear, let’s go over it a little bit. AsyncTask calls the execute method, passing through the SerialExecutor thread pool and passing in its member variable, mFuture, but SerialExecutor simply wraps the mFuture as a Runnable task and adds it to the cache queue. The synchronized keyword is used to ensure that multiple asynctasks are enqueued in sequence.

SerialExecutor then takes the queue head task from the cache queue and hands it off to another thread pool, THREADPOOLEXECUTOR, to actually execute it. At this point, the thread has been switched to the worker thread. When the task in the cache queue is actually executed, the mFuture’s run method is called, which in turn calls the mWorker’s call method passed in its constructor

mWorker = new WorkerRunnable<Params, Result>() {
      public Result call(a) throws Exception {
           //mTaskInvoked is true, indicating that the current task has been invoked
           mTaskInvoked.set(true);
           Result result = null;
                try {
                    // Set the priority of the thread to background
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    // key!!
                    // Execute the doInBackground method to perform our own background tasks and store the results to result
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    // Finally execute the postResult method
                    postResult(result);
                }
                returnresult; }};Copy the code

postResult

private void finish(Result result) {
        if (isCancelled()) {
            // If the task is cancelled, call back the onCancelled method
            onCancelled(result);
        } else {
            // Otherwise, call the onPostExecute method
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
Copy the code

This method sends a message to the Handler object. See the source code for the Hanlder object instantiated in AsyncTask

InternalHandler

/** * the default implementation of mHandler */
private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) { AsyncTaskResult<? > result = (AsyncTaskResult<? >) msg.obj;switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // When MESSAGE_POST_RESULT is received, AsyncTask's Finish method is called
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    // Call the onProgressUpdate method when MESSAGE_POST_PROGRESS is received
                    result.mTask.onProgressUpdate(result.mData);
                    break; }}}Copy the code

InternalHandler calls result.mtask. finish(result.mdata [0]) when it receives MESSAGE_POST_RESULT; After executing the doInBackground() method and passing the result, the finish() method is called.

private void finish(Result result) {
        if (isCancelled()) {
            // If the task is cancelled, call back the onCancelled method
            onCancelled(result);
        } else {
            // Otherwise, call the onPostExecute method
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
Copy the code

Make sure that all operations are already on the main thread. MHandler handles only two kinds of messages, a result message and a progress message. The progress message is simple and calls directly back to our AsyncTask onProgressUpdate method.

When the result message is received, the finish method will be called, which determines whether the AsyncTask has been cancelled by isCancelled. If cancelled, the onCancelled method will be called, otherwise the onPostExecute method will be called. Finally, set mStatus to FINISHED to indicate that the current AsyncTask is complete.

Summary of the principle of AsyncTask

AsyncTask is a Handler that implements communication between threads based on two thread pools and one main thread.

  • The first thread pool, SerialExecutor, internally maintains a two-way queue that acts as a task queue and scheduler. It uses the synchronized keyword to ensure that multiple Asynctasks can be queued in sequence and the tasks are removed from the queue head each time for execution.
  • The second thread pool, THREADPOOLEXECUTOR, is the actual executor of the background tasks and can execute them as they come in. The Handler for the main thread is an InternalHandler class, which is responsible for updating the UI on the main thread by messaging the results and progress of the execution of the worker thread tasks to the main thread.

expand

Q&A

  1. Why is AsyncTask created and executed on the main thread?

Because AsyncTask needs to create InternalHandler in the main thread, onProgressUpdate, onPostExecute, onCancelled can update UI normally.

  1. Why AsyncTask is not suitable for very time-consuming tasks?

AsyncTask is actually a thread pool. If a thread is occupied for a long time and is not idle, other threads can only wait, which causes blocking.

  1. The AsyncTask memory leaks

If an AsyncTask is declared as a non-static inner class of an Activity, then an AsyncTask retains a reference to the Activity that created the AsyncTask. If the Activity has been destroyed and the background AsyncTask thread is still executing, it will continue to hold the reference in memory, making the Activity unable to be reclaimed and causing a memory leak.

Consequences of improper Use of AsyncTask

  • Life cycle: AsyncTask does not have a life cycle bound to any component, so when creating an AsyncTask in an Activity or Fragment, call cancel(Boolean) on the Activity/Fragment onDestory();
  • A memory leak
  • Results lost: Cases such as screen rotation or an Activity being killed by the system in the background cause the Activity to be recreated. The previously running AsyncTask (non-static inner class) holds a reference to the previous Activity that is no longer valid. Calling onPostExecute() to update the interface will no longer work.

Three things to watch ❤️

If you find this article helpful, I’d like to invite you to do three small favors for me:

  1. Like, forward, have your “like and comment”, is the motivation of my creation.
  2. Follow the public account “Xiaoxinchat Android” and share original knowledge from time to time
  3. Also look forward to the follow-up article ing🚀