Weli收藏的文章Androidandroid收集Android开发

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

2018-03-15  本文已影响506人  afa1332

————解决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();
    }
}
上一篇下一篇

猜你喜欢

热点阅读