Android AsyncTask 優(yōu)化封裝——方便調(diào)用并解決其使用弊端

————解決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();
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窟哺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子浮声,更是在濱河造成了極大的恐慌旋奢,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羡洁,死亡現(xiàn)場離奇詭異爽丹,居然都是意外死亡粤蝎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門秸应,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碑宴,“玉大人,你說我怎么就攤上這事祸挪≌昙洌” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵整以,是天一觀的道長峻仇。 經(jīng)常有香客問我,道長帆调,這世上最難降的妖魔是什么番刊? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任影锈,我火速辦了婚禮,結(jié)果婚禮上枣抱,老公的妹妹穿的比我還像新娘辆床。我一直安慰自己,他們只是感情好轿秧,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布菇篡。 她就那樣靜靜地躺著一喘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪议蟆。 梳的紋絲不亂的頭發(fā)上萎战,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音偷仿,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛孝鹊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播苔咪,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼团赏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了丝里?” 一聲冷哼從身側(cè)響起体谒,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤抒痒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后故响,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體被去,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年糜值,在試婚紗的時候發(fā)現(xiàn)自己被綠了坯墨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捣染。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖榕栏,靈堂內(nèi)的尸體忽然破棺而出蕾各,到底是詐尸還是另有隱情,我是刑警寧澤妨托,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站内颗,受9級特大地震影響敦腔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜负懦,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望系吭。 院中可真熱鬧,春花似錦沃缘、人聲如沸则吟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽敬扛。三九已至,卻和暖如春谍珊,著一層夾襖步出監(jiān)牢的瞬間急侥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工贝润, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留陕悬,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓唯绍,卻偏偏與公主長得像枝誊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子叶撒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容