Android 异步任务类之AsyncTask 完全解析

1、什么是AsyncTask

1、是一种轻量级的异步任务类;
2、是一个封装了线程池和Handler的异步框架;
3、使用它可以更加方便的执行后台任务以及在主线程访问UI,但他不适合进行特别耗时的后台任务;

今天我们来回顾复习下AsyncTask, 它是Android 一种轻量级的异步任务类,从实现来说,AsyncTask封装了线程池和Handler。它可以在线程池中执行后台任务,把执行的进度和结果传递给主线程并在主线程中更新UI,通过AsyncTask可以更加方便的执行后台任务以及在主线程访问UI,但他不适合进行特别耗时的后台任务。

一、什么是AsyncTask?

1、是一种轻量级的异步任务类;
2、是一个封装了线程池和Handler的异步框架;
3、使用它可以更加方便的执行后台任务以及在主线程访问UI,但他不适合进行特别耗时的后台任务;

二、AsyncTask的使用方法

1、3个参数
  • Params:执行AsyncTask时,后台任务需要传入的参数,在doInBackground中取出使用。
  • Progress:后台任务执行时,如果需要显示当前进度,则制定进度的数据类型。
  • Result:后台任务执行完成后的返回值类型。
2、5个方法
  • onPreExecute:
    在主线程中执行,在后台任务执行前调用,通常用于做一些准备操作。

  • doInBackground(Params… params):
    在线程池中执行,用于执行后台任务;params 参数是 execute(Params… params)方法中传递的参数。在此方法中可以调用 publishProgress 方法来更新任务的进度,publishProgress方法会调用onProgressUpdate方法。

  • publishProgress(Progress… values):
    用于更新任务的进度,需要手动调用,publishProgress方法会调用onProgressUpdate方法;values参数为设置的进度值。

  • onProgressUpdate(Progress… values):
    在主线程中执行,当后台任务的执行进度发生改变时,会被调用,values参数为进度值。

  • onPostExecute(Result result):
    在主线程中执行,当后台任务执行完成时,会被调用。result 的值是doInBackground的返回值。

三、AsyncTask的机制原理

  • AsyncTask 的本质是一个静态的线程池,可以执行不同的异步任务,这些任务提交到静态的线程池中执行。
  • 线程池中的工作线程执行doInBackgroup(Params… params)方法执行异步任务。
  • 当任务状态改变后,工作线程向UI线程发送消息,AsyncTask内部的InternalHandler 响应这些消息,并调用相关的回调函数。

四、AsyncTask的注意事项

1、内存泄漏
  • 原因:非静态内部类持有外部类的匿名引用,导致Activity无法释放。

  • 解决:

  • AsyncTask内部持有外部Activity的弱引用。
  • AsyncTask改为静态内部类。
  • AsyncTask.cancel()。
2、生命周期

不受Activity生命周期的影响,在Activity销毁之前,取消AsyncTask的运行,以此来保证程序的稳定。

3、结果丢失

由于屏幕旋转、Activity在内存紧张时被回收等情况下,Activity会被重新创建,此时,还在运行的AsyncTask会持有一个Activity的非法引用即之前的Activity实例。导致onPostExecute()没有任何作用。

4、并行or串行
  • Android 1.6之前,默认采用串行执行任务;
  • Android 1.6~ 2.3 ,默认采用并行执行任务;
  • Android 3.0,默认采用串行执行任务,如果需要改为并行,可以调用AsyncTask的executeOnExecutor()来执行任务即可。
5、AsyncTask的使用限制
  • AsyncTask的对象必须在主线程中创建;
  • execute 方法必须在UI线程调用;
  • 不要在程序中手动调用 onPreExecute、onPostExecute、 doInBackground、onProgressUpdate方法;
  • 一个AsyncTask对象只能调用一次excute()方法,执行一次,否则会报异常。

AsyncTask的机制原理
1.AsyncTask本质上是一个静态的线程池,其派生出来的子类可以实现不同的异步任务,这些任务都会提交到线程池中去执行
2.耗时操作是在doInBackground中执行的
3.当任务状态(pendding,running,finished)改变后,工作线程会向ui线程发送消息,AsyncTask内部的InternalHandler会响应这些消息并执行相关回调函数

AsyncTask注意事项
1、AsyncTask不适合特别耗时的任务
AsyncTask的生命周期和Activity的生命周期不同步,Activity销毁了但是AsyncTask中的任务还是会继续执行完毕,一个最典型的例子就是Activity的横竖屏切换,AsyncTask中引用的Activity不是当前的Activity,onPostExecute()中执行的仍然是上一个Activity。还有一个原因是因为AsyncTask在执行长时间的耗时任务时也会持有一个Activity对象,即使这个Activity已经不可见了,Android也无法对这个Activity进行回收,导致内存泄露。

2、AsyncTask只能在主线程中创建以及使用
AsyncTask被用于执行异步任务,然后更新UI,所以最后的onPostExecute()方法执行在创建该AsyncTask对象的线程中,如果不在主线程中创建以及使用,就达不到更新UI的目的。

