Android AsyncTask implementation principle and tips sharing

Keywords: Android

Why use AsyncTask

We all write apps on the principle that the main thread cannot run tasks that require a lot of CPU time slices, such as a large number of complex floating-point operations, large disk IO operations, network socket s, etc., which will lead to slow response of our main thread to users and even ANR, which will deteriorate the user experience of the application, However, sometimes these time-consuming tasks do need to be performed, so we can usually use AsyncTask or new Thread
In this way, putting the task into the working thread for execution will not occupy the time slice of the main thread, so the main thread will respond to the user's operation in time. If we use new Thread to execute the task, we need to maintain a lot of additional code if we need to cancel the task execution or return the task execution result, AsyncTask is implemented based on the concurrent classes provided by the concurrent shelf package. The above two requirements have been encapsulated for us, which is why we chose AsyncTask.

How to use AsyncTask

Let's briefly introduce some usage examples of AsyncTask. Let's create a new class DemoAsyncTask to inherit AsyncTask, because AsyncTask is an abstract class, and the doInBackground method must be overridden.

private class DemoAsyncTask extends AsyncTask<String, Void, Void> {
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected Void doInBackground(String... params) {
        return null;
    }   

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
    }

    @Override
    protected void onProgressUpdate(Void... values) {
        super.onProgressUpdate(values);
    }

    @Override
    protected void onCancelled(Void aVoid) {
        super.onCancelled(aVoid);
    }

    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

DemoAsyncTask task = new DemoAsyncTask();
task.execute("demo test AsyncTask");
//task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, "test");
//myTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "test");

Simple analysis
The above is the simplest way to use AsyncTask. Among the methods we rewritten above, onInBackground runs on the working thread and all other methods run on the main thread. In addition, Android provides us with two methods, which are listed above.

1. The first method will use the default Executor to execute our tasks, which is actually SERIAL_EXECUTOR,SERIAL_ In fact, we can customize Executor through methods. The default implementation of Android helps us execute tasks one by one, that is, single thread. There is still a very interesting history about whether AsyncTask's task execution is single thread implementation or multi-threaded implementation. The earlier version was single thread implementation. Starting from Android 2. X, Google changed it to multi-threaded implementation, Later, Google found that if multithreading is implemented, there will be a lot of extra work to ensure thread safety for developers. Therefore, starting from Android 3.0, the default implementation is changed to single thread. Today we demonstrate that the code version of framework is 21 (Android 5.0).

