您的位置:首页 > 其它

AsyncTask 必知必会

2016-09-22 15:43 411 查看

概要

AsyncTask
在后台执行操作,并把结果发布到 UI 线程,避免手动调用 Thread 和 Handler.

官网提到
AsyncTask
适用于短时间操作,最多几秒时间。why? 后面源码分析说明。

官网还提到,如果执行长时间操作,强烈推荐我们使用并发包下的
Executor
,
ThreadPoolExecutor
,
FutureTask
。 这个怎么玩呢? 后面举例。

例子

我把官网的小例子改编了下

private class DownloadFilesTask extends AsyncTask<URL, Void, 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]);
}
return totalSize;
}

protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}


执行
AsyncTask
代码如下

new DownloadFilesTask().execute(url1, url2, url3);


构造方法

给开发者都调用的构造方法只有无参的构造方法

/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public AsyncTask() {
this((Looper) null);
}


从注释看,构造方法必须在
UI thread
中调用,why? 然后调用了带
Looper
参数的构造方法

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


这个构造函数只初始化了三个变量
mHandler
,
mWorker
,
mFuture


mWorker
类型是
Callable
,是用来执行的任务。

mFuture
最主要的作用是用来获取执行的结果,例如
AsyncTask
get()
方法就是用
mFuture
来获取结果的。这里还复写了
done()
方法,也就是任务
mWorker
任务执行完毕后,会调用的,这里只是简单保证了结果会被发送到
UI Thread
中。

mHandler
因为传入的参数为
null
,所以它的值为
getMainHandler()


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

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


也就是说
mHandler
也就是用主线程
Looper
创建的一个
Handler
对象, 用来传递结果,以及更新进度。

这也就回答了为何要在
UI thread
中创建
AsyncTask
对象,因为要保证用
Handler
能把消息发送回
UI thread


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

// 更新 asyncTask 状态
mStatus = Status.RUNNING;

// 后台任务执行前调用
onPreExecute();

mWorker.mParams = params;
//执行任务
exec.execute(mFuture);

return this;
}


execute()
方法实际是调用了
executeOnExecutor()
方法,这两个方法都加上了
@MainThread
注解,告诉这两个方法需要在
main thread
中调用,原理与前面一样。

executeOnExecutor()
中通过对
mStatus
的判断的那段代码,就可以知晓为何一个
AsyncTask
对象只能调用一次
execute()
方法了。

executeOnExecutor()
最后用
sDefaultExecutor
execute()
方法执行了
mFuture
任务。看看
sDefaultExecutor


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 volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new 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);
}
}
}


sDefaultExecutor
是静态的对象,也就说所有的
AsyncTask
共享一个
sDefaultExecutor
,那么如果创建了 N 个
AsyncTask
对象并且调用
execute()
方法,都会用
sDefaultExecutor
执行任务。

sDefaultExecutor
决定了
AsyncTask
执行任务的顺序,是顺序执行任务还是并行执行任务。 默认的是用的
SerialExecutor
,它保证任务的顺序执行,怎么保证了?

SerialExecutor
execute()
scheduleNext()
方法都添加了
synchronized
关键字,也就是任意时刻,只有一个任务能提交,而且从代码逻辑 看,提交之后,必须执行完,才能提交第二个任务并执行,如此循环下去,就保证了任务的顺序执行。

OK, 现在回到正题, 现在是用
sDefaultExecutor
提交了
mFuture
, 而
mFuture
是由
mWorker
构造,那么就会执行
mWorker
call()
方法

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


call()
方法中调用了喜闻乐见的
doInBackground()
方法,注意,这个方法是在后台线程中执行的,由
sDefaultExecutor
保证的。

然后在异常处理的代码中发现,在发生异常的时候,首先会把任务设置为取消状态,然后再抛出。

finally
中,用
postResult(result)
把结果发送出去,最后会传递到
UI thread
中,代码如下

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

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 void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}


finish()
方法中可以看到,如果任务取消了(前面提到的异常 或者 手动调用
asyncTask.cancel(boolean)
),就会调用
onCancelled()
回调,如果是正常流程,就执行
onPostExecute()
回调。

并发执行任务

默认情况下,
AsyncTask
是顺序执行任务,那么可不可以并行执行任务呢?我们只要改变执行任务的
Executor
就行了。
setDefaultExecutor()
方法可以改变默认的
Executor
,但是对开发者来说这个方法是不可以的