3、一个AsyncTask对象只能执行一次
一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常

4、AsyncTask在不同的Android版本下的并行和串行问题
关于AsyncTask的并行和串行问题,在不同的API下是有不同的。在Android1.6之前,AsyncTask是串行执行任务的;到了Android1.6时,开始采用线程池来并行执行任务;在Android3.0之后的版本中,AsyncTask又开始用一个线程串行执行任务。虽然Android3.0之后采用串行方式执行任务,但我们可以通过AsyncTask的executeOnExecutor(exe,params),自定义一个线程池来并行执行任务。

五、AsyncTask 源码解析

5.1、执行execute()方法

执行AsyncTask 的时候会执行方法 downloadTask?.execute(downloadUrl) ,我们再看看这个方法execute()实际上是执行 executeOnExecutor()

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

5.2、AsyncTask的三种状态
这里面Status 是AsyncTask 内部的三种状态,如果状态不是Status.PENDING 就会报错,这也是为什么AsyncTask只能执行一次的原因。

/**
     * Indicates the current status of the task. Each status will be set only once
     * during the lifetime of a task.
     */
    public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }

5.3、执行onPreExecute()方法

紧接着先执行 onPreExecute() ,在这里我们看到了AsyncTask 重写的第一个方法了,我们一般在这里做一些初始化操作。我们从这里可以看到这个方法是运行在主线程中。

接下来下面的这两行代码你可能不知道是干嘛的。mWork和mFuture实际上是在AsyncTask构造函数中实例化的。

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

5.4、AsyncTask构造函数的分析

我们继续来看看AsyncTask的构造函数、这里面只是初始化三个对象mHandler 、mWorker 、mFuture

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

5.5、AsyncTask的InternalHandler

mHandler 是AsyncTask的静态内部类InternalHandler,我们发现它是用来更新进度,以及任务完成时的做一些操作。

    private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }
    ···
        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;
            }
        }
    }

5.5、验证AsyncTask的doInBackground运行在子线程

mWorker 是作为参数传递到mFuture中的,在mWorker中我们看到了doInBackground,大家都知道这个方法可以执行一些耗时操作,它是运行在子线程的,我们只需要知道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;
            }
        };

我们来看看mFuture 是什么,点进去看发现它实现了RunnableFuture接口,而RunnableFuture又是继承Runnable接口的,原来mFuture 是个新的线程,我们看看他的构造函数和run方法。在这里终于发现原来mWorker作为参数callable传入到FutureTask中,最后c.call()确实是在子线程的run()方法中调用的。这里doInBackground的谜底也接开了。

    ...
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
    ...
    public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, 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);
        }
    }

5.6、exec.execute(mFuture)分析
在上述的5.1方法中我们看到执行完onPreExecute()方法后会执行exec.execute()方法,我们看看它做了什么:
exec 是一个线程池 sDefaultExecutor ,mFuture作为参数传入到SerialExecutor 这个静态内部类中。r.run()即是mFuture的执行。

    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    ...
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

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

线程池的知识这里不展开了,这里面可能大家会有疑问怎么AsyncTask的另外几个重写的方法没有执行呢,我们看看它在哪

5.7、onProgressUpdate方法
在doInBackground()中是不可以进行UI操作的,如果需要更新UI,比如说反馈当前任务的执行进度,可以调用publishProgress()方法完成,这个方法是运行在主线程的。我们从publishProgress传入的参数比如进度最终在重写onProgressUpdate的方法中处理。

    @WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }
   ...
   case MESSAGE_POST_PROGRESS:
        result.mTask.onProgressUpdate(result.mData);
   break;
    ...
        @MainThread
    protected void onProgressUpdate(Progress... values) {
    }

5.8、onPostExecute方法
在5.4节AsyncTask的构造函数中我们可以看到调用流程是:postResultIfNotInvoked () --> postResult() -->finish()–>onPostExecute()

     ···
       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);
                }
            }
        };
      ···
        private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            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;
    }
    ···
                    case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
    ···  
        private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }                  

5.9、AsyncTask的线程池
AsyncTask中还存在另一个线程池executeOnExecutor方法,但是在API28的点击去看发现最后用的还是这个SerialExecutor 单线程。我们看这个方法
当存在10个任务的时候,第一个任务进来,mActive为空执行scheduleNext,在scheduleNext方法中取出线程头部添加到线程池中,然后复制给mActive,当第二个任务进来时,mActive不为空,也就是说不执行scheduleNext()方法,所以只有等到第一个任务run方法执行完之后调用finally中的scheduleNext()才会执行下一个任务,所以来说其实还是单线程线性执行,一个接一个。

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

文章到这里基本上就结束了,有疑问的小伙伴儿可以评论去留言

本文参考

1、AsyncTask面试知识小结
2、Android面试系列之异步消息处理相关
3、Android – AsyncTask源码解析
4、Android AsyncTask完全解析,带你从源码的角度彻底理解

发布了78 篇原创文章 · 获赞 45 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/An_Times/article/details/90203343