返回介绍

Android 应用 AsyncTask 处理机制详解及源码分析

发布于 2025-02-28 12:35:45 字数 13062 浏览 0 评论 0 收藏 0

1 背景

Android 异步处理机制一直都是 Android 的一个核心,也是应用工程师面试的一个知识点。前面我们分析了 Handler 异步机制原理(不了解的可以阅读我的 《Android 异步消息处理机制详解及源码分析》 文章),这里继续分析 Android 的另一个异步机制 AsyncTask 的原理。

当使用线程和 Handler 组合实现异步处理时,当每次执行耗时操作都创建一条新线程进行处理,性能开销会比较大。为了提高性能我们使用 AsyncTask 实现异步处理(其实也是线程和 handler 组合实现),因为其内部使用了 java 提供的线程池技术,有效的降低了线程创建数量及限定了同时运行的线程数,还有一些针对性的对池的优化操作。所以说 AsyncTask 是 Android 为我们提供的方便编写异步任务的工具类。

2 实例演示

先看下使用 AsyncTask 模拟下载的效果图:

看下代码,如下:

public class MainActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    new TestAsyncTask(this).execute();
  }

  static final class TestAsyncTask extends AsyncTask<Void, Integer, Boolean> {
    //如上三个泛型参数从左到右含义依次为:
    //1. 在执行 AsyncTask 时需要传入的参数,可用于在后台任务中使用。
    //2. 后台任务执行时,如果需要在界面上显示当前的进度,则使用这个。
    //3. 当任务执行完毕后,如果需要对结果进行返回,则使用这个。
    private Context mContext = null;
    private ProgressDialog mDialog = null;
    private int mCount = 0;

    public TestAsyncTask(Context context) {
      mContext = context;
    }

    //在后台任务开始执行之间调用,用于进行一些界面上的初始化操作
    protected void onPreExecute() {
      super.onPreExecute();
      mDialog = new ProgressDialog(mContext);
      mDialog.setMax(100);
      mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
      mDialog.show();
    }

    //这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务
    protected Boolean doInBackground(Void... params) {
      while (mCount < 100) {
        publishProgress(mCount);
        mCount += 20;
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
      return true;
    }

    //当在后台任务中调用了 publishProgress(Progress...) 方法后,这个方法就很快会被调用
    protected void onProgressUpdate(Integer... values) {
      super.onProgressUpdate(values);
      mDialog.setProgress(values[0]);
    }

    //当后台任务执行完毕并通过 return 语句进行返回时,这个方法就很快会被调用
    protected void onPostExecute(Boolean aBoolean) {
      super.onPostExecute(aBoolean);
      if (aBoolean && mDialog != null && mDialog.isShowing()) {
        mDialog.dismiss();
      }
    }
  }
}

可以看见 Android 帮我们封装好的 AsyncTask 还是很方便使用的,咱们不做过多说明。接下来直接分析源码。

3 Android5.1.1(API 22)AsyncTask 源码分析

通过源码可以发现 AsyncTask 是一个抽象类,所以我们在在上面使用时需要实现它。

那怎么下手分析呢?很简单,我们就依据上面示例的流程来分析源码,具体如下。

3-1 AsyncTask 实例化源码分析

/**
   * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
   */
  public AsyncTask() {
    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);
        }
      }
    };
  }

看见注释没有,AsyncTask 的实例化只能在 UI 线程中。然后整个构造函数就只初始化了两个 AsyncTask 类的成员变量(mWorker 和 mFuture)。mWorker

为匿名内部类的实例对象 WorkerRunnable(实现了 Callable 接口),mFuture 为匿名内部类的实例对象 FutureTask,传入了 mWorker 作为形参(重写了
FutureTask 类的 done 方法)。

3-2 AsyncTask 的 execute 方法源码分析

正如上面实例一样,得到 AsyncTask 实例化对象之后就执行了 execute 方法,所以看下 execute 方法的源码,如下:

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

可以看见,execute 调运了 executeOnExecutor 方法,executeOnExecutor 方法除过传入了 params 形参以外,还传入了一个 static 的 SerialExecutor 对象

(SerialExecutor 实现了 Executor 接口)。继续看下 executeOnExecutor 源码,如下:

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

首先判断 AsyncTask 异步任务的状态,当处于 RUNNING 和 FINISHED 时就报 IllegalStateException 非法状态异常。由此可以看见一个 AsyncTask 的

