Android开发经验谈Android技术知识Android 面试专辑

AsyncTask 面试解析

2019-05-13  本文已影响6人  8e750c8f0fae

[Toc]

基础认识

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

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

作用

AsyncTask 的三种状态

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

状态 描述
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 会被调用,任务执行到这里就结束了。

基本使用

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. 为什么AsyncTask 的对象只能被调用一次,否则会出错?(每个状态只能执行一次)
  2. AsyncTask 的类为什么必须在主线程加载
  3. AsyncTask 的对象为什么必须在主线程中创建
  4. AsyncTask 是串行执行任务还是并行执行任务?
  5. AsyncTask 调用 cancel() 任务是否立即停止执行?onPostExecute() 还会被调用吗?onCancelled() 什么时候被调用?

内部源码分析

构造函数中做了什么

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

    /**
     * 该构造方法必须在 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 做了以下几个工作:

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

    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 是不会被调用的
上一篇 下一篇

猜你喜欢

热点阅读