AsyncTask是Android中常用的异步任务解决方案,在面试中面试官经常会问到有关AsyncTask相关的点,很多人只知道如何去用AsyncTask,没有深入过源码理解其原理,这在面试的时候往往对自己不利,本文从源码角度解读AsyncTask的原理及使用,相信读完本文你就可以很好地应对Android面试中有关AsyncTask的问题。

首先,AsyncTask是一个抽象类,我们使用的时候需要自定义一个类去继承AsyncTask,在继承时我们可以为AsyncTask类指定三个泛型参数,这三个参数的用途如下:

  1. Params
    在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
  2. Progress
    后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
  3. Result
    当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。

AsyncTask这个抽象类中有一个抽象方法protected abstract Result doInBackground(Params... params),所以如果要自定义一个自己的AsyncTask必须实现该抽象方法,在doInBackground中做自己的耗时操作的逻辑。

几个回调方法:

  1. onPreExecute()
    这个方法会在后台任务开始执行之间调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。

  2. doInBackground(Params…)
    这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress…)方法来完成。

  3. onProgressUpdate(Progress…)
    当在后台任务中调用了publishProgress(Progress…)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。

  4. onPostExecute(Result)
    当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。


放一张AsyncTask的核心逻辑图,大家可以先看图再看后面的具体介绍,读完介绍之后再回过头来看一下这张图,理解会更深刻(建议查看大图):
在这里插入图片描述
如果还看不清见原图地址:https://github.com/DmrfCoder/interview/blob/master/resource/AsyncTask.png

构造方法

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

在构造方法中没有执行过多的逻辑,只是初始化了三个对象:Handler mHandlerWorkerRunnable mWorkerFutureTask mFuture

接着要想启动一个任务,我们就需要调用该任务的excute()方法

excute()

excute()方法的源码:

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

excute()源码中调用了executeOnExecutor方法,传入了两个参数:sDefaultExecutorparams

excuteOnExcutor()

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

在该方法中先是调用了onPreExecute()方法,因此证明了onPreExecute()方法会第一个被调用,然后执行了exec.execute(mFuture)方法,这个exec实际上从excute()方法中传入的sDefaultExecutor

sDefaultExecutor

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

这里先new出了一个SERIAL_EXECUTOR常量,然后将sDefaultExecutor的值赋值为这个常量,也就是说明,刚才在executeOnExecutor()方法中调用的execute()方法,其实也就是调用的SerialExecutor类中的execute()方法。

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

SerialExecutor类中有一个ArrayDeque的队列,还有一个当前的Runnable mActive对象,在execute()方法中,首先会用mTasks.offer给队列的尾部加入一个匿名的Runnable类,在该Runnable匿名类中执行了excute中传入的Runnable对象的run方法,然后调用scheduleNext,该方法使用mTask.poll()取出队列头部的任务,然后调用THREAD_POOL_EXECUTOR.execute(mActive),这里的THREAD_POOL_EXECUTOR实际上是一个线程池,当当前队头的run方法执行完成之后又会在try..finally中调用scheduleNext()取出下一个任务进入线程池进行执行,所以可以看到,在AsyncTask中实际上有两个线程池,一个是SerialExcutor,另一个是THREAD_POOL_EXCUTOR,他们两是都是静态字段,对于所有的AsyncTask都会公用他们两,前者模仿的是单一线程池,用于做任务调度,利用队列将所有的任务排队,然后每次把队头的任务交给THREAD_POOL_EXCUTOR去做实际的执行,

注意excute方法中的Runnable参数,那么目前这个参数的值是什么呢?当然就是mFuture对象了,也就是说这里的r.run()实际上调用的是FutureTask类的run()方法,而我们刚才在构造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;            }        };