execute 方法只能被调运一次。接着看见 17 行 onPreExecute();没有?看下这个方法源码,如下:

/**
   * Runs on the UI thread before {@link #doInBackground}.
   *
   * @see #onPostExecute
   * @see #doInBackground
   */
  protected void onPreExecute() {
  }

空方法,而且通过注释也能看见,这不就是我们 AsyncTask 中第一个执行的方法吗?是的。

回过头继续往下看,看见 20 行 exec.execute(mFuture);代码没?exec 就是形参出入的上面定义的 static SerialExecutor 对象(SerialExecutor 实现了 Executor 接口),所以 execute 就是 SerialExecutor 静态内部类的方法喽,在执行 execute 方法时还传入了 AsyncTask 构造函数中实例化的第二个成员变量 mFuture。我们来看下 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();
          }
        }
      });
      if (mActive == null) {
        scheduleNext();
      }
    }

    protected synchronized void scheduleNext() {
      if ((mActive = mTasks.poll()) != null) {
        THREAD_POOL_EXECUTOR.execute(mActive);
      }
    }
  }

在源码中可以看见,SerialExecutor 在 AsyncTask 中是以常量的形式被使用的,所以在整个应用程序中的所有 AsyncTask 实例都会共用同一个

SerialExecutor 对象。接着可以看见,SerialExecutor 是使用 ArrayDeque 这个队列来管理 Runnable 对象的,如果我们一次性启动了很多个任务,首先在第一次运行 execute() 方法的时候会调用 ArrayDeque 的 offer() 方法将传入的 Runnable 对象添加到队列的最后,然后判断 mActive 对象是不是等于 null,第一次运行是 null,然后调用 scheduleNext() 方法,在这个方法中会从队列的头部取值,并赋值给 mActive 对象,然后调用 THREAD_POOL_EXECUTOR 去执行取出的取出的 Runnable 对象。之后如果再有新的任务被执行时就等待上一个任务执行完毕后才会得到执行,所以说同一时刻只会有一个线程正在执行,其余的均处于等待状态,这就是 SerialExecutor 类的核心作用。

我们再来看看上面用到的 THREAD_POOL_EXECUTOR 与 execute,如下:

public abstract class AsyncTask<Params, Progress, Result> {
  ......
  private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
  private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
  private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
  private static final int KEEP_ALIVE = 1;

  private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);

    public Thread newThread(Runnable r) {
      return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
  };

  private static final BlockingQueue<Runnable> sPoolWorkQueue =
      new LinkedBlockingQueue<Runnable>(128);

  /**
   * An {@link Executor} that can be used to execute tasks in parallel.
   */
  public static final Executor THREAD_POOL_EXECUTOR
      = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
          TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
  ......
}

看见没有,实质就是在一个线程池中执行,这个 THREAD_POOL_EXECUTOR 线程池是一个常量,也就是说整个 App 中不论有多少 AsyncTask 都只有这

一个线程池。也就是说上面 SerialExecutor 类中 execute() 方法的所有逻辑就是在子线程中执行,注意 SerialExecutor 的 execute 方法有一个 Runnable 参数

,这个参数就是 mFuture 对象,所以我们看下 FutureTask 类的 run() 方法,如下源码:

public void run() {
    if (state != NEW ||
      !UNSAFE.compareAndSwapObject(this, runnerOffset,
                     null, Thread.currentThread()))
      return;
    try {
      Callable<V> c = callable;
      if (c != null && state == NEW) {
        V result;
        boolean ran;
        try {
          result = c.call();
          ran = true;
        } catch (Throwable ex) {
          result = null;
          ran = false;
          setException(ex);
        }
        if (ran)
          set(result);
      }
    } finally {
      // runner must be non-null until state is settled to
      // prevent concurrent calls to run()
      runner = null;
      // state must be re-read after nulling runner to prevent
      // leaked interrupts
      int s = state;
      if (s >= INTERRUPTING)
        handlePossibleCancellationInterrupt(s);
    }
  }

看见没有?第 7 行的 c = callable;其实就是 AsyncTask 构造函数中实例化 FutureTask 对象时传入的参数 mWorker。12 行看见 result = c.call(); 没有?