2. It also provides an interface for multithreading implementation, that is, the last line of code we wrote above`
AsyncTask.THREAD_POOL_EXECUTOR.

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

In fact, I believe you often use it in your work and study AsyncTask,Let's go straight to see it AsyncTask Class source code (insert a digression, you can also summarize your experience in work and study and write it down~~)

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

AsyncTask Abstract class has three generic parameter types. The first is the parameter type you need to pass in, and the second is the type of task completion progress. Generally Integer,The third is the return type of the task completion result. Sometimes these parameters are not all required, and the unnecessary parameters are set to Void OK, in addition Result Only one, but Params There can be more than one.
We can see AsyncTask The default constructor of initializes two objects, mWorker and mFuture. 

private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);

            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //noinspection unchecked
            return postResult(doInBackground(mParams));
        }
    };

    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 occured while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                postResultIfNotInvoked(null);
            }
        }
    };

mWoker Realized Callback Interface, Callback Interface is JDK1.5 An interface in the advanced concurrency rack package, which can have a generic return value.

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;
}

FutureTask Realized RunnableFuture Interface, RunnableFuture Interface is JDK Provided. You can know it inherits from the name Runnable and Future Interface, mFuture yes FutureTask An instance of can be directly Executor Class instance execute. Let's keep watching AsyncTask of execute method.

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

    onPreExecute();

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

    return this;
}

Call first onPreExecute()Method, which is still in the main thread(Strictly speaking, call AsyncTask Thread executing),then exec.execute(mFuture),Give the task to exec handle, execute mFuture Actually invoke mWoker,Then call postResult(doInBackground(mParams)),At this time, it is already running in the worker thread pool and will not block the main thread mHandler send out MESSAGE_POST_RESULT Message, then call finish Method, if isCancelled,Callback onCancelled,Otherwise callback onPostExecute. 

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}
private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler {
    @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;
        }
    }
}
private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

Now we've actually AsyncTask The whole task execution process has been completed, and the callback methods exposed to us have also been completed. Now let's look back, AsyncTask Actually, it's just right JDK 1.5 Advanced concurrency features, concurrent A package made by the rack package is convenient for developers to deal with asynchronous tasks. Of course, there are many detail processing methods worth learning, such as feedback on task execution progress, atomic guarantee of task execution, etc. These are left to you to learn by yourself.

use AsyncTask A little skill
 Let's illustrate with an example, "click the button to start downloading QQAndroid Install the package, and then display a dialog box to feedback the download progress ". We first initialize a dialog box. Because we want to display the progress, we use Github The upper progress bar shows the percentage NumberProgressbar,The button to start the task we use* circlebutton*,A button with cool animation, Github There are many very good open source projects above. Of course, cool controls are part of them. Later, I will have the opportunity to learn some popular controls and their implementation principles. Let's take them for the moment~~. 

1.Initialize the progress bar prompt dialog box first.
    builder = new AlertDialog.Builder(
    MainActivity.this);
    LayoutInflater inflater = LayoutInflater.from(MainActivity.this);
    mDialogView = inflater.inflate(R.layout.progress_dialog_layout, null);
    mNumberProgressBar = (NumberProgressBar)mDialogView.findViewById(R.id.number_progress_bar);
    builder.setView(mDialogView);
    mDialog = builder.create();

*   2.Set button click events.
    findViewById(R.id.circle_btn).setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v) {
    dismissDialog();
    mNumberProgressBar.setProgress(0);
    myTask = new MyAsyncTask();
    myTask.execute(qqDownloadUrl);
    }
    });
3.DownloadAsyncTask Implementation, a little long.
    private class DownloadAsyncTask extends AsyncTask<String , Integer, String> {
  @Override
  protected void onPreExecute() {
      super.onPreExecute();
      mDialog.show();

  }

  @Override
  protected void onPostExecute(String aVoid) {
      super.onPostExecute(aVoid);
      dismissDialog();
  }

  @Override
  protected void onProgressUpdate(Integer... values) {
      super.onProgressUpdate(values);

      mNumberProgressBar.setProgress(values[0]);
  }

  @Override
  protected void onCancelled(String aVoid) {
      super.onCancelled(aVoid);
      dismissDialog();
  }

  @Override
  protected void onCancelled() {
      super.onCancelled();
      dismissDialog();
  }

  @Override
  protected String doInBackground(String... params) {
      String urlStr = params[0];
      FileOutputStream output = null;
      try {
          URL url = new URL(urlStr);
          HttpURLConnection connection = (HttpURLConnection)url.openConnection();
          String qqApkFile = "qqApkFile";
          File file = new File(Environment.getExternalStorageDirectory() + "/" + qqApkFile);
          if (file.exists()) {
              file.delete();
          }
          file.createNewFile();
          InputStream input = connection.getInputStream();
          output = new FileOutputStream(file);
          int total = connection.getContentLength();
          if (total <= 0) {
              return null;
          }
          int plus = 0;
          int totalRead = 0;
          byte[] buffer = new byte[4*1024];
          while((plus = input.read(buffer)) != -1){
              output.write(buffer);
              totalRead += plus;
              publishProgress(totalRead * 100 / total);
              if (isCancelled()) {
                  break;
              }
          }
          output.flush();
      } catch (MalformedURLException e) {
          e.printStackTrace();
          if (output != null) {
              try {
                  output.close();
              } catch (IOException e2) {
                  e2.printStackTrace();
              }
          }
      } catch (IOException e) {
          e.printStackTrace();
          if (output != null) {
              try {
                  output.close();
              } catch (IOException e2) {
                  e2.printStackTrace();
              }
          }
      } finally {
          if (output != null) {
              try {
                  output.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
      return null;
  }

```

}
Such a simple download file is basically realized. So far, there is no skill, but now we have a problem, that is, if our Activity Executing a task in the background may take a long time, and the user may click return to exit Activity Or quit App,The background task will not exit immediately if AsyncTask Internal Activity The reference of member variables in will also cause Activity The recovery delay causes memory leakage for a period of time, so we need to add the following fourth step.
  • 4. In onpause, judge whether the application wants to exit, so as to decide whether to cancel the execution of AsyncTask.

    @Override
    protected void onPause() {
    super.onPause();
    if (myTask != null && isFinishing()) {
    myTask.cancel(false);
    }
    }

In this way, our asynchronous tasks will be cancelled when the Activity exits, and will be successfully destroyed and recycled by the system. The fourth step will often be missed, and generally there will be no fatal problems. However, once there is a problem, it is difficult to troubleshoot, so it is necessary to follow the coding specification.
Summary
We have made clear the basic implementation principle of AsyncTask. At the same time, we also introduced a small skill to pay attention to when using AsyncTask. I hope you can gain something after reading it

Welcome to communicate and discuss together!

Posted by HDMICable on Tue, 16 Nov 2021 20:18:09 -0800