What AsyncTask does in fact, we all understand, here I will not say, today is mainly to analyze its source code, and then understand what pits we will actually use the process.

Let’s talk briefly about how AsyncTask is used.

Use AsyncTask
  • 1. Write a class inheriting AsyncTask. There are three generic parameters of AsyncTask, namely, “input parameter of executing task”, “progress of executing task in background”, and “final result parameter of executing task in background”. Not every parameter should be used. When not used, java.lang.Void is used instead.

    private class MyAsyncTask extends AsyncTask<String,Integer ,String>{ .... }Copy the code
  • 2. Invoke the execute method to execute the task. The execute method enters the task input parameters

            MyAsyncTask mTask = new MyAsyncTask();  
             mTask.execute("http://www.jianshu.com");
    Copy the code
  • 3. The following analyzes the use of each method inside AsyncTask

    Private Class MyAsyncTask extends AsyncTask<String,Integer,String>{private Class MyAsyncTask extends AsyncTask<String,Integer,String>{private Class MyAsyncTask extends AsyncTask<String,Integer,String>{ Override protected void onPreExecute() {super.onpreexecute (); } // Override protected String doInBackground(String... params) { //publishProgress(50); Call onProgressUpdate (); return null; Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); } @override protected void onPostExecute(String s) {super.onPostexecute (s); } @override protected void onCancelled() {super.oncancelled (); }}Copy the code

1.execute(Params… Params) to execute an asynchronous task, requiring us to call this method in our code to trigger the execution of the asynchronous task.

2. OnPreExecute (), in the execute (Params… Params is executed immediately after being called. It is used to perform some UI operations before performing background tasks, such as prompting the user to start downloading. This is the main thread, which can directly update the UI

3.doInBackground(Params… Params), executed immediately after onPreExecute(), for time-consuming operations that take input arguments and return computed results. Call publishProgress during execution (Progress… Values) to update progress information. Do not update the UI here

4.onProgressUpdate(Progress… Values), on the call publishProgress(Progress… Values), this method is executed to update the progress information directly to the UI component.

5. OnPostExecute (Result Result). When the background operation ends, this method will be called, and the calculation Result will be passed to this method as a parameter, and the Result will be directly displayed on the UI component.

The UI should be updated while executing tasks onCancelled at the main thread, which can be directly updated.

Next we from the source analysis, it is how to achieve, in the process of using what shortcomings, and how to optimize.

AsyncTask source code analysis

Let’s take a look at the AsyncTask constructor

Public AsyncTask() {mWorker = new WorkerRunnable<Params, Result>() { public Result call() 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); } return result; }}; //FutureTask implements the Futrure interface MFuture = new FutureTask<Result>(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 occurred while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); }}}; }Copy the code

If you look at the constructors, one is WorkerRunnable that implements Callable, and the other is FutureTask, and if you know about Java multithreading, asynchronous tasks should be pretty easy to understand, so I’ll talk about this briefly, In the WorkerRunnable class, doInBackground(mParams) is executed in the Call method, which is used to perform the time-consuming task we mentioned earlier; The Done method of the FutureTask class calls back as soon as the time-consuming task is completed. The callback uses the postResultIfNotInvoked(Get ()); The get() method returns an execution result. This method is a blocking method that does not return a result until a time-consuming task has been executed, and a blocking method that does not return an execution result until the execution is complete. PostResultIfNotInvoked (get())) the method comes back but I’ll talk about how the task is invoked;

When we call execute(Params… Params) to start an asynchronous task; Take a look at the source code:

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

This is just calling the executeOnExecutor method, and this method takes two arguments, the second argument params is the argument to execute the asynchronous task, and I’m not going to go into that, but the first one sDefaultExecutor is a thread pool;

 private static volatile Executor sDefaultExecutor=SERIAL_EXECUTOR;

 public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

 public static final Executor THREAD_POOL_EXECUTOR;
//这个是静态代码块,AsyncTask初始化时就先执行这一块代码
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

ThreadPoolExecutor is a thread pool, and this class is explained briefly;

  • CorePoolSize: The number of core threads in the thread pool. By default, the number of core threads will remain alive in the thread pool even if they handle idle state. If the allowCoreThreadTimeOut property of ThreadPoolExecutor is set to true, idle core threads will execute a timeout policy while waiting for a new task to arrive, specified by keepAliveTime. When the wait time exceeds the keepAliveTime specified, the core thread is terminated.
  • MaximumPoolSize: The maximum number of threads that can be held in the thread pool. When the number of active threads reaches this value, new tasks will be blocked.
  • KeepAliveTime: Specifies the timeout period during which a non-core thread is idle before it is reclaimed. KeepAliveTime also applies to the core thread when the allowCoreThreadTimeOut property of ThreadPoolExecutor is set to true.
  • Unit: specifies the TimeUnit for keepAliveTime. This is an enumeration, usually using timeunit.milliseconds, timeunit.seconds, and timeunit.minutes.
  • WorkQueue: A queue of tasks in a thread pool in which Runnable objects submitted via the execute method of the thread pool are stored.
  • ThreadFactory: a threadFactory that provides the ability to create new threads for a thread pool. ThreadFactory is an interface that has only one method: Thread newThread (Runnable R). RejectExecutionHandler, a less commonly used parameter, should be used when ThreadPoolExecutor is closed or when ThreadPoolExecutor is saturated (the maximum thread pool size is reached and the work queue is full). The execute method will notify the caller by calling the Handler’s rejectExecution method, which by default throws a RejectExecutionException. Now that we know the parameters of the relevant constructors, let’s look at the general rules for ThreadPoolExecutor to perform its tasks: (1) If the number of threads in the thread pool has not reached the number of core threads, a core thread will be directly started to execute the task. (2) If the number of threads in the thread pool has reached or exceeded the number of core threads, the task will be inserted into the task queue and queued for execution. (3) If the task cannot be inserted into the task queue in step (2), this is usually because the task queue is full. If the number of threads does not reach the maximum specified by the thread pool, a non-core thread will be immediately started to execute the task. (4) If the number of threads has reached the maximum specified by the thread pool in step (3), the task will be rejected and ThreadPoolExecutor will call the rejectExecution method of the RejectExecutionHandler to notify the caller. Let’s start by explaining what each parameter in the constructor of ThreadPoolExecutor means.

Let’s go back in and look at this method

@MainThread public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { mStatus = Status.RUNNING; // The method to call back when the task is started. We talked about onPreExecute() earlier when we showed you how to use this; mWorker.mParams = params; exec.execute(mFuture); return this; }Copy the code

By calling execute(Params… Params) and call the executeOnExecutor method. In the executeOnExecutor method, onPreExecute() is called first; This method performs a pre-task callback, doing simple things such as prompting the user to start downloading, and so on. exec.execute(mFuture); This method is key. It submits the mFuture from the AsyncTask constructor to the exec thread pool to start executing the task. This is to encapsulate the time-consuming task and throw it to the thread pool, whose child threads start processing the task based on the task in the task queue. The done method in FutureTask is then called back after the processing is complete; We go back to the postResultIfNotInvoked(Get ()) method;

private void postResultIfNotInvoked(Result result) {
    final boolean wasTaskInvoked = mTaskInvoked.get();
    if (!wasTaskInvoked) {
        postResult(result);
    }
}
Copy the code

In this method, result is the result returned by get(), which is the result of a time-consuming task and then we call postResult(result);

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

A look at this source, do not send a message, hander receives the message processing

private static class InternalHandler extends Handler { public InternalHandler() { super(Looper.getMainLooper()); } @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void handleMessage(Message msg) { AsyncTaskResult<? > result = (AsyncTaskResult<? >) msg.obj; switch (msg.what) { case MESSAGE_POST_RESULT: Result.mtask. finish(result.mdata [0]); result.mdata [0]; break; case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break; }}}Copy the code

After receiving time-consuming tasks, the Hander invokes the Finish method.

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

The finish method calls onPostExecute(result); Now you can see why onPostExecute(result) calls back to the end of the task, and you can update the UI directly in the onPostExecute(result) method.

Well, that’s pretty much the source code for AsyncTask; Here is a summary of the pitfalls of using AsyncTask;

  • AsyncTask maintains a length of 128 buffer queue, buffer queue is full, if the submitted a task to the thread will be thrown RejectedExecutionException.

    private static final BlockingQueue<Runnable> sPoolWorkQueue =
          new LinkedBlockingQueue<Runnable>(128);
    Copy the code
  • Life cycle Many developers assume that an AsyncTask created in an Activity will be destroyed when the Activity is destroyed. However, this is not the case. The AsyncTask executes until the doInBackground() method completes. Then, if cancel(Boolean) is called, the onCancelled(Result Result) method is executed; Otherwise, the onPostExecute(Result Result) method is executed. If the AsyncTask is not canceled before the Activity is destroyed, it may cause the AsyncTask to crash. Because the view it wants to process no longer exists. So, we always have to make sure that the task is canceled before the destruction activity. In summary, we use AsyncTask to ensure that the AsyncTask is cancelled correctly.

In addition, even if we call cancle() correctly, we may not actually cancel the task. Because if there a non-interruptible in doInBackgroud operations, such as BitmapFactory. DecodeStream (), then this will continue to operate.

  • Memory leak 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.

  • As a result, a missing screen rotation or the Activity being killed in the background will cause the Activity to be recreated. The previously running AsyncTask will hold a reference to the previous Activity, which is invalid, and calling onPostExecute() to update the interface will no longer work.

I do Android development for many years, later will be updated on android advanced UI,NDK development, performance optimization and other articles, more please pay attention to my wechat public number: thank you!


image