浅谈AsyncTask源码实现

栏目: IOS · Android · 发布时间: 5年前

内容简介:AsyncTask是Android提供的专门用于处理异步任务和UI线程之间交互的类,该类是一个抽象类,doInBackground()方法是唯一抽象方法,方法调用位于子线程中,专门用于处理耗时任务。AsyncTask线程部分逻辑使用的是FutureTask+Callable+线程池,在子线程与UI线程之间数据交互使用的是Handler+Message,在Android不同版本之间虽然有差异,不过源码核心实现机制没有太大变动,有关版本差异性在文末做介绍。AsyncTask的三个泛型类型如下:

AsyncTask介绍

AsyncTask是Android提供的专门用于处理异步任务和UI线程之间交互的类,该类是一个抽象类,doInBackground()方法是唯一抽象方法,方法调用位于子线程中,专门用于处理耗时任务。

AsyncTask线程部分逻辑使用的是FutureTask+Callable+线程池,在子线程与UI线程之间数据交互使用的是Handler+Message,在Android不同版本之间虽然有差异,不过源码核心实现机制没有太大变动,有关版本差异性在文末做介绍。

AsyncTask的三个泛型类型如下:

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

AsyncTask处理任务是一般经过如下四步:

  1. onPreExecute() 该方法会在后台任务开始执行之间调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
  2. doInBackground(Params…) 该方法的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress…)方法来完成。
  3. onProgressUpdate(Progress…) 当在后台任务中调用了publishProgress(Progress…)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
  4. onPostExecute(Result) 当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。

AsyncTask使用时注意事项:

  • AsyncTask类必须在主线程中加载,在Android4.1及其以后默认会自动在主线程加载。
  • AsyncTask对象必须在主线程中创建。
  • 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法 。
  • AsyncTask对象的execute方法必须在主线程中调用。
  • 一个AsyncTask对象只能调用一次execute()方法。

AsyncTask的典型应用

MyAsyncTask<String,Integer,String> task=new MyAsyncTask<String,Integer,String>() {
	@Override
	protected String doInBackground(String... strings) {//子线程
		Log.d(TAG,"doInBackground");
		//TODO
		int progress;
		publishProgress(progress);//该方法在一般在子线程
		return result;
	}
 
	@Override
	protected void onProgressUpdate(Integer... values) {//UI线程进度更新
		Log.d(TAG,"onProgressUpdate");
		//TODO
	}
 
	@Override
	protected void onCancelled() {//UI线程
		Log.d(TAG,"onCancelled");
		//TODO
	}
 
	@Override
	protected void onPostExecute(String s) {//UI线程
		Log.d(TAG,"onPostExecute");
		//TODO
	}
};
task.execute("params");

AsyncTask的源码

本文介绍的源码摘自Android8.0中源码,对应API为26。

构造方法

public AsyncTask() {this((Looper) null);}
/**
 * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
 *
 * @hide
 */