/** @hide */
public static void setDefaultExecutor(Executor exec) {
sDefaultExecutor = exec;
}


这个方法是隐藏的,why? 因为一旦开发者改变了
AsyncTask
sDefaultExecutor
,一会并行执行一会顺序执行,那岂不是乱套了~

那么利用
executeOnExecutor()
方法自己传入
Executor


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


AsyncTask
同时也提供了一个线程池用来并行执行任务

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


也就是说
new MyAsyncTask().execute(THREAD_POOL_EXECUTOR, url1, url2, url3)
就可以并行执行多个任务。

那有人会问,既然可以并行,为何默认要顺序执行? 问题就出在
ThreadPoolExecutor
上,如果任务的处理速度超过了任务的提交速度,那么并行完全 OK。 然而,如果提交的任务的速度远远大于任务的处理速度,那么 CPU 超负荷,性能得不到保障。因此,如果想要并行,就要考虑如何构造
ThreadPoolExecutor
对象。

进度更新

到现在为止,我还没有提到如何更新进度,官方的例子如下

private class DownloadFilesTask 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");
}
}


从例子中可以看到,在
doInBackground()
方法中,调用了
publishProgress()
方法来发布进度,源码如下

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

private static class InternalHandler extends Handler {
// ...

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


publishProgress()
是通过
mHandler
把消息传回
UI thread
,然后回调了
onProgressUpdate()
(这个方法在例子中复写了)。 但是我们要注意,只有在这个任务没有被取消的情况下,才能发布进度。

如何正确取消任务

可能通过
AsyncTask
cancel()
方法取消任务

public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}


从源码实现看,参数
mayInterruptIfRunning
代表是否中断线程。如果线程不能响应中断,那么这种方式是无法取消任务的,只能等后台任务执行完毕,然后回调
onCancelled()
而不是
onPostExecute()


哪些情况线程能响应中断,请参考
Thread
类的
interrupt()
方法.

例子中,在
doInBackground()
方法中,用
for
循环,进行了多个下载任务,需要注意的是在
publishProgress()
方法后,会调用
isCancelled()
来判断任务是否取消,如果取消了就不再进行下一个下载任务。 这给了我们一个很好的示范,如果在
doInbackground()
中通过循环来处理一些事情,需要在恰当的位置判断任务是否中断,从而决定任务是否进行下一次循环。

执行长时间任务

开头提到,官网说如果运行的不是几秒的任务,而是长时间的任务,就不要用
AsyncTask
,而是用
Executor
,
ThreadPoolExecutor
,
FutureTask
。 其实这是因为
AsyncTask
默认顺序执行任务,如果某一个任务执行时间过长,会导致后面的任务延迟太长,而如果你很有把握,可以并发执行,也就不存在所谓的不能执行长时间任务了。

那么,既然官网都说了这茬,那么如何做呢? 如果你对 Java 并发的这些类掌握得比较好的话,就很容易了。

demo 代码如下

public class TestActivity extends AppCompatActivity {
private TextView mTextView;
private Handler mUIHandler;
private FutureTask<String> mFutureTask;
private ExecutorService mService;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_acrtivity);
mTextView = findViewById(R.id.tv);
mService = Executors.newCachedThreadPool();
mFutureTask = new FutureTask<String>(new Task()) {
@Override
protected void done() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mUIHandler.sendEmptyMessage(666);
}
});
}
};
mUIHandler = new Handler(getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == 666) {
try {
mTextView.setText(mFutureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
};
}

@Override
protected void onResume() {
super.onResume();
mService.execute(mFutureTask);
}

private static class Task implements Callable<String> {
@Override
public String call() throws Exception {
Log.d("david", "doInBackground ... ");
// 假如这里3秒代表的是30秒,甚至更长时间运行的任务
TimeUnit.SECONDS.sleep(3);
return "Download has completed!";
}
}

}


原理与
AsyncTask
一致,我就不献丑解释代码了。

那么,你可能有疑问,这明明可以通过
Thread
Handler
实现的,为何要搞这么复杂。 简单的理由有几个

Thread 类执行任务无法查询执行的结果,也没有 API 调用来取消任务。

并发任务需要用
ThreadPoolExecutor
,而 Thread 类无法做到这一点。

其实把代码做下封装,基本上就是
AsyncTask
了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  asynctask 源码