zoukankan      html  css  js  c++  java
  • Android开发——异步任务中Activity销毁时的问题

    0.  前言

    在Android开发中经常会发生Activity的销毁重建,比如用户长时间接听一个电话后回到APP。在Android开发——Fragment知识整理(二)我们提到了使用Fragment大量保存Activity销毁重建数据的方法,但是有一个问题是,在异步任务时旋转屏幕,如何处理异步任务呢?如果单纯的在Activity销毁之前关闭上一个异步任务,onPostExecute()中的关闭对话框就不会走了,会出现对话框无法关闭的现象;如果不关闭,可能会更新已经不存在的控件,造成错误,不仅如此最主要的是Activity的销毁会造成对话框dismiss空指针异常,因为与当前对话框绑定的FragmentManager已经是null

    因此我们的目标是在异步加载数据时旋转屏幕,不会对加载任务进行中断重启,并且对话框正常显示


    1.  继承Fragment并在其中声明引用

    public class KeepDataFragment extends Fragment {
        // 保存一个异步的任务
        private MyAsyncTask data;
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);
        }
        public void setData(MyAsyncTask data) {
            this.data = data;
        }
        public MyAsyncTask getData() {
            return data;
        }
    }
    

    这里我们创建KeepDataFragment并继承Fragment,并在其中声明需要保存的数据对象,这里是保存了一个异步的任务,然后提供gettersetter。最后一定要在onCreate调用setRetainInstance(true)


    2.  异步任务和进度条

    public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        private MainActivity activity;
        private boolean isCompleted;
        private LoadingDialog mLoadingDialog;
        private List<String> items;
    
        public MyAsyncTask(MainActivity activity) {
            this.activity = activity;
        }
    
        @Override
        protected void onPreExecute() {
            mLoadingDialog = new LoadingDialog();
            mLoadingDialog.show(activity.getFragmentManager(), "LOADING");
        }
    
        @Override
        protected Void doInBackground(Void... params) {
            items = loadingData();
            return null;
        }
        private List<String> loadingData() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {}
            return new ArrayList<String>(Arrays.asList("东南大学", "信息科学与工程学院", "信息安全学科"));
        }
    
        @Override
        protected void onPostExecute(Void unused) {
            isCompleted = true;
            notifyActivityTaskCompleted();
            if (mLoadingDialog != null)
                mLoadingDialog.dismiss();
        }
    
        public List<String> getItems() {
            return items;
        }
        
        public void setActivity(MainActivity activity) {
            // 如果上一个Activity销毁,将与上一个Activity绑定的DialogFragment销毁
            if (activity == null) {
                mLoadingDialog.dismiss();
            }
            // 设置为当前的Activity
            this.activity = activity;
            // 开启一个与当前Activity绑定的等待框
            if (activity != null && !isCompleted) {
                mLoadingDialog = new LoadingDialog();
                mLoadingDialog.show(activity.getFragmentManager(), "LOADING");
            }
            // 如果完成,通知Activity
            if (isCompleted) {
                notifyActivityTaskCompleted();
            }
        }
    
        private void notifyActivityTaskCompleted() {
            if (null != activity) {
                activity.onTaskCompleted();
            }
        }
    }
    

    这里使用AsyncTask进行异步任务,不熟悉AsyncTask的可以参考Android开发——AsyncTask的使用以及源码解析任务开始时显示了一个FragmentDialog对话框,如果不熟悉可以参考Android开发——官方推荐使用DialogFragment替换AlertDialog,这里就不赘述了。任务下载中时,我们模拟了5秒耗时任务并返回了一个字符串List。下载任务结束时让进度框消失,并为Activity提供回调,因为这里持有了Activity的引用。这里我们也提供了setActivity方法,在Activity被销毁时在onSaveInstanceState()中设置setActivity(null)取消之前的对话框,同时也防止了内存泄漏;当Activity重建时在onCreate()中设置setActivity(this)传入新的Activity,从而再次显示一个加载框,这里需要注意的是Activity的销毁重建并不影响加载的数据,所有后台的数据一直继续在加载


    3.  MainActivity中的实现

    public class MainActivity extends ListActivity {
        private ListAdapter mAdapter;
        private List<String> mDatas;
        private KeepDataFragment dataFragment;
        private MyAsyncTask mMyTask;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            FragmentManager fm = getFragmentManager();
            dataFragment = (KeepDataFragment) fm.findFragmentByTag("data");
            if (dataFragment == null) {
                dataFragment = new KeepDataFragment();
                fm.beginTransaction().add(dataFragment, "data").commit();
            }
            mMyTask = dataFragment.getData();
            if (mMyTask != null) {
                //使AsyncTask持有Activity的引用
                mMyTask.setActivity(this);
            } else {
                mMyTask = new MyAsyncTask(this);
                dataFragment.setData(mMyTask);
                mMyTask.execute();
            }
        }
    
        @Override
        protected void onRestoreInstanceState(Bundle state) {
            super.onRestoreInstanceState(state);
        }
    
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            mMyTask.setActivity(null);
            super.onSaveInstanceState(outState);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
        }
    
        public void onTaskCompleted() {
            mDatas = mMyTask.getItems();
            mAdapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, mDatas);
            setListAdapter(mAdapter);
        }
    }
    

    onCreate中,如果是第一次进入,则Activity引用传给AsyncTask、开启任务mMyTask并把它交给KeepDataFragment来维护,正常情况下AsyncTask正常进行,完成后回调Activity中的onTaskCompleted()。注意要考虑不正常的情况,即加载过程中屏幕的旋转Activity销毁时设置setActivity(null)取消之前的对话框,并在Activity重建时KeepDataFragment 实例因为未被销毁直接通过dataFragment.getData() 取出加载任务mTask并设置setActivity(this)从而再次显示一个新的加载框,直到任务完成正常进行Activity的回调显示数据方法。


    看一下如下效果,加载数据的5秒钟内无论如何旋转屏幕都不会出现问题,这样就完成了进行异步任务时Activity的销毁重建不会发生中断并开启新的下载任务,而且对话框也会正常显示。




    源码下载地址点这里

  • 相关阅读:
    转载:【Oracle 集群】RAC知识图文详细教程(三)--RAC工作原理和相关组件
    转载:【Oracle 集群】RAC知识图文详细教程(一)--集群概念介绍
    转载:【Oracle 集群】RAC知识图文详细教程(二)--Oracle 集群概念及原理
    题目总结
    面试题(包含答案)
    ElementUI动态表格数据转换formatter
    父组件搜索列表 给 子组件传值问题
    项目提取公共接口方法
    数组常用方法总结
    vue全局注册
  • 原文地址:https://www.cnblogs.com/qitian1/p/6461454.html
Copyright © 2011-2022 走看看