public AsyncTask(@Nullable Handler handler) {
	this(handler != null ? handler.getLooper() : null);
}
/**
 * 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);
			}
		}
	};
}

这里除了无参的构造方法之外,其它两个构造方法都使用了 @hide 标记,意味着只有无参的构造方法是暴露出来的。前面介绍了AsyncTask创建只能在UI线程中,原因在这里已经引出来了,在上面介绍了除了doInBackground(Params…)是在子线程之外,其余的几个回调方法都在UI线程中。异步耗时任务的执行就是在mWorker中执行的,mWorker其实就是一个实现了Callable的抽象类,除此之外添加了一个属性变量Params数组。

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
	Params[] mParams;
}

AsyncTask为什么使用Callable来启动线程,这里就不做详细介绍了,简单说明一下,它不但可以有返回值还可以抛出异常,另外配合FutureTask的操作,还可以设置取消操作,这些优势可以说是Callable处理异步耗时任务的杀手锏。由于mWorker中call()方法的执行都在子线程中,那么执行耗时任务完成之后需要通知UI线程,这时候消息传递就是通过mHandler,这也就意味着mHandler如果是在主线程创建的话,在handleMessage()接收到的消息都会位于UI线程。

AsyncTask默认构造方法中传入的Looper是null,意味着mHandler使用的是getMainHandler()。

private static Handler getMainHandler() {
	synchronized (AsyncTask.class) {
		if (sHandler == null) {
			sHandler = new InternalHandler(Looper.getMainLooper());
		}
		return sHandler;
	}
}

getMainHandler()是静态方法并且加锁了,sHandler本身是一个静态变量,所以这里其实就是sHandler的单例模式。在AsyncTask类中需要在UI线程调用的方法使用了 @MainThread 注解,但是AsyncTask无参的构造方法却没有加上 @MainThread 注解,而构造方法最后调用的是一个带有Looper的构造方法,个人认为AsyncTask并不是说一定需要在UI线程创建,但是由于execute()方法加上了 @MainThread 注解,并且在execute()方法中有一个onPreExecute()UI线程的回调方法,由于受调用方法以及回调方法的约束,所以多数情况下AsyncTask需要在UI线程创建。如果一个业务场景想使用AsyncTask,但是并不需要更新UI,这时候也可以不在UI线程创建,说实话并不建议这么使用,由于使用了 @hide 注解,也许后续某个版本就剔除了该构造方法。

execute方法

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

execute()方法才是AsyncTask的执行方法,入参Params也是通过execute()方法传入到doInBackground()方法中的。前面说过execute()方法只能执行一次,并且也只能在UI线程调用,原因就在executeOnExecutor()方法中,可以发现该方法是一个final修饰符修饰的方法,也意味着该方法不可以被重载。在executeOnExecutor()方法调用的第一行就判断了AsyncTask当前对象的执行状态Status是否是PENDING状态,这个状态是初始状态,如果是非PENDING状态就会抛出异常,而一旦调用了executeOnExecutor()状态就会立刻变为RUNNING状态,再者后台任务执行完成后AsyncTask的状态会变为FINISHED状态,所以execute()方法只能调用一次。为什么必须在主线程中调用,这一点主要是因为onPreExecute()方法的调用,由于该回调方法需要在UI线程使用,而且这里没有Handler传递消息再调用onPreExecute()方法。

虽然execute()方法的执行使用了线程池,但是在Android3.0以及以上版本中任务的执行确实顺序的。在execute()方法调用的executeOnExecutor()方法中,线程池出入的是sDefaultExecutor成员变量,有关sDefaultExecutor源码如下:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
public static final Executor THREAD_POOL_EXECUTOR;
 
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;
}
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虽然也是使用线程池处理异步任务,但是仍然是顺序执行的,主要原因就在SerialExecutor实现中,在Android3.0以前是没有SerialExecutor的。SerialExecutor实现这里其实来自 Java 的JDK的示例中,在接口Executor的API给出的示例。正常情况下线程池的并发执行数量跟设置的corePoolSize和maximumPoolSize有关的,而threadPoolExecutor中设置的数值很显然值都是大于1的,为了仍然让并发任务顺序执行,这里使用了一个双向的队列ArrayDeque。当调用execute()方法,这时候实际上是将任务放入了ArrayDeque双向队列中,而在ArrayDeque中的任务实际上是在上一个异步任务执行完成后才可以执行下一个任务,即Runnable中run()方法执行完成后才可以继续执行下一个异步任务Runnable中run()方法。如果开发者不想使用AsyncTask提供的默认异步任务顺序执行,可以直接调用executeOnExecutor()方法,传入一个自定义的线程池。

postResult()和postResultIfNotInvoked()方法

postResult()和postResultIfNotInvoked()方法源代码如下:

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

从这里可以看出postResultIfNotInvoked()其实调用了postResult()方法,唯一的不同就在于对于wasTaskInvoked的判断,该值取值自mTaskInvoked,mTaskInvoked在AsyncTask的构造方法mWorker的call()方法第一行就被数值为了true。一般执行流程在mWorker的call()方法执行完成后一定会执行postResult()方法,但是由于在某些情况下如异步方法call()方法还没有执行就被用户取消掉了,那么这时候call()方法就不会被执行了,因此才会在FutureTask的done()方法中再次进行确认,判断call()方法有没有执行,如果没有执行再发送执行结果。通过postResultIfNotInvoked()和postResult()方法实际上是为了确保一旦AsyncTask调用了execute()或者executeOnExecutor()方法,那么一定有一个回调方法无论是onCanceled()或者onPostExecute()回调到UI线程。

postResult()方法通过getHandler()方法将AsyncTaskResult对象发送到了主线程。getHandler()方法在AsyncTask构造方法()被赋值,在构造方法中已经做了分析,这里可以简单的理解为就是getMainHandler()方法。

private void finish(Result result) {
	if (isCancelled()) {
		onCancelled(result);
	} else {
		onPostExecute(result);
	}
	mStatus = Status.FINISHED;
}
 
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;
		}
	}
}
private static class AsyncTaskResult<Data> {
	final AsyncTask mTask;
	final Data[] mData;
 
	AsyncTaskResult(AsyncTask task, Data... data) {
		mTask = task;
		mData = data;
	}
}

在finish()方法中对任务是否取消进行了判断,如果任务已经取消了isCancelled(),则回调onCancelled()方法,否则调用onPostExecute()方法。

AsyncTask任务取消逻辑

AsyncTask涉及到任务取消的字段以方法如下:

private final AtomicBoolean mCancelled = new AtomicBoolean();
//判断任务是否取消
public final boolean isCancelled() {
	return mCancelled.get();
}
//主动取消异步任务
public final boolean cancel(boolean mayInterruptIfRunning) {
	mCancelled.set(true);
	return mFuture.cancel(mayInterruptIfRunning);
}
//取消后UI可以调用的方法
protected void onCancelled() {}

由于异步任务一般比较耗时,为了防止不必须要的等待所以提供任务取消方法是必须的。mCancelled值被赋值为true有两处,另外一处位于AsyncTask构造方法mWorker的call()方法中,在call()方法中一旦执行的任务有异常,则在异常中直接将mCancelled变量设置为true。

isCancelled()方法使用的是mCancelled原子变量判断的,而在cancel()方法中直接调用了mFuture的cancel()方法,那么isCancelled()方法是否也可以直接调用mFuture的isCancelled()方法呢?其实答案是肯定的,在以前的版本中就是直接使用的mFuture的isCancelled()方法,现在使用mCancelled原子变量,应该主要原因是原子变量比Handler+Message消息传递效率更高。这里如果想要调用mFuture的isCancelled()方法,则需要在使用mCancelled变量的地方使用Handler发送一个标识取消类型的消息,也意味着需要再增加一个消息常量用户标识任务取消。

其它

线程池相关设计

线程池相关参数

Android2.3.3、4.0、4.1的实现

private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 1;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(10);
public static final Executor THREAD_POOL_EXECUTOR
	= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
			TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

Android4.4以后的实现

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;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

可以发现在以前的很多迭代版本线程池的核心线程数都是5,最大线程数目是128,阻塞队列长度为10,也意味着AsyncTask中对多可以同时存放的异步任务数目是138个,当达到138个时就会执行线程池的任务拒绝策略进行处理。

但是在Android3.0及其以后AsyncTask加入了SerialExecutor以及executeOnExecutor()方法,默认情况下调用execute()方法时,异步任务是顺序执行的。在SerialExecutor的mTasks队列并没有设置队列上限,这也意味着对加入的异步任务没有限制,其实无论加入多少异步任务,这时候线程池中只有一个线程在执行异步任务。

在Android4.4以及后续版本源码中对线程池中个参数都进行了优化,将线程池的核心线程数和最大线程数都设计成了动态的,跟手机CPU密切相关,并且空闲线程可以存活的时间也有原来的1s更改为了30s。阻塞队列大小由原来的10更改为了128,实际上可以在线程池中同时执行的最大任务数目和原来4.4以前的差别不是很大。

Handler逻辑设计

我们说过在Android的4.1及其以后版本中AsyncTask默认会在UI线程加载。在AsyncTask使用注意事项中也说明了,AsyncTask必须在UI线程加载,其实主要还是因为Handler中消息接收一定要在UI线程。如下是不同版本中有关Handler处理源码:

Android4.1源码:

//AsyncTask源码
/** @hide Used to force static handler to be created. */
public static void init() {
	sHandler.getLooper();
}
//ActivityThread源码
public static void main(String[] args) {
        SamplingProfilerIntegration.start();
 
	...
	
	Looper.prepareMainLooper();
	if (sMainThreadHandler == null) {
		sMainThreadHandler = new Handler();
	}
 
	ActivityThread thread = new ActivityThread();
	thread.attach(false);
 
	//init方法
	AsyncTask.init();
 
	if (false) {
		Looper.myLooper().setMessageLogging(new
				LogPrinter(Log.DEBUG, "ActivityThread"));
	}
 
	Looper.loop();
 
	throw new RuntimeException("Main thread loop unexpectedly exited");
}

从源码可以看出AsyncTask一定会在UI线程中加载,因为在ActivityThread的main()方法中调用了AsyncTask的init()方法。在本文使用的Android8.0的AsyncTask源码中已经剔除了init()方法,取而代之的是新加入了一个getMainHandler()的方法,该方法主要目的就是确保Handler中消息一定要在UI线程,所以无论哪种实现机制其目的都是一样的,为了确保消息可以在UI线程接收。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

谁说商业直觉是天生的

谁说商业直觉是天生的

[美] 戴夫·帕特奈克 (Dev Patnaik)、[美] 彼得·莫特森 (Peter Mortensen) / 马慧 / 万卷出版公司 / 2010-07 / 36.00

《Wired to Care》是帕特奈克集近年来在创新顾问公司 Jump Associates 实务经验,与史丹佛大学教学经验之大成,虽然《Wired to Care》定位为一本用设计创新方法谈企业管理的书,但本书,活像是一本近代的设计史,从以销售为设计目标的Raymond Loewy谈起,到以人为设计中心的OXO GOOD GRIPSSwivelPeeler削皮刀。由此作者向我们揭示了企业如何运......一起来看看 《谁说商业直觉是天生的》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

SHA 加密
SHA 加密

SHA 加密工具