针对AsyncTask的超时处理
为什么要处理超时?
有些时候,AsyncTask后台处理超时,造成无法收到AsyncTask结果。虽然这种情况比较少出现,但我们必须要考虑到出现这种情况的处理。另外,要强调一点是,比较耗时的任务不宜使用AsyncTask,比较好的选择是使用线程池实现。
源码分析
先看AsyncTask相关源码:
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
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 occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
从上面的代码中,可以看出在Asynctask构造里定义了两个对象,分别是WorkerRunnable和FutureTask对象。这里感兴趣的是FutureTask对象调用了get()方法,请看get()方法源码:
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return The computed result.
*
* @throws CancellationException If the computation was cancelled.
* @throws ExecutionException If the computation threw an exception.
* @throws InterruptedException If the current thread was interrupted
* while waiting.
*/
public final Result get() throws InterruptedException, ExecutionException {
return mFuture.get();
}
其中,mFuture是一个FutureTask对象,也就是调用的实际是FutureTask的get()方法。在调用get方法时,若任务取消会抛出一个 CancellationException 异常,若发生错误会抛出一个ExecutionException 异常,若当前线程被中断时会抛出一个InterruptedException 异常。
接下来看FutureTask中get方法实现:
/**
* @throws CancellationException {@inheritDoc}
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
get()方法又进一步调用了awaitDone方法,并声明了两个异常InterruptedException, ExecutionException,
/**
* Awaits completion or aborts on interrupt or timeout.
*
* @param timed true if use timed waits
* @param nanos time to wait, if timed
* @return state upon completion
*/
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
long last = timed ? System.nanoTime() : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
long now = System.nanoTime();
if ((nanos -= (now - last)) <= 0L) {
removeWaiter(q);
return state;
}
last = now;
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
简单来说,awaitDone在超时时间内执行任务当超过超时时间时会中止任务。进一步来说执行任务是从任务队列中移除任务的同时然后执行的。(详细此处不再分析)
超时问题解决方案:
回到上面的get方法,查看源码,还可以看到有一个get方法的重载:
/**
* @throws CancellationException {@inheritDoc}
*/
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}
这个有参的get方法同上面无参的get方法一样,调用的是也是 get(long timeout, TimeUnit unit)方法。当调用有参的get方法时就相当于设置了超时时间,如果到达超时时间任务未执行完就会抛出一个TimeoutException异常。
而默认调用的是无参的get方法,即相当于asynctask未设置超时时间。
根据上面一系列的源码分析,我们可以在外部直接调用有参的get方法设置超时时间。
即:
解决方案一:
int TIME_OUT=3*1000;
try {
AsyncTask(此处应该是它的实例化对象).execute().get(TIME_OUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
//处理超时操作,如:弹出提示框,跳转页面等操作,处理界面的话需要通过handler否则会造成ANR
}
解决方案二:
这种方法稍微有些麻烦,就是在asynctask中的doInBackground方法中定义一个定时器任务,当达到超时时间后,该定时器需要执行的操作是取消asynctask任务。使用Timer的 schedule (TimerTask task, Date when)实现。同时设置一个任务完成的标志位。当任务完成时就改变该标志位的值。接着在onpostExecute中根据标志位,做相应的处理,若超时则弹出提示框,页面跳转或是其他处理,否则就直接更新UI。
示例代码:
public class MainActivity extends Activity {
private final static int TIME_OUT = 3 * 1000;
private final static int SLEEP_TIME = 2 * 1000;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new CancelSelfWhenTimeOutTask().execute();
}
private class CancelSelfWhenTimeOutTask extends AsyncTask<Void, Void, Void> {
private boolean done = false;
@Override
protected Void doInBackground(Void... params) {
cancelSelfWhenTimeOut();//定义一个定时器(Timer),若超过指定时间,则取消任务
doTask();//执行耗时任务
return null;
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
//任务完成,发送handler更新ui
}
private void cancelSelfWhenTimeOut() {
//当时间超过TIME_OUT时,若done标志位仍为false,则取消asynctask任务
new Timer().schedule(new TimerTask() {
@Override
public void run() {
if (!done) {
CancelSelfWhenTimeOutTask.this.cancel(true);
}
}
}, TIME_OUT);
}
private void doTask() {
try {
//执行耗时任务
done = true;//改变标志位
} catch (InterruptedException e) {
}
}
}
}