AsyncTask 面试解析

[Toc]java

基础认识

AsyncTask 是基于 Handler 进行封装的轻量级异步类,它是一个抽象类,咱们要使用的时候须要实现其子类的如下 4 个方法android

方法 描述
onPreExecute() 任务执行前被调用,执行在 UI 线程中,在这里咱们作一些任务启动前的准备
doInBackground() 执行在新的子线程中的,作异步任务的处理
onProgressUpdate() 这个方法是在调用 publishProgress 的时候被调用的,是运行在 UI 线程的
onPostExecute() 这个方法是任务执行完毕以后被调用的,是运行在 UI 线程中的

做用

  • 实现多线程: 在工做线程中执行任务,如 耗时任务
  • 异步通讯、消息传递: 实现工做线程 & 主线程(UI线程)之间的通讯,即:将工做线程的执行结果传递给主线程,从而在主线程中执行相关的UI操做

AsyncTask 的三种状态

每一个状态在一个任务的生命周期中只会被执行一次。bash

状态 描述
PENDING 等待(尚未开始执行任务)
RUNNING 执行中
FINSHED 完成

AsyncTask 的内部执行过程

AsyncTask 的对象调用 execute 方法,execute 内部又调用了 executeOnExecutor ,onPreExecute 方法就是在这里被回调,以后将 AsyncTask 的参数封装成一个并发类,而后将其添加到排队线程池(SerialExecutor)中进行排队,若是当前有任务正在执行,则等待,不然 THREAD_POOL_EXECUTOR 执行该任务。在任务的执行过程当中,经过 InternalHandler 将进度 pos(MESSAGE_POST_GROGRESS)发送到主线程中,此时会调用 onProgressUpdate 方法,任务执行完毕以后,InternalHandler 将结果 post(MESSAGE_POST_RESULT) 发送到主线程中,此时 onPostExecute 或者 onCancle 会被调用,任务执行到这里就结束了。多线程

基本使用

  • 举个栗子:在异步任务中每隔 1s 打印 1 ~ 10 的数值
    1. 咱们不干预任务的执行过程,由任务执行完成,查看任务执行状况;
    2. 任务执行完成后,咱们再点击开始,查看任务执行状况;
    3. 干预任务的执行过程,在任务执行期间点击取消,查看任务执行状况;
public class MainActivity extends AppCompatActivity {
    private TextView mText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mText = findViewById(R.id.text);
        progressAsycn1 = new ProgressAsycn();
        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                progressAsycn1.execute(1);
            }
        });
         findViewById(R.id.btn_cancel).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                progressAsycn1.cancel(true);
                mText.append(String.format("取消任务%s\n",new Date().toString()));
            }
        });

    }


    private ProgressAsycn progressAsycn1;
    private class ProgressAsycn extends AsyncTask<Integer,Integer,String> {

        // 这个方法是在启动以前被调用的,执行在 UI 线程中,在这里咱们作一些任务启动前的准备
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            mText.append(String.format("准备执行%s\n",new Date().toString()));
        }

        // 这个方法是执行在新的线程的中的
        @Override
        protected String doInBackground(Integer... params) {
            for (int i = params[0]; i <= 10; i++) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                publishProgress(i);
            }
            return "任务已经执行完毕";
        }

        // 这个方法是在调用 publishProgress 的时候被调用的,是运行在 UI 线程的
        @Override
        protected void onProgressUpdate(Integer... values) {
            mText.append(String.format("工做进度:%d\n",values[0]));
        }

        // 这个方法是任务执行完毕以后被调用的
        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            mText.append(String.format("任务执行完毕%s\n",new Date().toString()));
        }

        /**
         * 异步任务被取消时回调,即 AsyncTask 的对象调用了 cancel 方法
         * 这个方法和 onPostExecute 互斥
         * doInBackground 方法中的任务执行完毕,才会被回调
         */
        @Override
        protected void onCancelled() {
            mText.append(String.format("异步任务已取消%s\n",new Date().toString()));
        }
    }
}
复制代码
  • 执行结果并发

    1. 不干预任务的执行过程,最后的执行结果以下

    asyncTask1.png
    2. 在 1 执行完以后,再点击开始,结果程序报以下错误

    java.lang.IllegalStateException: Cannot execute task: the task has already been executed (a task can be executed only once)
    复制代码
    1. 在任务的执行过程当中,点击取消任务,调用 cancel() 方法,以后咱们能够看到 onProgressUpdate() 和 onPostExecute() 方法再也不被调用,可是咱们取消任务的时候,任务仍是没有中止的,等到任务真正中止的时候,onCancelled() 方法被调用,执行效果图以下:

    asyncTask2.png

  • 问题:从上面的执行结果中,咱们能够看出两个问题app

      1. AsyncTask 的对象只能被调用一次,再次调用的时候,会出错
      1. Asynctask 调用了 cancel() 方法取消任务,可是任务并无真正的中止

