[source code analysis] usage and rules of AsyncTask

Introduction

AsyncTask, I believe you are familiar with it. It encapsulates Thread and Handler internally, which allows us to put some time-consuming operations into AsyncTask and update the results to the UI in time. AsyncTask is mainly used for short-term and time-consuming operations. It is not recommended to use AsyncTask for long-term and time-consuming operations. Here's an official Google example to understand the use of AsyncTask.

An example

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
  protected void onPreExecute() {
		showProgress();
  }

  protected Long doInBackground(URL... urls) {
      int count = urls.length;
      long totalSize = 0;
      for (int i = 0; i < count; i++) {
          totalSize += Downloader.downloadFile(urls[i]);
          publishProgress((int) ((i / (float) count) * 100));
          // Escape early if cancel() is called
          if (isCancelled()) break;
      }
      return totalSize;
  }

  protected void onProgressUpdate(Integer... progress) {
      setProgressPercent(progress[0]);
  }

  protected void onPostExecute(Long result) {
      showDialog("Downloaded " + result + " bytes");
  }
 }
//Copy code

AsyncTask is an abstract class, we must customize a class to inherit from it when we want to use it. The prototype of AsyncTask is:

public abstract class AsyncTask<Params, Progress, Result> {
}
//Copy code

It receives three generic parameters, which are parameter type, progress type and result type.

In the above example, the DownloadFilesTask receive parameter type is URL type, the Integer type is used to represent the task progress, and the final task result is a Long type.

Note: the above three generic types do not have to use a specific type. For types that are not used, you can use the Void type instead.

To inherit AsyncTask, we need to override the doInBackground method at least. At the same time, AsyncTask also provides three other methods for us to override, namely onPreExecute, onProgressUpdate and onPostExecute.

  • onPreExecute method. Executed before the task starts executing, it runs in the UI thread. Usually we can show a waiting progress bar here.
  • doInBackground method. Throughout the time-consuming task, it runs in a child thread. Perform time-consuming operations here.
  • onProgressUpdate method. Throughout the time-consuming task, the publishProgress method is executed after it is called and runs in the UI thread. It is usually used to show a progress of the whole task.
  • onProgressUpdate method. After the task is called, the return result of doInBackground is passed to the onPostExecute parameter value, which runs in the main thread. Usually we get the result data after the task is completed.

Rules for AsyncTask

  • The AsyncTask class must be loaded on the UI thread. (it will be automatically completed after 4.1 system version)
  • The AsyncTask object must be created in the UI thread, that is to say, the AsyncTask construction method must be invoked in the UI thread. (the asynctask object can be created in the sub thread after testing, as long as the execute method is guaranteed to execute in the UI thread, it is OK. But no one will do it, because it's too much!!!)
  • The execute method must be invoked in the UI thread. This ensures that the onPreExecute method runs on the UI thread.
  • Do not call the onPreExecute, doInBackground, onProgressUpdate, onProgressUpdate methods actively.
  • Under a single thread, the task of an AsyncTask object can only be executed once, otherwise a runtime error will be reported.

AsyncTask rules for executing tasks

At the beginning of AsyncTask, tasks were executed sequentially in a background thread. Since Android 1.6, it has become possible to execute tasks in parallel in a background thread. Then, in Android 3.0, it changed to single thread sequential execution to avoid the wrong behavior of concurrent tasks.

To verify the above conclusion, let's look at a Demo example.

public class MainActivity extends Activity {
    public static final String TAG = "MyApplication";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyTask("task1").execute();
        new MyTask("task2").execute();
        new MyTask("task3").execute();
        new MyTask("task4").execute();
        new MyTask("task5").execute();
        new MyTask("task6").execute();

    }

    private class MyTask extends AsyncTask<Void, Void, Void> {
        private String taskName;

        MyTask(String taskName) {
            this.taskName = taskName;
        }

        @Override
        protected Void doInBackground(Void... integers) {
            try {
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Log.e(TAG, taskName + " finish at: " + df.format(new Date()));
        }
    }
}
//Copy code

This example is relatively simple. When MainActivity is started, MyTask is executed six times, and the time node after task execution is printed out.

The system version of the mobile phone is Android 8.0. From the Log information above, we can see that AsyncTask is indeed executed serially. Since the minimum system version of the existing testing machine is Android 4.1, it is hard to find an old machine under Android 3.0, so we can only verify whether AsyncTask is executed in parallel between Android 1.6 and Android 3.0 through the source code.

Source code analysis

Android version 2.3

Whether AsyncTask executes in serial or parallel depends on its execute method.

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    ...ellipsis 
    mWorker.mParams = params;
    sExecutor.execute(mFuture);

    return this;
}
//Copy code

In the execute method, it is actually a ThreadPoolExecutor object through sExecutor. Its initialization is as follows.

private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
//Copy code

ThreadPoolExecutor is a multithreaded container where multiple threads can be created to perform multiple tasks. This proves that Android version 1.6 to Android version 3.0 is direct, and the mechanism of AsyncTask to execute tasks is indeed different now, it can let tasks execute in parallel.

Android 8.0

Let's compare the execute method of Android 8.0.

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params) {
    ...ellipsis
    mWorker.mParams = params;
    exec.execute(mFuture);

    return this;
}
//Copy code

The executeOnExecutor method is invoked in the execute method and the sDefaultExecutor is passed as Executor object. The initialization of sDefaultExecutor is shown below.

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    public synchronized void execute(final Runnable r) {
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    r.run();
                } finally {
                    scheduleNext(); //Continue to execute scheduleNext method after task execution
                }
            }
        });
        if (mActive == null) { //The first task performs the method
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) { //Judge whether there is the next task in the mTask queue, and take it out for execution if there is one.
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}
//Copy code

As you can see, in Android 8.0, an ArrayDeque queue is created. Each time, only one task is obtained from the queue for execution. After execution, it will continue to judge whether there are tasks in the queue. If there are any, it will be taken out for execution until all tasks are completed. It can be seen that Android version 8.0 performs tasks serially.

If we want to change the default behavior of AsyncTask, can we modify it? The answer is yes.

We can directly call the executeOnExecutor method of AsyncTask, and pass an Executor object to it, and it will become a parallel execution method.

For the above example, this can be changed.

public class MainActivity extends Activity {
    public static final String TAG = "MyApplication";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyTask("task1").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyTask("task2").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyTask("task3").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyTask("task4").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyTask("task5").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyTask("task6").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

    }

    private class MyTask extends AsyncTask<Void, Void, Void> {
        private String taskName;

        MyTask(String taskName) {
            this.taskName = taskName;
        }

        @Override
        protected Void doInBackground(Void... integers) {
            try {
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Log.e(TAG, taskName + " finish at: " + df.format(new Date()));
        }
    }
}
//Copy code

After execution, the printed Log information is shown in the figure below.

Note: the first five tasks are executed at the same time, because asynctask.thread'pool'executor creates five core threads, and the sixth Task needs to wait for the idle thread to continue execution. Therefore, the execution time of the sixth Task is inconsistent with that of the first five tasks.

Keywords: Android Google Mobile

Added by [/Darthus] on Fri, 25 Oct 2019 09:03:57 +0300