Fragment 在屏幕切换上的应用

CSDN记录我的Android之旅——
还记得之前在做胎压监测时犯的一个错误,当时甚至不知道屏幕旋转会造成当前Activity销毁,结果当一进来应用,默认打开一个线程,屏幕翻转时出现AFC,基础有限,困扰了我很长时间。
其实,当用户没有指定屏幕方向和configChanges时,屏幕偏转会导致当前Activity销毁,并重建,恢复大量数据,如果处理不好则很容易出现异常。比如我的情况,屏幕翻转,onCreate重新启动时,再次启动线程,而上一个线程还没有停止,并且在线程里去更新控制一些已经没有的控件,造成错误。
其实我们想去解决屏幕旋转带来的一系列问题方法还是很多的。比如,最简单的就是重写onConfigurationChanged方法,当屏幕发生旋转时,Activity不会重建而是会回调此方法,然后用户自行处理屏幕旋转后的操作。
简单的贴个例子说明问题。
Demo1:
这是一个模拟开机异步加载数据的例子,
LoadDataAsyncTask用于异步加载数据,2秒加载数据,加载完数据显示ListView。DialogFragment暂且理解成一个进度框,具体后面会提到。

public class ConfigChangesTestActivity extends ListActivity
{
    private static final String TAG = "MainActivity";
    private ListAdapter mAdapter;
    private ArrayList<String> mDatas;
    private DialogFragment mLoadingDialog;
    private LoadDataAsyncTask mLoadDataAsyncTask;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        Log.e(TAG, "onCreate");
        initData(savedInstanceState);
    }

    /**
     * 初始化数据
     */
    private void initData(Bundle savedInstanceState)
    {

        mLoadingDialog = new LoadingDialog();
        mLoadingDialog.show(getFragmentManager(), "LoadingDialog");
        mLoadDataAsyncTask = new LoadDataAsyncTask();
        mLoadDataAsyncTask.execute();

    }

    /**
     * 初始化适配器
     */
    private void initAdapter()
    {
        mAdapter = new ArrayAdapter<String>(ConfigChangesTestActivity.this,
                android.R.layout.simple_list_item_1, mDatas);
        setListAdapter(mAdapter);
    }

    /**
     * 模拟耗时操作
     * 
     * @return
     */
    private ArrayList<String> generateTimeConsumingDatas()
    {
        try
        {
            Thread.sleep(2000);
        } catch (InterruptedException e)
        {
        }
        return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据",
                "onSaveInstanceState保存数据",
                "getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop",
                "Spark"));
    }

    /**
     * 当配置发生变化时,不会重新启动Activity。但是会回调此方法,用户自行进行对屏幕旋转后进行处理
     */
    @Override
    public void onConfigurationChanged(Configuration newConfig)
    {
        super.onConfigurationChanged(newConfig);

        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
        {
            Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
        {
            Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
        }

    }

    private class LoadDataAsyncTask extends AsyncTask<Void, Void, Void>
    {
        @Override
        protected Void doInBackground(Void... params)
        {
            mDatas = generateTimeConsumingDatas();
            return null;
        }

        @Override
        protected void onPostExecute(Void result)
        {
            mLoadingDialog.dismiss();
            initAdapter();
        }
    }

    @Override
    protected void onDestroy()
    {
        Log.e(TAG, "onDestroy");
        super.onDestroy();
    }

}

言归正传,前面写了这么多,似乎和今天的主题没有什么关系,到底Fragment在屏幕切换上有什么用呢?比如我们正在一个输入框输入用户名或者密码,这是突然屏幕旋转,选择后发现之前输入的内容都没有了,又得重新输入。。。这就是一个很差的用户体验,再比如打开应用加载数据时,你不希望屏幕一旋转,造成加载线程重新开始,甚至崩溃,,这里就必须解决数据恢复这个问题。想要恢复Activity的数据,避免重复加载,android提供了一些方法:
(1)使用onSaveInstanceState()和onRestoreInstanceState()进行数据恢复。具体什么时候会回调这两个方法,参考链接:[http://www.cnblogs.com/hanyonglu/archive/2012/03/28/2420515.html]
下面给出一个例子:(不考虑在加载过程中选转屏幕,只是保存数据,数据不会重新加载)
Demo2:

public class SavedInstanceStateUsingActivity extends ListActivity
{
    private static final String TAG = "MainActivity";
    private ListAdapter mAdapter;
    private ArrayList<String> mDatas;
    private DialogFragment mLoadingDialog;
    private LoadDataAsyncTask mLoadDataAsyncTask;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        Log.e(TAG, "onCreate");
        initData(savedInstanceState);
    }

    /**
     * 初始化数据
     */
    private void initData(Bundle savedInstanceState)
    {
        if (savedInstanceState != null)
            mDatas = savedInstanceState.getStringArrayList("mDatas");

        if (mDatas == null)
        {
            mLoadingDialog = new LoadingDialog();
            mLoadingDialog.show(getFragmentManager(), "LoadingDialog");
            mLoadDataAsyncTask = new LoadDataAsyncTask();
            mLoadDataAsyncTask.execute();

        } else
        {
            initAdapter();
        }

    }

    /**
     * 初始化适配器
     */
    private void initAdapter()
    {
        mAdapter = new ArrayAdapter<String>(
                SavedInstanceStateUsingActivity.this,
                android.R.layout.simple_list_item_1, mDatas);
        setListAdapter(mAdapter);
    }

    @Override
    protected void onRestoreInstanceState(Bundle state)
    {
        super.onRestoreInstanceState(state);
        Log.e(TAG, "onRestoreInstanceState");
    }

    @Override
    protected void onSaveInstanceState(Bundle outState)
    {
        super.onSaveInstanceState(outState);
        Log.e(TAG, "onSaveInstanceState");
        outState.putSerializable("mDatas", mDatas);

    }

    /**
     * 模拟耗时操作
     * 
     * @return
     */
    private ArrayList<String> generateTimeConsumingDatas()
    {
        try
        {
            Thread.sleep(2000);
        } catch (InterruptedException e)
        {
        }
        return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据",
                "onSaveInstanceState保存数据",
                "getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop",
                "Spark"));
    }

    private class LoadDataAsyncTask extends AsyncTask<Void, Void, Void>
    {
        @Override
        protected Void doInBackground(Void... params)
        {
            mDatas = generateTimeConsumingDatas();
            return null;
        }

        @Override
        protected void onPostExecute(Void result)
        {
            mLoadingDialog.dismiss();
            initAdapter();
        }
    }

    @Override
    protected void onDestroy()
    {
        Log.e(TAG, "onDestroy");
        super.onDestroy();
    }

}

