主线程和子线程
主线程是指进程所拥有的线程,在Java中默认一个进程只有一个线程,这个线程就是主线程。主线程主要用于处理界面交互相关的逻辑,因为用户随时都可能会和界面发生交互,因此主线程在任何时候都必须要有较快的响应速度,否则就会给人一种卡顿的感觉。这就要求主线程不能处理太耗时的任务,比如网络请求、IO操作等。这样,只能把网络请求、IO这些耗时操作放到子线程中去处理。所以,正常一个带有网络接口请求的应用,那么至少会需要两个线程,一个主线程负责处理交互,一个子线程负责处理接口请求,那么子线程请求接口之后拿到的数据,怎么传递给主线程去更新UI呢?我们可以用Handler来处理子线程与主线程的交互。而AsyncTask就是封装了Handler+线程池来处理子线程与主线程交互的一个API,方便了开发者,不用每次都自己创建线程和写Handler了。
AsyncTask基本使用
创建一个下载文件的异步任务
private class DownloadFileTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
执行该异步任务
DownloadFileTask downloadTask = new DownloadFileTask().execute(url1,url2,url3);
AsyncTask是一个抽象的泛型类,主要接受三个参数,分别是Params、Progress、Result,其中Params主要是AsyncTask执行时传递进来的参数类型,Progress是在异步任务执行的过程中的参数类型,Result是异步任务结束之后的类型。另外,AsyncTask提供了4个核心方法,分别如下:
- onPreExecute(),在主线程中执行,在异步任务开始执行之前调用,一般用于任务执行前的准备工作
- doInBackground(Params... param),在线程池中执行,开始执行异步执行任务,param是通过AsyncTask的execute方法传递进来的,在这个方法中可以通过publishProgress来更新任务的进度,这个方法会调用到onProgressUpdate
- onProgressUpdate(Progress... progress),在主线程中执行,用于更新进度。
- onPostExecute(Result result),在主线程中执行,异步任务执行结束之后执行,其中result参数是doInBackground的返回值。
取消AsyncTask异步任务
downloadTask.cancel(true);
注意:这里的cancel方法的参数既可以传true也可以传false,其中true表示可以中断正在执行的任务。另外,虽然调用了这个cancel方法,但是doInbackground的异步任务,并不会立即结束,只是不会调用onProgressUpdate和onPostExecute。举个例子,doInBackground中有个循环,当执行cancel并不会中断这个循环,如果需要中断这个循环,那么可以在doInBackground中添加一个判断逻辑,如果isCancelled()则return。
AsyncTask在具体的使用过程中也有一些条件限制,主要有如下几点:
- AsyncTask这个类必须在主线程中加载,不过这个在Android4.1及以上版本中已经被系统自动完成。
- 必须在主线程创建该实例对象
- 必须在主线程调用execute(Params...)
- 不要手动调用上面的四个核心方法
- 一个AsyncTask任务只能execute一次,否则会报运行时异常
AsyncTask的发展史
在一开始出现AsyncTask时,只有一个子线程来执行异步任务。从Android1.6开始,从单线程改成了线程池了支持并发任务了。然后从3.0开始为了解决并发导致的错误又改成了串行执行了。如果你还是想并发执行任务,可以调用AsyncTask的executeOnExecutor方法。
AsyncTask的工作原理
为了分析AsyncTask的工作原理,我们先分析它的execute方法,execute方法又调用了executeOnExecutor方法,它们的实现如下:
@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;
}
可以看到,在这里已经开始调用了onPreExecute方法,然后把Params封装到FutureTask对象中了,最后开始执行sDefaultExecutor这个默认线程池。下面分析这个线程池的执行过程,代码如下:
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的实现可以分析出多个AsyncTask同时执行默认是串行的。SerialExecutor首先会将FutureTask插入到队列中,如果发现没有正在执行的Task,那么就从队列中取出一个Task放到THREAD_POOL_EXECUTOR线程池中执行,当执行完成之后再会调用scheduleNext,直到mTasks中的Task都执行完成为止。FutureTask已经放到THREAD_POOL_EXECUTOR线程池中了,那么就会开始执行这个FutureTask了,FutureTask的实现如下:
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);
}
}
};
}
FutureTask的执行从call方法开始,在call中调用了第二个核心方法,就是doInBackground,然后拿到doInBackground的返回值通过postResult(result)传递给AsyncTask的另外一个重要角色InternalHandler,这样就通过Handler机制实现了从子线程传递数据到主线程了,然后在Handler中执行了第四个重要方法onPostExecute来更新UI。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;
}
}
}
可以发现InternalHandler是个静态的内部类,为了能够将执行环境切换到主线程,这就要求创建的Handler对象必须在主线程创建。由于静态成员必须在类加载时初始化,所以才会要求AsyncTask的类必须在主线程中加载。
参考:
https://developer.android.com/reference/android/os/AsyncTask
《Android开发艺术探索-任玉刚》
网友评论