Android面试必备——AsyncTask源码解析

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 work
private 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源码的入门分析,希望大家再看一遍这张图:
在这里插入图片描述

你可能感兴趣的:(Android,Android,Java面试必备)