WorkerRunnable是一个抽象类,其实现了Callable接口,所以在这里实现了Callable接口需要的``call方法,call方法里调用了doInBackground()方法,调用完成之后在try…finally中调用了postResult()方法将结果返回,而postResult()`中:

 private Result postResult(Result result) {        @SuppressWarnings("unchecked")        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,                new AsyncTaskResult<Result>(this, result));        message.sendToTarget();        return result;    }

在这里使用getHandler()拿到刚才构造方法中的mHandler对象,然后发出了一条消息,消息中携带了MESSAGE_POST_RESULT常量和一个表示任务执行结果的AsyncTaskResult对象。而这个mHandler实际上是一个InternalHandler对象:

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

可以看到在该InternalHanderhandleMessage方法中接收到了刚才发送的消息,并根据msg.what的不同调用了不同的逻辑,如果这是一条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()方法,这样当前任务的执行就全部结束了。

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

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

正因如此,在doInBackground()方法中调用publishProgress()方法才可以从子线程切换到UI线程,从而完成对UI元素的更新操作。其实也没有什么神秘的,因为说到底,AsyncTask也是使用的异步消息处理机制,只是做了非常好的封装而已。

关于THREAD_POOL_EXECUTOR

刚才提到的THREAD_POOL_EXECUTOR:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();// We want at least 2 threads and at most 4 threads in the core pool,// preferring to have 1 less than the CPU count to avoid saturating// the CPU with background workprivate static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2   1;private static final int KEEP_ALIVE_SECONDS = 30;static {        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,                sPoolWorkQueue, sThreadFactory);        threadPoolExecutor.allowCoreThreadTimeOut(true);        THREAD_POOL_EXECUTOR = threadPoolExecutor;    }

可以看到,AsyncTask实际上是对线程池ThreadPoolExcutor的封装,在实例化ThreadPoolExcotor的时候传入的核心线程数是在2-4个之间,最大线程数是cpu count*2 1个,我们在SerialExecutorscheduleNext方法中使用该线程池去真正执行任务。

AsyncTask默认是串行执行任务的,如果想要并行,从Android 3.0开始AsyncTask增加了executeOnExecutor方法,用该方法可以让AsyncTask并行处理任务。方法签名如下:

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,            Params... params)

exec是一个Executor对象,为了让AsyncTask并行处理任务,第一个参数传入一个线程池对象。第二个参数params表示的是要执行的任务的参数

一个疑问

刚才提到了,AsyncTaskSerialExecutor线程池中做的调度是串行的,也就是说同时只会有一个线程被执行,那这里的ThreadPoolExcutor THREAD_POOL_EXECUTOR为什么没有初始化成singleThreadPool?这点我个人也不是很理解,之前面试的时候有请教过面试官,面试官说是起到调度作用,我个人猜测可能和AsyncTask并行执行任务有关,如上所述,如果想要让AsyncTask并行执行任务需要调用executeOnExecutor并传入线程池,而这里我们可以直接传入AsyncTask帮我们实例化好的线程池对象
AsyncTask.THREAD_POOL_EXECUTOR,这样就不用我们自己创建线程池了,比如:

Executor exec = new ThreadPoolExecutor(15, 200, 10,   TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());new DownloadTask().executeOnExecutor(exec);

当然这只是我个人的理解,欢迎大家在评论区留言发表自己的看法。

总结

以上就是关于AsyncTask源码的入门分析,希望大家再看一遍这张图:
在这里插入图片描述

更多相关文章

  1. Android开发实践:自定义带消息循环(Looper)的工作线程
  2. Android实现屏幕旋转方法总结
  3. Android的startActivityForResult()与onActivityResult()与setRe
  4. #菜鸟之旅#Android Studio开发JNI工程——Native调用Java方法获
  5. Android中线程形态AsyncTask、HandlerThread 和 IntentService简
  6. Android多线程:理解和简单使用总结

随机推荐

  1. Andriod 环境配置以及第一个Android Appl
  2. android 调用系统相机拍照并保存照片原图
  3. Android系统的开机画面显示过程分析(11)
  4. Android(安卓)Textview实现文字颜色渐变
  5. Android(安卓)javah -jni 找不到类的解决
  6. android studio中光线传感器解析
  7. Android(安卓)ViewPager的初始化及遇到的
  8. [Android] 输入法的开发
  9. 安卓APP:利用AndroidStudio开发usb串口通
  10. android p状态栏,插入sim卡,关闭数据流量,状