————解決AsyncTask使用弊端帜羊,并采用Builder模式封裝成鏈?zhǔn)秸{(diào)用的形式
如需下載源碼岔留,請訪問
https://github.com/fengchuanfang/AsyncTaskApplication
文章原創(chuàng)环葵,轉(zhuǎn)載請注明出處:
Android AsyncTask 優(yōu)化封裝
AsyncTask是Android系統(tǒng)封裝的一個輕量級的用來執(zhí)行異步任務(wù)的抽象類烟零,用它可以很容易的開啟一個線程執(zhí)行后臺耗時任務(wù)醉锄,并可以把執(zhí)行的實時進(jìn)度和最終結(jié)果傳遞給主線程偿曙,以方便在主線程中更新UI州既。
AsyncTask雖說是輕量級的但用起來也不是很方便谜洽,而且還有很多弊端
1、每次使用時吴叶,都需要創(chuàng)建一個子類
AsyncTask是一個抽象類阐虚,無法通過new關(guān)鍵字直接創(chuàng)建一個實例對象,所以我們要想使用它蚌卤,就必須創(chuàng)建一個子類去繼承它实束,并指明它的三個泛型<Params,Progress,Result>。然后實例化子類對象調(diào)用execute()方法逊彭。
示例如下:
class MyAsycTask extends AsyncTask<String, Integer, Boolean> {
@Override
protected void onPreExecute() {
progressDialog.show();//顯示進(jìn)度框
}
@Override
protected Boolean doInBackground(String... integers) {
publishProgress(currentProgress()); //向UI線程發(fā)送當(dāng)前進(jìn)度
return doDownload(); //向ui線程反饋下載成功與否
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss(); //隱藏進(jìn)度框
//TODO:根據(jù)result判斷下載成功還是失敗
}
@Override
protected void onProgressUpdate(Integer... values) {
progressDialog.setMessage("當(dāng)前下載進(jìn)度:" + values[0] + "%");//在UI線程中展示當(dāng)前進(jìn)度
}
}
然后再在需要使用此異步任務(wù)的地方進(jìn)行開啟:
public void startTask() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { //根據(jù)不同的api采用并行模式進(jìn)行開啟
new MyAsycTask().executeOnExecutor(THREAD_POOL_EXECUTOR,"參數(shù)");
} else {
new MyAsycTask().execute("參數(shù)");
}
}
2咸灿、生命周期不受Activity的約束
關(guān)于AsyncTask的使用有一個很容易被人忽略的地方,那就是在Activity中開啟的AsyncTask并不會隨著Activity的銷毀而銷毀侮叮。即使Activity已經(jīng)銷毀甚至所屬的應(yīng)用進(jìn)程已經(jīng)銷毀避矢,AsyncTask還是會一直執(zhí)行doInBackground(Params... params)方法,直到方法執(zhí)行完成囊榜,然后根據(jù)cancel(boolean mayInterruptIfRunning)是否調(diào)用审胸,回調(diào)onCancelled(Result result)或onPostExecute(Result result)方法。
3卸勺、cancel(boolean mayInterruptIfRunning)方法調(diào)用不當(dāng)會失效砂沛。
如果的doInBackground()中有不可打斷的方法則cancel方法可能會失效,比如BitmapFactory.decodeStream()孔庭, IO等操作尺上,也就是即使調(diào)用了cancel方法材蛛,onPostExecute(Result result)方法仍然有可能被回調(diào)。
4怎抛、容易導(dǎo)致內(nèi)存泄漏卑吭,Activity不被回收
使用AsyncTask時,經(jīng)常出現(xiàn)的情況是马绝,在Activity中使用非靜態(tài)匿名內(nèi)部AsyncTask類豆赏,由于Java內(nèi)部類的特點,AsyncTask內(nèi)部類會持有外部類Activity的隱式引用富稻。由于AsyncTask的生命周期可能比Activity的長掷邦,當(dāng)Activity進(jìn)行銷毀AsyncTask還在執(zhí)行時,由于AsyncTask持有Activity的引用椭赋,導(dǎo)致Activity對象無法回收抚岗,進(jìn)而產(chǎn)生內(nèi)存泄露。
5哪怔、Activity被回收宣蔚,操作UI時出現(xiàn)空指針異常
如果在Activity中使用靜態(tài)AsynTask子類,依然無法高枕無憂认境,雖然靜態(tài)AsyncTask子類中不會持有Activity的隱式引用胚委。但是AsyncTask的生命周期依然可能比Activity的長,當(dāng)Activity進(jìn)行銷毀叉信,AsyncTask執(zhí)行回調(diào)onPostExecute(Result result)方法時亩冬,如果在onPostExecute(Result result)中進(jìn)行了UI操作,UI對象會隨著Activity的回收而被回收硼身,導(dǎo)致UI操作報空指針異常硅急。
6、onPostExecute()回調(diào)UI線程不起作用
如果在開啟AsyncTask后佳遂,Activity重新創(chuàng)建重新創(chuàng)建了例如屏幕旋轉(zhuǎn)等铜秆,還在運行的AsyncTask會持有一個Activity的非法引用即之前的Activity實例,會導(dǎo)致AsyncTask執(zhí)行后的數(shù)據(jù)丟失而且之前的Activity對象不被回收讶迁。
7、多個異步任務(wù)同時開啟時核蘸,存在串行并行問題巍糯。
當(dāng)多個異步任務(wù)調(diào)用方法execute()同時開啟時客扎,api的不同會導(dǎo)致串行或并行的執(zhí)行方式的不同。
在1.6(Donut)之前徙鱼,多個異步任務(wù)串行執(zhí)行
從1.6到2.3(Gingerbread)针姿,多個異步任務(wù)并行執(zhí)行
3.0(Honeycomb)到現(xiàn)在,如果調(diào)用方法execute()開啟是串行厌衙,調(diào)用executeOnExecutor(Executor)是并行。
AnsycTask雖然是個輕量級的異步操作類婶希,方便了子線程與UI線程的數(shù)據(jù)傳遞,但是使用不當(dāng)又會產(chǎn)生很多問題喻杈。所以有必要對其進(jìn)行進(jìn)一步的封裝彤枢,以使它在方便我們調(diào)用的同時還能消除弊端。
下面采用Builder模式將AsyncTask封裝成外觀上類似Rxjava的鏈?zhǔn)秸{(diào)用的形式缴啡,
第一步瓷们、將AsyncTask經(jīng)常重寫的四個方法,用四個函數(shù)接口分別定義一下
public interface IPreExecute {
void onPreExecute();
}
public interface IDoInBackground<Params,Progress,Result> {
Result doInBackground(IPublishProgress<Progress> publishProgress,Params... params);
}
public interface IProgressUpdate<Progress>{
void onProgressUpdate(Progress... values);
}
public interface IPostExecute<Result> {
void onPostExecute(Result result);
}
第二步式镐、其中IDoInBackground接口中固蚤,doInBackground方法與重寫AnyncTask的方法并不一樣,多了一個接口IPublishProgress參數(shù)你弦,IPublishProgress接口如下:
public interface IPublishProgress<Progress>{
void showProgress(Progress... values);
}
此接口需要自定義的AnyncTask去實現(xiàn)燎孟,以方便在doInBackground(IPublishProgress<Progress> publishProgress,Params... params);方法中,通過publishProgress去更新實時進(jìn)度旷偿。
第三步爆侣、另外增加一個函數(shù)接口,在方法doInBackground執(zhí)行結(jié)束開始回調(diào)方法onPostExecute之前茫负,用來判斷所屬Activity是否依然處于活躍狀態(tài)乎赴,如果處于活躍狀態(tài)則回調(diào)方法onPostExecute潮尝,如果處于非活躍狀態(tài)則不回調(diào)勉失,避免回調(diào)后操作UI產(chǎn)生空指針異常嗤堰。
public interface IIsViewActive {
boolean isViewActive();
}
第四步、新建一個AsyncTask的子類MyAsyncTask持有第一步中所定義的四個函數(shù)接口的引用告匠,并重寫AsyncTask中經(jīng)常重寫的四個方法
如下:
public class MyAsyncTask <Params, Progress, Result> extends AsyncTask<Params, Progress, Result> implements IPublishProgress<Progress> {
private IPreExecute mPreExecute;
private IProgressUpdate<Progress> mProgressUpdate;
private IDoInBackground<Params, Progress, Result> mDoInBackground;
private IIsViewActive mViewActive;
private IPostExecute<Result> mPostExecute;
@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);
}
@Override
public void showProgress(Progress... values) {
this.publishProgress(values);
}
@SafeVarargs
public final AsyncTask<Params, Progress, Result> start(Params... params) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return super.executeOnExecutor(THREAD_POOL_EXECUTOR, params);
} else {
return super.execute(params);
}
}
}
在每一個方法中后专,判斷對應(yīng)函數(shù)接口的引用是否為空输莺,不為空則進(jìn)行相應(yīng)的回調(diào),
同時實現(xiàn)IPublishProgress接口型凳,在實現(xiàn)方法中showProgress中嘱函,調(diào)用AsyncTask的publishProgress(values)方法(publishProgress(values)是個final方法,不能重寫)疏唾。
同時持有IIsViewActive接口的引用函似,在onPostExecute方法中通過它來判斷所屬Activity是否處于活躍狀態(tài),如果處于活躍狀態(tài)顿天,則回調(diào)IPostExecute接口蔑担,否則不回調(diào)。
添加start方法,此處采用并行模式進(jìn)行開啟局扶,需要串行模式的可以修改此處。
第五步畜埋,現(xiàn)在自定義異步任務(wù)類中有很多接口的引用,其實例化對象構(gòu)建起來比較復(fù)雜对室,所以我們在其內(nèi)部添加一個Builder,來簡化它的構(gòu)建咖祭,同時私有化其構(gòu)造方法么翰,避免外部直接實例化其對象。
最終代碼如下:
public class MyAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> implements IPublishProgress<Progress> {
private IPreExecute mPreExecute;
private IProgressUpdate<Progress> mProgressUpdate;
private IDoInBackground<Params, Progress, Result> mDoInBackground;
private IIsViewActive mViewActive;
private IPostExecute<Result> 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<Params, Progress, Result> 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 <Params, Progress, Result> Builder<Params, Progress, Result> newBuilder() {
return new Builder<>();
}
public static class Builder<Params, Progress, Result> {
private final MyAsyncTask<Params, Progress, Result> mAsyncTask;
public Builder() {
mAsyncTask = new MyAsyncTask<>();
}
public Builder<Params, Progress, Result> setPreExecute(IPreExecute preExecute) {
mAsyncTask.mPreExecute = preExecute;
return this;
}
public Builder<Params, Progress, Result> setProgressUpdate(IProgressUpdate<Progress> progressUpdate) {
mAsyncTask.mProgressUpdate = progressUpdate;
return this;
}
public Builder<Params, Progress, Result> setDoInBackground(IDoInBackground<Params, Progress, Result> doInBackground) {
mAsyncTask.mDoInBackground = doInBackground;
return this;
}
public Builder<Params, Progress, Result> setViewActive(IIsViewActive viewActive) {
mAsyncTask.mViewActive = viewActive;
return this;
}
public Builder<Params, Progress, Result> setPostExecute(IPostExecute<Result> postExecute) {
mAsyncTask.mPostExecute = postExecute;
return this;
}
@SafeVarargs
public final AsyncTask<Params, Progress, Result> start(Params... params) {
return mAsyncTask.start(params);
}
}
}
封裝后的異步任務(wù)類看起來復(fù)雜码耐,但用起來簡單,而且是個一勞永逸的過程敦间。下面用一個事例演示一下在Activity中的使用桦沉,在方法loadData()中使用的是全功能調(diào)用方式,在saveData()中使用的是最簡短調(diào)用方式剿骨。
由于MyAsyncTask中所持有的接口引用在使用時均添加了非空判斷埠褪,所以在通過Builder構(gòu)建MyAsynTask時,并不是每一個接口參數(shù)都需要傳贷掖,可按照實際應(yīng)用場景只傳需要的便可渴语。
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();
}
/**
* 全功能調(diào)用方式
*/
private void loadData() {
MyAsyncTask.<String, Integer, Boolean>newBuilder()
.setPreExecute(new IPreExecute() {
@Override
public void onPreExecute() {
mainTextView.setText("開始下載數(shù)據(jù)……");
}
})
.setDoInBackground(new IDoInBackground<String, Integer, Boolean>() {
@Override
public Boolean doInBackground(IPublishProgress<Integer> 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<Integer>() {
@Override
public void onProgressUpdate(Integer... values) {
mainTextView.setText("正在下載數(shù)據(jù)驾凶,當(dāng)前進(jìn)度為:" + (values[0] * 100 / 10) + "%");
}
})
.setViewActive(new IIsViewActive() {
@Override
public boolean isViewActive() {
return MainActivity.this.isViewActive();
}
})
.setPostExecute(new IPostExecute<Boolean>() {
@Override
public void onPostExecute(Boolean aBoolean) {
if (aBoolean) {
mainTextView.setText("下載成功");
} else {
mainTextView.setText("下載失敗");
}
}
})
.start("參數(shù)");
}
/**
* @return 判斷當(dāng)前Activity是否處于活躍狀態(tài)
*/
public boolean isViewActive() {
return !(isFinishing() || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && isDestroyed()));
}
/**
* 最簡短的調(diào)用方式
*/
private void saveData() {
MyAsyncTask.<Void, Void, Void>newBuilder()
.setDoInBackground(new IDoInBackground<Void, Void, Void>() {
@Override
public Void doInBackground(IPublishProgress<Void> publishProgress, Void... voids) {
//TODO:執(zhí)行數(shù)據(jù)保存
return null;
}
})
.start();
}
}