AsyncTask 使用&源码分析

2019-10-10  本文已影响0人  请叫我林锋

前言:AsyncTask 在 Android 日常开发中非常常见,如果你没用过那你一定要学习一下,用过的话了解它的内部原理也是非常重要,可以帮助我们更好的更规范的使用它。

1、AsyncTask 的使用

是什么?

一个封装了线程池和 Handler 的类。

有什么用?

想象一下我们要进行一项耗时操作并在结束时更新UI,一般会使用【Thread + Handler】。而使用 AsyncTask 就可以优雅的实现,它有两个优点:1、它是线程池实现的,所以免去了频繁创建和销毁线程带来的系统开销,提高了效率 2、结构简单,代码高内聚。

怎么用?

首先它有三个范型参数:Params, Progress, Result。

其次,它还有几个可以供我们使用的方法:

  private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
      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");
      }
  }

这是官方给出使用 AsyncTask 的示例,通过它可以下载东西并且更新UI。可以看到这里创建了一个 DownloadFilesTask 子类继承 AsyncTask ,然后重写了 doInBackground() ,onProgressUpdate(),onPostExecute() 这三个方法。

在 doInBackground 中,拿到 urls 去下载东西,然后通过调用 publishProgress() 方法来触发 onProgressUpdate() 去更新UI,这里它设置了一下进度。注意这里有个 isCancelled() 判断,如果任务被取消了,那么它就会停止任务,虽然这里依然 return totalSize,但是它不会再去调用 onPostExecute() 了。

可以通过 execute() 来执行一个任务:

new DownloadFilesTask().execute(url1,url2,url3);

在使用 AsyncTask 时要注意以下几点,原因会在下面进行分析:

2、源码分析(基于 API28)

下面我们将对 AsyncTask 的源码进行分析。首先从 AsyncTask 的 execute() 方法入手。

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

可以看到,当我们调用 execute(Params... params) 方法的时候可以传入参数 params,这是可变参数,可以理解为数组,要求数据类型要一致。然后它会去调用 executeOnExecutor((sDefaultExecutor, params) 方法。这里多了一个 sDefaultExecutor 参数,让我们看看它是什么。

    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();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

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

可以看到 sDefaultExecutor 是一个实现了 Executor 接口的类的对象,SerialExecutor 从字面上可以理解为串行执行器,就是它控制了 AsyncTask 是串行执行任务的,具体执行任务的流程我们等会儿再分析。我们接着看 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;
    }

这里先判断了一下 mStatus 的状态,如果这个任务已经被执行过了或者已经结束了,再去执行一次就会抛出异常。这解释了为什么 AsyncTask 只能够执行一次 execute 。

然后它会把状态设为 RUNNING ,然后去执行 onPreExecute() 方法,这里出现我们可以重写的第一个方法,我们可以在这个方法里面做开始执行任务之前要做的工作,比如更新一下UI。那我们也就可以理解为什么 execute() 要在主线程执行了,如果在子线程执行,那么就不能更新 UI 了,也就与设计这个方法的初衷相违背了。

接着出现了 mWorker 和 mFuture,看看它们是什么东西。

private final WorkerRunnable<Params, Result> mWorker;
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
}

可以看到 mWorker 是一个实现了 Callable 接口的抽象类,它拥有一个 mParams。看看 Callable 是什么东西。

public interface Callable<V> {
    V call() throws Exception;
}

可以看到 Callable 就是一个接口函数,里面有个 call 方法。接着我们再去看看 mFuture 是什么东西。

    private final FutureTask<Result> mFuture;

FutureTask 在这里我们可以把它理解为 一个 Runnable。

接着我们看看 mWorker 和 mFuture 这两个对象在是在哪里初始化的。

    public AsyncTask(@Nullable Looper callbackLooper) {
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);

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

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

这是 AsyncTask 的构造方法,当我们初始化 AsyncTask 对象的时候,会调用 AsyncTask 的构造方法。可以看到,这里有三个对象的初始化,分别是 mHandler,mWorker,mFuture。
调用构造方法时,这个构造方法的参数 callbackLooper 一定是空,那么就调用 getMainHandler () 去获得主线程的 Handler,如下代码所示。可以看到这个 sHandler 获得了主线程的 Looper ,也就说明了我们的 AsyncTask 对象在子线程和主线程创建的效果其实是一样的(但是为了兼容其他 API,我们还是需要在主线程创建对象)。

private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }

