公司目前的项目是个二次开发的项目,加上我接手时项目开发只有我和另外一个实习生,一切都需要自己摸索,导致项目中出现了很多历史遗留问题.好在经过不断的优化,现在的项目已经趋于稳定.早期项目的崩溃率非常高,好在大部分崩溃能在框架中统一处理.比如AsyncTask.下面总结一下AsyncTask中常见的坑:

AsyncTask的cancel()方法无法直接停止子线程

这是一个具有迷惑性的方法.在cancel()方法中,AsyncTask调用了FutureTask的cancel()方法,这里面又调用了子线程的interrupt()方法.但是,interrupt()方法不会直接终止线程,而只会更改一个标志位,表示有中断请求,需要用户在线程中在合适的地方自己判断是否有中断请求并作出相应的处理.不过很遗憾,AsyncTask的源码并没有判断子线程的中断状态,因此,cancel()只能阻止onPostExecute()方法的回调,而不会对子线程做任何处理.当初看到这里时,我曾有个疑惑,为什么不直接使用Thread的stop()方法呢,不就不用进行任何判断了么,但实际上,stop()方法已经被禁用了.因为stop()方法对线程的粗暴停止会产生很多问题.关于Java线程中断机制的具体分析请看这篇文章.

回调onPostExecute()方法时Activity已被销毁

这是最常见的.在AsyncTask回调onPostExecute()时,所在Activity或Fragment可能已经销毁,导致Context和所有View对象都为空.此时临时的处理方法有两种,一是在使用前进行判空,二是在生命周期方法中,调用cancel()方法,虽然cancel()不能直接停止doInBackground(),但cancel后不会再回调onPostExcute()方法.
但一个个地处理太繁琐,而且容易忘,我采用的方法是新建一个继承自AsyncTask的类BaseAsyncTask,在其中重写onPostExecute()方法,在重写的方法中对Activity或Fragment统一进行判空.代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public abstract class BaseAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result>{
protected WeakReference<BaseFragment> mReference = null;
//判断所在Activity已被销毁时是否停止任务
private boolean mIsStopWhenFragmentDestroy;

public BaseAsyncTask(){
}

public BaseAsyncTask(BaseFragment fragment){
if(fragment != null) {
mIsStopWhenFragmentDestroy = true;
this.mReference = new WeakReference<>(fragment);
}
}

@Override
final protected void onPostExecute(Result result) {
if(mIsStopWhenFragmentDestroy){
if (mReference == null || mReference.get() == null
|| !mReference.get().isAdded()
|| mReference.get().getActivity().isFinishing()) {
return;
}
}
onPostExecuteAfterCheck(result);
}

protected void onPostExecuteAfterCheck(Result result){

}
}

以上代码中onPostExcute()方法置为final,防止重写.所有调用者统一重写经过检查后调用的onPostExecuteAfterCheck()方法.同时,考虑到有时需求希望Activity方法被销毁后onPostExecuteAfterCheck()方法仍被回调,因此加了标志位mIsStopWhenFragmentDestroy.另外,由于Handler极易引发内存泄露,因此此处使用弱引用.

AsyncTask的阻塞问题

这真的是一个大坑.Android3.0之后AsyncTask从使用容量为5的线程池变为使用单线程.也就是说,AsyncTask程是全局阻塞的.必须要等上一个AsyncTask执行完了,下一个才会开始执行.项目初期,由于项目的并发性要求不高,并且异步任务耗时都比较短,也就没有在意这个问题.直到有一天晚上出包之前,突然出现很多以前从未遇到的bug(是的,bug总是在出包前夜才出现),排查后发现基本都是AsyncTask没执行的问题,当时项目刚引了一个AR的jar包,因此我猜想可能是jar包中的方法占用了AsyncTask的线程资源,导致自己的AsyncTask全部没执行.
这个问题其实也很好解决,在执行AsyncTask的时候不要调用execute()方法,统一调用executeOnExecutor()方法,并传入一个线程池,这个线程池就由你自己控制了,使用起来非常灵活,这也是Android将AsyncTask改为单任务机制的原因.

参考