提出问题

咱们在阅读源码以前,先给本身提一些问题,而后咱们在阅读源码的时候,带着问题来去找答案,这样咱们的目标才会更加明确。异步

  1. 为何AsyncTask 的对象只能被调用一次,不然会出错?(每一个状态只能执行一次)
  2. AsyncTask 的类为何必须在主线程加载
  3. AsyncTask 的对象为何必须在主线程中建立
  4. AsyncTask 是串行执行任务仍是并行执行任务?
  5. AsyncTask 调用 cancel() 任务是否当即中止执行?onPostExecute() 还会被调用吗?onCancelled() 何时被调用?

内部源码分析

构造函数中作了什么

首先,咱们查看在 AsyncTask 的构造函数里面到底作了些什么async

/**
     * 该构造方法必须在 UI 线程中被调用
     */
    public AsyncTask() {
        this((Looper) null);
    }
    
    
    /**
     * 该构造方法必须在 UI 线程中被调用
     *
     * @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);
                    // doInBackground 的调用时机
                    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 作了如下几个工做:ide

  • 初始化 handler函数

    • 咱们能够从默认调用的构造函数中传入的 callbackLooper 是为 null,那么 handler 初始化的时候是调用 getMainHandler() 方法的,getMainHandler() 方法的的具体代码以下:
    private static InternalHandler sHandler;
    private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }    
    复制代码
    • 咱们从上面的代码中能够知道,getMainHandler() 最后返回的值是 sHandler,而 sHandler 是个静态 InternalHandler 实例,而关于 InternalHandler 的实现,咱们在下面再作分析。 咱们在这里须要注意的点有两个
      • 为了可以将执行环境切换到主线程中,咱们要求 sHandler 必须在主线程中建立,因此 AsyncTask 的构造函数必须在 UI 线程中调用
      • 咱们知道静态成员会在加载类的时候进行初始化,因为 sHandler 是个静态变量,那么咱们要求 AsyncTask 类必须在 UI 线程中进行加载,不然 AsyncTask 没法正常工做
  • 初始化 mWorker

    • mWorker 是一个 Callable 对象,并在以后初始化 mFutrue 的时候做为参数传入,咱们在 mWorker 的代码实现这里能够看到 doInBackground() 在这里被调用了(可是真正的调用时机不不是在这里进行的,而是在 SerialExecutor 的 execute 的方法中),并将结果返回给 result,并在方法结束以前调用 postResult(result)。 方法
  • 初始化 mFuture

    • MFuture 是一个 FutureTask 对象

execute() 中作了什么

接着若是想要启动某一个任务,就须要调用该任务的 execute() 方法,所以如今咱们来看一看 execute() 方法的源码

@MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
复制代码

execute() 方法的比较简单,只是调用了 executeOnExecutor 方法,那么具体的逻辑是在 executeOnExecutor 里面,咱们再接着看下去:

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

复制代码
  • 首先,executeOnExecutor 先对当前的状态进行判断,因为 AsyncTask 中有三个状态(PENDING、RUNNING、FINISHED),而且每一个状态在 AsyncTask 的生命周期中有且只能被执行一次,若是当前的状态不是 Status.PENDING(未执行),那么就抛出异常。在这里也解释了,咱们 execute() 方法只能被执行一次,再次调用会报错的缘由。
  • 接着,咱们能够看到 onPreExecute() 方法被调用了
  • 紧接着,咱们能够看到 exec.execute(mFuture) 这行代码,而 exec 又是什么,经过查找上面的 execute 方法,咱们能够看到这个 exec 是一个 sDefaultExecutor 变量,sDefaultExecutor 变量的定义以下:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

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

复制代码

从以上代码能够看到,在 executeOnExecutor 中的 exce 最终调用的是 SerialExecutor 中 execute() 方法。而 execute() 这个方法的逻辑是在子线程中执行的,而 execute 这个方法传入的 Runnable 正是 mFuture,在 run 方法中调用了, mFuture 对象的 run。咱们再找到 FutureTask 中实现的 run 方法的代码,代码以下(有省略):

public void run() {
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                try {
                    result = c.call();
                } catch (Throwable ex) {
                   
                }
            }
        } finally {
            // .....
        }
    }

复制代码

从上面的代码中咱们能够看出,最终调用了 Callable 中的 call (),而这个 callball 就是咱们在 executeOnExecutor 中传入 mFuture 的 mWorker 对象。如今咱们又从新拿出 MWorker 的代码来看一下:

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;
            }
        };
复制代码

咱们从上面的代码能够看到两个主要的点,首先是 doInBackground() 方法的调用,并将结果给到 result,最后在方法结束以前调用了 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;
    }
复制代码

而在 postResult 中使用了 sHandler 对象发送了一条消息,消息中携带了 MESSAGE_POST_RESULT 常量和一个表示任务执行结果的 AsyncTaskResult 对象。这个 sHandler 对象是 InternalHandler 类的一个实例,那么稍后这条消息确定会在 InternalHandler 的 handleMessage() 方法中被处理。InternalHandler 的源码以下所示:

private static class InternalHandler extends Handler {
       
        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;
            }
        }
    }
复制代码

在 handleMessage 这里对消息的类型进行了判断,若是这是一条 MESSAGE_POST_RESULT 消息,就会去执行 finish() 方法,若是这是一条 MESSAGE_POST_PROGRESS 消息,就会去执行 onProgressUpdate() 方法。那么 finish() 方法的源码以下所示:

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

复制代码

能够看到,若是当前任务被取消掉了,就会调用 onCancelled() 方法,若是没有被取消,则调用 onPostExecute() 方法,这样当前任务的执行就所有结束了。

咱们注意到,在刚才 InternalHandler 的handleMessage() 方法里,还有一种 MESSAGE_POST_PROGRESS 的消息类型,这种消息是用于当前进度的,调用的正是 onProgressUpdate() 方法,那么何时才会发出这样一条消息呢?相信你已经猜到了,查看publishProgress()方法的源码,以下所示:

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

最后咱们来理一下 executeOnExecutor 中的任务启动到结束,关键方法的调用顺序:

executeOnExecutor() -> sDefaultExecutor.execute() -> mFuture.run() -> mWorker.call() -> doInBackground() -> postResult() -> sHandler.sendMessage() -> sHandler.handleMessage() -> onPostExecute()

复制代码

问题解决

  1. 为何 AsyncTask 的对象只能被调用一次,不然会出错?(每一个状态只能执行一次)

    从上面咱们知道,AsyncTask 有 3 个状态,分别为 PENDING、RUNNING、FINSHED,并且每一个状态在 AsyncTask 的生命周期中有且只执行一次。因为在执行完 execute 方法的时候会先对 AsyncTask 的状态进行判断,若是是 PENDING(等待中)的状态,就会往下执行并将 AsyncTask 的状态设置为 RUNNING(运行中)的状态;不然会抛出错误。AsyncTask finish 的时候,AsyncTask 的状态会被设置为 FINSHED 状态。
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)");
            }
        }
复制代码
  1. AsyncTask 的类为何必须在主线程加载

    因为 sHandler 是一个静态的 Handler 对象,为了可以将执行环境切换到主线程中,这就要求 sHandler 必须在主线程中建立,也便是 AsyncTask 必须在主线程中建立。因为静态成员会在加载类的时候进行初始化,所以也要求 AsyncTask 的类必须在主线程中加载,不然 AsyncTask 没法正常工做。

  2. AsyncTask 的对象为何必须在主线程中建立

    由于在 AsyncTask 的构造函数中对 handler 进行了初始化的操做,因此 AsyncTask 必须在主线程中进行建立,不然 AsyncTask 没法进行线程切换的工做

  3. AsyncTask 是串行执行任务仍是并行执行任务?

    在 Android 1.6 以前,AsyncTask 是串行执行任务的,Android 1.6的时候 AsyncTask 是并行执行任务的,Android 3.0 以后,为了不并行错误,AsyncTask 又采用一个线程来串行执行任务。

  4. AsyncTask 调用 cancel() 任务是否当即中止执行?onPostExecute() 还会被调用吗?onCancelled() 何时被调用?

    任务不会当即中止的,咱们调用 cancel 的时候,只是将 AsyncTask 设置为 canceled(可取消)状态,咱们从如下代码能够看出,AsyncTask 设置为已取消的状态,那么以后 onProgressUpdate 和 onPostExecute 都不会被调用,而是调用了 onCancelled() 方法。onCancelled() 方法是在异步任务结束的时候才调用的。时机是和 onPostExecute 方法同样的,只是这两个方法是互斥的,不能同时出现。

@WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }
    
// 线程执行完以后才会被调用
private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
复制代码

AsyncTask 在使用的过程当中的一些限制总结

  • (1)AsyncTask 的类必须在主线程加载
  • (2)AsyncTask 的对象必须在主线程中建立
  • (3)execute 方法必须在 UI 线程中调用
  • (4)不要在主线程中调用 onPreExecute 、doInbackground、onPressUpdate、onPostExecute
  • (5)AsyncTask 的对象只能被调用一次,不然会出错
  • (6)AsyncTask 不太适合作太耗时的操做
  • (7)在 Android 1.6 以前,AsyncTask 是串行执行任务的,Android 1.6的时候 AsyncTask 是并行执行任务的,Android 3.0 以后,为了不并行错误,AsyncTask 又采用一个线程来串行执行任务。
  • (8)若是在 AsyncTask 中的 doInBackGround 中开启了新的线程,咱们执行了 cancle() 方法来中止异步任务,线程是不会被中止的,直到任务执行完成为止,这个过程当中,onProgressUpdate 和 onPostExecute 是不会被调用的
相关文章
相关标签/搜索