美文网首页
AsyncTask 使用&源码分析

AsyncTask 使用&源码分析

作者: 请叫我林锋 | 来源:发表于2019-10-10 22:15 被阅读0次

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

    1、AsyncTask 的使用

    是什么?

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

    有什么用?

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

    怎么用?

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

    • Params。代表这项执行这项任务所需要的参数。
    • Progress。代表这项任务的执行进度。
    • Result。代表这项任务的处理结果。

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

    • onPreExecute()。主线程运行,可以在这个方法中更新UI来提示将要开始执行任务了。
    • doInBackground(Params... params)。这个方法是抽象方法,它会在线程池中运行,所以我们可以在这个方法里面去执行耗时操作。
    • onProgressUpdate(Progress... values)。主线程运行,我们可以在这个方法中来更新 UI 来提示用户任务的进度。当 publishProgress(Progress... values) 方法被调用的时候并且该任务没有被取消,就会调用该方法。
    • onPostExecute(Result result)。主线程运行,当耗时任务结束时就会调用该方法,可以在这个方法中来更新 UI 来提示任务的执行结果。
    • onCancelled(Result result) 与 onCancelled()。主线程运行,当任务被取消的时候就会调用。
      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 时要注意以下几点,原因会在下面进行分析:

    • 在使用 execute() 时,必须要在主线程。
    • AsyncTask 的对象需要在主线程创建。
    • 在 Android3.0 之后 AysncTask 任务的执行默认是异步的,如果想要同步执行可以使 executeOnExecutor()。
    • 一个任务只能被执行一次,也就是说一个 AsyncTask 只能调用一次 execute() 或 executeOnExecutor()。
    • 用 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 引用着,导致资源不能够回收。

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

    相关文章

      网友评论

          本文标题:AsyncTask 使用&源码分析

          本文链接:https://www.haomeiwen.com/subject/qclzpctx.html