在构造方法接着可以看到创建了一个 WorkerRunnable 的匿名内部类对象给 mWorker,这里面能够看到我们的第二个方法 doInBackground(mParams),这个 mParams,就是我们前面赋值的,在 doInBackground() 中我们可以执行耗时操作,因为它是在线程池中执行的,后面会继续分析。

下面接着创建了一个 FutureTask 的匿名内部类对象给 mFuture,可以看到它把 mWorker 组装进来了。

现在我们可以继续分析当执行完 onPreExecute() 后,它做了什么呢?

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

可以看到,这边把我们传进去的 params 传给了 mWorker。然后 exec.execute(mFture),这里的 exec 就是上面的 sDefaultExecutor ,也就是串行执行器,为了方便阅读,我再贴出这个串行执行器的代码,细细分析 execute()到底做了什么。

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

可以看到,这里先创建了一个队列,范型是 Runnable,当我们执行 execute 时,会调用 offer 方法把 Runnable 对象压入队列,这个 Runnable 对象保存了我们的 mFuture 对象。

接着下面判断如果当前没有任务再执行(mActive == null) ,就调用 scheduleNext() 方法。如果当前有任务正在执行,execute()方法到这里就结束了,也就是说如果有任务正在执行,入个队列就完事了。

再看看 scheduleNext() 方法,它首先会从任务队列中取出第一个任务,如果存在任务,就把这个任务放到 THREAD_POOL_EXECUTOR (线程池)中去执行。当这个任务执行完的时候,一定会进到 finnally 语句块,然后又去执行了 scheduleNext(),也就是说再去队列中取出任务去执行。看到这里也就明白了,AsyncTask 的任务处理是串行的了。

接着看 r.run() 做了什么,这里的 r 就是我们前面组装的 mFture 对象,看看它的 run 做了什么事情。

public void run() {
        ...
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } 
              ...
            }
        }
            ...
    }

上面的 callable 就是我们传进去的 mWorker 对象,可以看到它触发了 mWorker 的 call() 方法。

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

这里的 call() 方法被触发时,就会看到我们熟悉的 doInBackground(),这下我们能理解为什么 doInBackground() 是在子线程中运行的了,因为这个任务是在 THREAD_POOL_EXECUTOR 线程池中执行的。执行完 doInBackground() 会得到一个 result,在 finally 语句中,通过 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;
    }

然后下面这是一段消息通信的代码,了解 Handler 的同学应该很熟悉。

    private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

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

我们传入的 msg.waht 是 MESSAGE_POST_RESULT,它会去执行 finsih,再看看 finish 做了什么。

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

可以看到这里进行了判断,如果任务被取消了,就会去执行我们的 onCancelled() 方法。如果任务没有被取消就会去执行我们的 onPostExecute() 方法。通过 Handler 将子线程任务的执行结果又切换回了主线程,我们可以在这两个方法中更新UI。至此我们算是走完 AsyncTask 的全部流程。

经过源码分析我们明白了前面提出的在使用 AsynsTask 过程中需要注意的问题。并且现在我们也可以解释为什么使用 executeOnExecutor(THREAD_POOL_EXECUTOR) 可以并发执行。原因就是 executeOnExecutor(THREAD_POOL_EXECUTOR) 没有使用串行执行器,THREAD_POOL_EXECUTOR 本身是具有执行并发任务的能力的。只是我们在调用 execute() 时,还使用中利用串行执行器将队列里的一个个任务排队分发给 THREAD_POOL_EXECUTOR 去执行而已。我们在使用 executeOnExecutor(THREAD_POOL_EXECUTOR) 的时候还要注意 THREAD_POOL_EXECUTOR 线程池中队列的最大容量,若队列中的任务数超过里最大容量,就会抛出异常。

同时对于较长的耗时操作,不要使用 AsyncTask,可以使用线程池。原因有两点:1、AsyncTask 和 Activity 的生命周期没有紧密联系,比如旋转屏幕导致 Activity 被重建,而 AsyncTask 持有的是之前的 Activity 对象,所以它的更新 UI 都是没有作用的。2、使用 AsyncTask 的内存泄漏问题,我们经常把 AsyncTask 作为内部类来方便的引用外部 Activity 的控件来进行更新进度或者结果,这就导致了 Activity 已经不可见了,但是仍被 AsyncTask 引用着,导致资源不能够回收。

分析源码可以让我们正确使用工具,思考一些之前没有考虑过的问题,更可以学习顶级程序员是如何写代码的。所以后续我还会继续分析安卓开发中常用的工具原理。最后,这篇分析也仅是个人的看法,如果哪里有不正确之处,还请各位大佬指出。

上一篇下一篇

猜你喜欢

热点阅读