Android AsyncTask 优化封装——方便调用并解决其使用弊端

————解决AsyncTask使用弊端,并采用Builder模式封装成链式调用的形式

如需下载源码,请访问
https://github.com/fengchuanfang/AsyncTaskApplication

文章原创,转载请注明出处:
Android AsyncTask 优化封装

AsyncTask是Android系统封装的一个轻量级的用来执行异步任务的抽象类,用它可以很容易的开启一个线程执行后台耗时任务,并可以把执行的实时进度和最终结果传递给主线程,以方便在主线程中更新UI。

AsyncTask虽说是轻量级的但用起来也不是很方便,而且还有很多弊端

1、每次使用时,都需要创建一个子类

AsyncTask是一个抽象类,无法通过new关键字直接创建一个实例对象,所以我们要想使用它,就必须创建一个子类去继承它,并指明它的三个泛型<Params,Progress,Result>。然后实例化子类对象调用execute()方法。
示例如下:

    class MyAsycTask extends AsyncTask<String, Integer, Boolean> {
        @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<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参数,IPublishProgress接口如下:

public interface IPublishProgress<Progress>{
    void showProgress(Progress... values);
}

此接口需要自定义的AnyncTask去实现,以方便在doInBackground(IPublishProgress<Progress> publishProgress,Params... params);方法中,通过publishProgress去更新实时进度。

第三步、另外增加一个函数接口,在方法doInBackground执行结束开始回调方法onPostExecute之前,用来判断所属Activity是否依然处于活跃状态,如果处于活跃状态则回调方法onPostExecute,如果处于非活跃状态则不回调,避免回调后操作UI产生空指针异常。

public interface IIsViewActive {
    boolean isViewActive();
}

第四步、新建一个AsyncTask的子类MyAsyncTask持有第一步中所定义的四个函数接口的引用,并重写AsyncTask中经常重写的四个方法
如下:

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);
        }
    }

}

在每一个方法中,判断对应函数接口的引用是否为空,不为空则进行相应的回调,

同时实现IPublishProgress接口,在实现方法中showProgress中,调用AsyncTask的publishProgress(values)方法(publishProgress(values)是个final方法,不能重写)。

同时持有IIsViewActive接口的引用,在onPostExecute方法中通过它来判断所属Activity是否处于活跃状态,如果处于活跃状态,则回调IPostExecute接口,否则不回调。

添加start方法,此处采用并行模式进行开启,需要串行模式的可以修改此处。

第五步,现在自定义异步任务类中有很多接口的引用,其实例化对象构建起来比较复杂,所以我们在其内部添加一个Builder,来简化它的构建,同时私有化其构造方法,避免外部直接实例化其对象。
最终代码如下:

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);
        }
    }
}

封装后的异步任务类看起来复杂,但用起来简单,而且是个一劳永逸的过程。下面用一个事例演示一下在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.<String, Integer, Boolean>newBuilder()
                .setPreExecute(new IPreExecute() {
                    @Override
                    public void onPreExecute() {
                        mainTextView.setText("开始下载数据……");
                    }
                })
                .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("正在下载数据,当前进度为:" + (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("参数");
    }

    /**
     * @return 判断当前Activity是否处于活跃状态
     */
    public boolean isViewActive() {
        return !(isFinishing() || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && isDestroyed()));
    }

    /**
     * 最简短的调用方式
     */
    private void saveData() {
        MyAsyncTask.<Void, Void, Void>newBuilder()
                .setDoInBackground(new IDoInBackground<Void, Void, Void>() {
                    @Override
                    public Void doInBackground(IPublishProgress<Void> publishProgress, Void... voids) {
                        //TODO:执行数据保存
                        return null;
                    }
                })
                .start();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容