根据下列的问题来加深对AsyncTask源码的理解
由于个人水平有限,若本文存在问题,还望指出。
1.AsyncTask中封装了几个线程池,作用分别是啥。
2.AsyncTask对象为何要在主线程创建
3.AsyncTask对象为何只能执行一次
使用场景
在Android中封装了子线程的类有很多,如IntentService,AsyncTask,HandlerThread等,它们有着不同的使用场景。IntentService是Service的封装,方便service进行耗时操作的处理,同时作为四大组件之一,它的优先级很高,不容易被杀死。AsyncTask里面封装了线程池和handler,适用于多任务的处理的和主线程与子线程之间的信息交互。HandlerThread里面封装好了Looper。
基本使用
1.最简单的使用
AsyncTask task = new AsyncTask() {
@Override
protected Object doInBackground(Object[] objects) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
TLog.e("task",Thread.currentThread().getName());
return null;
}
};
task.execute();
上述代码,将AsyncTask做为内部类使用,且只重写了AsyncTask中的抽象方法。代码日志为:
可以看出在doInBackground中函数中,使用的是子线程,这也是为啥可以在这个函数中做耗时操作的原因。但上述代码中存在一个非常明显的问题。内存泄漏,因为内部类会隐式的持有外部类的引用,且在内部类中有耗时操作。于是,在我们使用Asynctask时,一般都是建议使用静态类或者外部类,如下代码所示。
public static class MyTask extends AsyncTask<Void,Integer,Boolean>{
@Override
protected Boolean doInBackground(Void... voids) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
且如果需要在该类中使用context时,建议使用Application或者采用弱引用。如以下代码所示。
Context mContext;
Context context;
WeakReference<MainActivity> mReference;
public MyTask(Context context){
mContext = context.getApplicationContext();
}
public MyTask(MainActivity activity){
mReference = new WeakReference<>(activity);
context = mReference.get();
}
成员变量
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static final int MESSAGE_POST_RESULT = 0x1;
private static final int MESSAGE_POST_PROGRESS = 0x2;
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static InternalHandler sHandler;
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
private volatile Status mStatus = Status.PENDING;
public static final Executor THREAD_POOL_EXECUTOR;
SERIAL_EXECUTOR:串行线程池,串行取出WorkRunable(API-26)
MESSAGE: UI线程与doInbackground中的子线程通信的消息
sHandler :UI线程中的handler
mWorker:实现了callable接口,在mWorker中封装了doinbackground的方法,doinbackground方法的输入参数为Params,并将结果以Result参数返回。
(callable接口和runnable接口类似,只不过callable支持泛型和有返回值)
mFuture:实现了runable接口和future接口,线程池中执行的runnable为此mFuture
mStatus:Asynctask当前实例的状态,在执行的时候,若mStatus的状态部位Pending,则会抛出异常,这是限制同一AsyncTask实例执行两次的源头。
THREAD_POOL_EXECUTOR:处理任务的线程池
从这个这些参数可以得出以下几点:
1.看出AsyncTask中有两个线程池
2.主线程和子线程之间的通信也是通过handler进行
方法介绍
1.构造方法
源码中有3个构造方法,但实际上就一个,如下所示。(把源码那些非主要逻辑的代码给清楚掉了,这样看出去更清楚。)
Creates a new asynchronous task. This constructor must be invoked on the UI thread.> 源码英文介绍的意思大概是,创建一个异步的任务,这个构造器必须在ui线程执行。
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 {
...
Result result = null;
try {
...
result = doInBackground(mParams);
...
} catch (Throwable tr) {
...
} finally {
postResult(result);
}
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
...
}
}
};
}
如果非要转牛角尖,该构造函数,在有loop的子线程中创建,其实也不会报错,也可以正常执行,但源码上面的都说了要在UI线程执行了,就在UI线程执行吧,还有另外一点是低版本的源码是必须在UI线程执行的,不然要出错,权当是为了兼容低版本吧。
API-26版本中的Handler的创建,如下所示
API-26
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}
低版本
private static final InternalHandler sHandler = new InternalHandler();
API-26中的handler使用的loop是主线程中的loop。
低版本中的Handler的使用的是创建AsyncTask中的线程中的loop。(静态成员会在加载类时进行初始化)从这点来看的话,AsyncTask就必须要在主线程中创建了。
该构造函数中,创建了mWorker 和mFuture两个重要的成员变量。
mWorker中最重要的两个方法是doInBackground和postResult,doInBackground方法是一个抽象方法,输入参数为泛型Params,是我们必须重写的,然后在里面写具体的处理逻辑,返回值为泛型Result。postResult为将doInBackground方法的返回通过handler发送给主线程。
mFuture方法既然实现了runable接口,那么我们肯定要看它的run方法,代码如下。
public void run() {
...
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
....
}
if (ran)
set(result);
}
} finally {
....
}
}
上述代码中callable就是mWorker,然后调用了mWorker.call,返回值result就是doInBackground的返回值。看到这,可以知道,在mFuture中执行了mWorker,mWorker中执行了doInbackground,即具体的子线程处理逻辑,然后将结果通过postResult方法中的handler传递给了主线程。
2.execute方法
执行方法也有3个,我们常用的为两个,且第一个是对第二个的封装。
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
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()方法,然后在该方法中执行executeOnExecutor方法,executeOnExecutor方法中第一个参数为线程池,这里使用的是sDefaultExecutor线程池,该线程池将mWorker排队取出,一个接一个的扔到THREAD_POOL_EXECUTOR线程池中串行执行(执行完一个再扔下一个)。如果要并行执行,则可以直接使用executeOnExecutor方法,并将输入参数exec设置成THREAD_POOL_EXECUTOR线程池。
执行逻辑
在这里可以看到通过mStatus成员变量去判断该Asynctask的实例是否第一次执行,若不是,则会抛出异常。然后将Params参数赋值给了mWork.mParams,即execute方法中输入的Params最终到了doInbackground的输入,接着通过exec线程池执行mFuture。AsyncTask默认的是串行执行(低版本的有并行执行的,但如今的高版本都是串行执行),使用的是sDefaultExecutor线程池。