其实就是调运 WorkerRunnable 类的 call 方法,所以我们回到 AsyncTask 构造函数的 WorkerRunnable 匿名内部内中可以看见如下:

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

看见没有?在 postResult() 方法的参数里面,我们可以看见 doInBackground() 方法。所以这验证了我们上面例子中使用的 AsyncTask,首先在主线程执

行 onPreExecute 方法,接着在子线程执行 doInBackground 方法,所以这也就是为什么我们可以在 doInBackground() 方法中去处理耗时操作的原因了

,接着等待 doInBackground 方法耗时操作执行完毕以后将返回值传递给了 postResult() 方法。所以我们来看下 postResult 这个方法的源码,如下:

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

先看下这个 getHandler 拿到的是哪个 Handler 吧,如下:

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:
          // There is only one result
          result.mTask.finish(result.mData[0]);
          break;
        case MESSAGE_POST_PROGRESS:
          result.mTask.onProgressUpdate(result.mData);
          break;
      }
    }
  }

看见没有,拿到的是 MainLooper,也就是说在在 UI 线程中的 Handler(不清楚的请阅读 《Android 异步消息处理机制详解及源码分析》 文章)。所以

上面的方法其实就是将子线程的数据发送到了 UI 来处理,也就是通过 MESSAGE_POST_RESULT 在 handleMessage 来处理。所以我们继续看

handleMessage 中的 result.mTask.finish(result.mData[0]);就会发现 finish 的代码如下:

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

看见没有?依据返回值 true 与 false 回调 AsyncTask 的 onPostExecute 或者 onCancelled 方法。

到此是不是会好奇 onProgressUpdate 方法啥时候调运的呢?继续往下看可以发现 handleMessage 方法中的 MESSAGE_POST_PROGRESS 不就是回调我们 UI Thread 中的 onProgressUpdate 方法吗?那怎么样才能让他回调呢?追踪 MESSAGE_POST_PROGRESS 消息你会发现如下:

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

额,没意思了。这不就是我们上面例子中的在子线程的 doInBackground 耗时操作中调运通知回调 onProgressUpdate 的方法么。

看见没有,AsyncTask 的实质就是 Handler 异步消息处理机制(不清楚的请阅读 《Android 异步消息处理机制详解及源码分析》 文章),只是对线程做了优化处理和封装而已。

4 为当年低版本 AsyncTask 的臭名正身

接触 Android 比较久的可能都知道,在 Android 3.0 之前是并没有 SerialExecutor 这个类的(上面有分析)。那些版本的代码是直接创建了指定大小的线程池常量来执行 task 的。其中 MAXIMUM_POOL_SIZE = 128;,所以那时候如果我们应用中一个界面需要同时创建的 AsyncTask 线程大于 128(批量获取数据,譬如照片浏览瀑布流一次加载)程序直接就挂了。所以当时的 AsyncTask 因为这个原因臭名昭著。

回过头来看看现在高版本的 AsyncTask,是不是没有这个问题了吧?因为现在是顺序执行的。而且更劲爆的是现在的 AsyncTask 还直接提供了客户化实现 Executor 接口功能,使用如下方法执行 AsyncTask 即可使用自定义 Executor,如下:

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
      Params... params) {
    ......
    return this;
  }

可以看出,在 3.0 以上版中 AsyncTask 已经不存在那个臭名昭著的 Bug 了,所以可以放心使用了,妈妈再也不用担心我的 AsyncTask 出 Bug 了。

5 AsyncTask 与 Handler 异步机制对比

前面文章也分析过 Handler 了,这里也分析了 AsyncTask,现在把他们两拽一起来比较比较。具体如下:

  1. AsyncTask 是对 Handler 与 Thread 的封装。

  2. AsyncTask 在代码上比 Handler 要轻量级别,但实际上比 Handler 更耗资源,因为 AsyncTask 底层是一个线程池,而 Handler 仅仅就是发送了一个消息队列。但是,如果异步任务的数据特别庞大,AsyncTask 线程池比 Handler 节省开销,因为 Handler 需要不停的 new Thread 执行。

  3. AsyncTask 的实例化只能在主线程,Handler 可以随意,只和 Looper 有关系。

6 AsyncTask 总结

到此整个 Android 的 AsyncTask 已经分析完毕,相信你现在对于 AsyncTask 会有一个很深入的理解与认识了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文