使用系统提供的onSaveIntanceState()的回调中,使用Bundle来完全恢复你Activity的状态是可能是不现实的(Bundle不是设计用来携带大量数据的(例如bitmap),并且Bundle中的数据必须能够被序列化和反序列化),这样会消耗大量的内存和导致配置变化缓慢。在这种情况下,我们用一个空视图的Fragment来保存需要保存状态的引用。
(2)直接上代码
效果是运行开始,出现一个进度框,,5秒后,显示ListView。不管旋转屏幕多少次,也不会i影响进度框的加载时间。
MyAsyncTask,延时5秒,显示ListView,用于模拟异步复杂操作,
Demo3:
OtherRetainedFragment.java //用于存取MyAsyncTask对象

public class OtherRetainedFragment extends Fragment
{

    // data object we want to retain
    // 保存一个异步的任务
    private MyAsyncTask data;

    // this method is only called once for this fragment
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        // retain this fragment
        setRetainInstance(true);
    }

    public void setData(MyAsyncTask data)
    {
        this.data = data;
    }

    public MyAsyncTask getData()
    {
        return data;
    }


}

MyAsyncTask.java //异步任务类

public class MyAsyncTask extends AsyncTask<Void, Void, Void>
{
    private FixProblemsActivity activity;
    /**
     * 是否完成
     */
    private boolean isCompleted;
    /**
     * 进度框
     */
    private LoadingDialog mLoadingDialog;
    private List<String> items;

    public MyAsyncTask(FixProblemsActivity 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;
    }

    /**
     * 加载完成回调当前的Activity
     */
    @Override
    protected void onPostExecute(Void unused)
    {
        isCompleted = true;
        notifyActivityTaskCompleted();
        if (mLoadingDialog != null)
            mLoadingDialog.dismiss();
    }

    public List<String> getItems()
    {
        return items;
    }

    private List<String> loadingData()
    {
        try
        {
            Thread.sleep(5000);
        } catch (InterruptedException e)
        {
        }
        return new ArrayList<String>(Arrays.asList("通过Fragment保存大量数据",
                "onSaveInstanceState保存数据",
                "getLastNonConfigurationInstance已经被弃用", "RabbitMQ", "Hadoop",
                "Spark"));
    }

    /**
     * 设置Activity,因为Activity会一直变化
     * 
     * @param activity
     */
    public void setActivity(FixProblemsActivity 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();
        }
    }

}

FixProblemsActivity.java //主activity。

public class FixProblemsActivity extends ListActivity
{
    private static final String TAG = "MainActivity";
    private ListAdapter mAdapter;
    private List<String> mDatas;
    private OtherRetainedFragment dataFragment;
    private MyAsyncTask mMyTask;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        Log.e(TAG, "onCreate");

        // find the retained fragment on activity restarts
        FragmentManager fm = getFragmentManager();
        dataFragment = (OtherRetainedFragment) fm.findFragmentByTag("data");

        // create the fragment and data the first time
        if (dataFragment == null)
        {
            // add the fragment
            dataFragment = new OtherRetainedFragment();
            fm.beginTransaction().add(dataFragment, "data").commit();
        }
        mMyTask = dataFragment.getData();
        if (mMyTask != null)
        {
            mMyTask.setActivity(this);
        } else
        {
            mMyTask = new MyAsyncTask(this);
            dataFragment.setData(mMyTask);
            mMyTask.execute();
        }
        // the data is available in dataFragment.getData()
    }


    @Override
    protected void onRestoreInstanceState(Bundle state)
    {
        super.onRestoreInstanceState(state);
        Log.e(TAG, "onRestoreInstanceState");
    }


    @Override
    protected void onSaveInstanceState(Bundle outState)
    {
        mMyTask.setActivity(null);
        super.onSaveInstanceState(outState);
        Log.e(TAG, "onSaveInstanceState");
    }

    @Override
    protected void onDestroy()
    {
        Log.e(TAG, "onDestroy");
        super.onDestroy();

    }
    /**
     * 回调
     */
    public void onTaskCompleted()
    {
        mDatas = mMyTask.getItems();
        mAdapter = new ArrayAdapter<String>(FixProblemsActivity.this,
                android.R.layout.simple_list_item_1, mDatas);
        setListAdapter(mAdapter);
    }

}

关键就是在Fragment中,setRetainInstance(true);这个方法会在配置发生变化时,保存当前fragment,跳过oncreate()和ondestroy()方法,所以这时候切忌不要在onCreate()中写初始化配置。
既然已经保存了Fragment,重新旋转屏幕,进入onCreate()方法,然后取出之前Fragment持有的对象,进行处理即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值