————解决AsyncTask使用弊端,并采用Builder模式封装成链式调用的形式
如需下载源码,请访问
https://github.com/fengchuanfang/AsyncTaskApplication
文章原创,转载请注明出处:
Android AsyncTask 优化封装
AsyncTask是Android系统封装的一个轻量级的用来执行异步任务的抽象类,用它可以很容易的开启一个线程执行后台耗时任务,并可以把执行的实时进度和最终结果传递给主线程,以方便在主线程中更新UI。
AsyncTask虽说是轻量级的但用起来也不是很方便,而且还有很多弊端
1、每次使用时,都需要创建一个子类
AsyncTask是一个抽象类,无法通过new关键字直接创建一个实例对象,所以我们要想使用它,就必须创建一个子类去继承它,并指明它的三个泛型
示例如下:
class MyAsycTask extends AsyncTask {
@Override
protected void onPreExecute() {
progressDialog.show();//显示进度框
}
@Override
protected Boolean doInBackground(String... integers) {
publishProgress(currentProgress()); //向UI线程发送当前进度
return doDownload(); //向ui线程反馈下载成功与否
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss(); //隐藏进度框
//TODO:根据result判断下载成功还是失败
}
@Override
protected void onProgressUpdate(Integer... values) {
progressDialog.setMessage("当前下载进度:" + values[0] + "%");//在UI线程中展示当前进度
}
}
然后再在需要使用此异步任务的地方进行开启:
public void startTask() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { //根据不同的api采用并行模式进行开启
new MyAsycTask().executeOnExecutor(THREAD_POOL_EXECUTOR,"参数");
} else {
new MyAsycTask().execute("参数");
}
}
2、生命周期不受Activity的约束
关于AsyncTask的使用有一个很容易被人忽略的地方,那就是在Activity中开启的AsyncTask并不会随着Activity的销毁而销毁。即使Activity已经销毁甚至所属的应用进程已经销毁,AsyncTask还是会一直执行doInBackground(Params... params)方法,直到方法执行完成,然后根据cancel(boolean mayInterruptIfRunning)是否调用,回调onCancelled(Result result)或onPostExecute(Result result)方法。
3、cancel(boolean mayInterruptIfRunning)方法调用不当会失效。
如果的doInBackground()中有不可打断的方法则cancel方法可能会失效,比如BitmapFactory.decodeStream(), IO等操作,也就是即使调用了cancel方法,onPostExecute(Result result)方法仍然有可能被回调。
4、容易导致内存泄漏,Activity不被回收
使用AsyncTask时,经常出现的情况是,在Activity中使用非静态匿名内部AsyncTask类,由于Java内部类的特点,AsyncTask内部类会持有外部类Activity的隐式引用。由于AsyncTask的生命周期可能比Activity的长,当Activity进行销毁AsyncTask还在执行时,由于AsyncTask持有Activity的引用,导致Activity对象无法回收,进而产生内存泄露。
5、Activity被回收,操作UI时出现空指针异常
如果在Activity中使用静态AsynTask子类,依然无法高枕无忧,虽然静态AsyncTask子类中不会持有Activity的隐式引用。但是AsyncTask的生命周期依然可能比Activity的长,当Activity进行销毁,AsyncTask执行回调onPostExecute(Result result)方法时,如果在onPostExecute(Result result)中进行了UI操作,UI对象会随着Activity的回收而被回收,导致UI操作报空指针异常。
6、onPostExecute()回调UI线程不起作用
如果在开启AsyncTask后,Activity重新创建重新创建了例如屏幕旋转等,还在运行的AsyncTask会持有一个Activity的非法引用即之前的Activity实例,会导致AsyncTask执行后的数据丢失而且之前的Activity对象不被回收。
7、多个异步任务同时开启时,存在串行并行问题。
当多个异步任务调用方法execute()同时开启时,api的不同会导致串行或并行的执行方式的不同。
在1.6(Donut)之前,多个异步任务串行执行
从1.6到2.3(Gingerbread),多个异步任务并行执行
3.0(Honeycomb)到现在,如果调用方法execute()开启是串行,调用executeOnExecutor(Executor)是并行。
AnsycTask虽然是个轻量级的异步操作类,方便了子线程与UI线程的数据传递,但是使用不当又会产生很多问题。所以有必要对其进行进一步的封装,以使它在方便我们调用的同时还能消除弊端。
下面采用Builder模式将AsyncTask封装成外观上类似Rxjava的链式调用的形式,
第一步、将AsyncTask经常重写的四个方法,用四个函数接口分别定义一下
public interface IPreExecute {
void onPreExecute();
}
public interface IDoInBackground {
Result doInBackground(IPublishProgress
public interface IProgressUpdate
public interface IPostExecute {
void onPostExecute(Result result);
}
第二步、其中IDoInBackground接口中,doInBackground方法与重写AnyncTask的方法并不一样,多了一个接口IPublishProgress参数,IPublishProgress接口如下:
public interface IPublishProgress
此接口需要自定义的AnyncTask去实现,以方便在doInBackground(IPublishProgress
第三步、另外增加一个函数接口,在方法doInBackground执行结束开始回调方法onPostExecute之前,用来判断所属Activity是否依然处于活跃状态,如果处于活跃状态则回调方法onPostExecute,如果处于非活跃状态则不回调,避免回调后操作UI产生空指针异常。
public interface IIsViewActive {
boolean isViewActive();
}
第四步、新建一个AsyncTask的子类MyAsyncTask持有第一步中所定义的四个函数接口的引用,并重写AsyncTask中经常重写的四个方法
如下:
public class MyAsyncTask extends AsyncTask implements IPublishProgress
在每一个方法中,判断对应函数接口的引用是否为空,不为空则进行相应的回调,
同时实现IPublishProgress接口,在实现方法中showProgress中,调用AsyncTask的publishProgress(values)方法(publishProgress(values)是个final方法,不能重写)。
同时持有IIsViewActive接口的引用,在onPostExecute方法中通过它来判断所属Activity是否处于活跃状态,如果处于活跃状态,则回调IPostExecute接口,否则不回调。
添加start方法,此处采用并行模式进行开启,需要串行模式的可以修改此处。
第五步,现在自定义异步任务类中有很多接口的引用,其实例化对象构建起来比较复杂,所以我们在其内部添加一个Builder,来简化它的构建,同时私有化其构造方法,避免外部直接实例化其对象。
最终代码如下:
public class MyAsyncTask extends AsyncTask implements IPublishProgress {
private IPreExecute mPreExecute;
private IProgressUpdate mProgressUpdate;
private IDoInBackground mDoInBackground;
private IIsViewActive mViewActive;
private IPostExecute mPostExecute;
private MyAsyncTask() {
}
@Override
protected void onPreExecute() {
super.onPreExecute();
if (mPreExecute != null) mPreExecute.onPreExecute();
}
@SafeVarargs
@Override
protected final void onProgressUpdate(Progress... values) {
super.onProgressUpdate(values);
if (mProgressUpdate != null) mProgressUpdate.onProgressUpdate(values);
}
@Override
public Result doInBackground(Params... params) {
return mDoInBackground == null ? null : mDoInBackground.doInBackground(this, params);
}
@Override
protected void onPostExecute(Result result) {
super.onPostExecute(result);
if (mPostExecute != null && (mViewActive == null || mViewActive.isViewActive())) mPostExecute.onPostExecute(result);
}
@SafeVarargs
public final AsyncTask start(Params... params) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return super.executeOnExecutor(THREAD_POOL_EXECUTOR, params);
} else {
return super.execute(params);
}
}
@Override
public void showProgress(Progress[] values) {
this.publishProgress(values);
}
public static Builder newBuilder() {
return new Builder<>();
}
public static class Builder {
private final MyAsyncTask mAsyncTask;
public Builder() {
mAsyncTask = new MyAsyncTask<>();
}
public Builder setPreExecute(IPreExecute preExecute) {
mAsyncTask.mPreExecute = preExecute;
return this;
}
public Builder setProgressUpdate(IProgressUpdate progressUpdate) {
mAsyncTask.mProgressUpdate = progressUpdate;
return this;
}
public Builder setDoInBackground(IDoInBackground doInBackground) {
mAsyncTask.mDoInBackground = doInBackground;
return this;
}
public Builder setViewActive(IIsViewActive viewActive) {
mAsyncTask.mViewActive = viewActive;
return this;
}
public Builder setPostExecute(IPostExecute postExecute) {
mAsyncTask.mPostExecute = postExecute;
return this;
}
@SafeVarargs
public final AsyncTask start(Params... params) {
return mAsyncTask.start(params);
}
}
}
封装后的异步任务类看起来复杂,但用起来简单,而且是个一劳永逸的过程。下面用一个事例演示一下在Activity中的使用,在方法loadData()中使用的是全功能调用方式,在saveData()中使用的是最简短调用方式。
由于MyAsyncTask中所持有的接口引用在使用时均添加了非空判断,所以在通过Builder构建MyAsynTask时,并不是每一个接口参数都需要传,可按照实际应用场景只传需要的便可。
public class MainActivity extends AppCompatActivity {
private TextView mainTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainTextView = findViewById(R.id.main_textview);
loadData();
}
/**
* 全功能调用方式
*/
private void loadData() {
MyAsyncTask.newBuilder()
.setPreExecute(new IPreExecute() {
@Override
public void onPreExecute() {
mainTextView.setText("开始下载数据……");
}
})
.setDoInBackground(new IDoInBackground() {
@Override
public Boolean doInBackground(IPublishProgress publishProgress, String... strings) {
try {
for (int i = 1; i < 11; i++) {
Thread.sleep(1000);
publishProgress.showProgress(i);
}
} catch (Exception e) {
return false;
}
return true;
}
})
.setProgressUpdate(new IProgressUpdate() {
@Override
public void onProgressUpdate(Integer... values) {
mainTextView.setText("正在下载数据,当前进度为:" + (values[0] * 100 / 10) + "%");
}
})
.setViewActive(new IIsViewActive() {
@Override
public boolean isViewActive() {
return MainActivity.this.isViewActive();
}
})
.setPostExecute(new IPostExecute() {
@Override
public void onPostExecute(Boolean aBoolean) {
if (aBoolean) {
mainTextView.setText("下载成功");
} else {
mainTextView.setText("下载失败");
}
}
})
.start("参数");
}
/**
* @return 判断当前Activity是否处于活跃状态
*/
public boolean isViewActive() {
return !(isFinishing() || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && isDestroyed()));
}
/**
* 最简短的调用方式
*/
private void saveData() {
MyAsyncTask.newBuilder()
.setDoInBackground(new IDoInBackground() {
@Override
public Void doInBackground(IPublishProgress publishProgress, Void... voids) {
//TODO:执行数据保存
return null;
}
})
.start();
}
}