Fragment 在屏幕切换上的应用
来源:互联网 发布:设置网络共享打印机 编辑:程序博客网 时间:2024/05/20 13:04
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持有的对象,进行处理即可。
- Fragment 在屏幕切换上的应用
- Fragment在Android机顶盒上的应用
- 安卓笔记android fragment在viewpager中的使用和屏幕切换的状态保存等
- fragment在横竖屏切换(屏幕旋转)时候崩溃的问题
- 在fragment上的超级复杂之fragmen的平滑切换
- 切换屏幕时恢复应用的状态
- eclipse的Ctrl+Alt+方向键在英特尔显示器上会切换屏幕方向
- eclipse的Ctrl+Alt+方向键在英特尔显示器上会切换屏幕方向
- Android:两个Fragment在同一个Framelayout上切换显示
- 在activity上添加Fragment及底部按钮切换
- Android应用开发-- 如何在页面切换的过程中屏蔽屏幕事件?
- Fragment互相切换,点击Fragment上的按钮跳转到Fragment简单总结
- FragmentTabHost屏幕切换时Fragment显示空白
- 在屏幕上输出你的名字。。。。(Scanner)的初步应用。
- Android开发-fragment切换、hide、show、fragmentmanager的基础应用
- Fragment的应用,实现横竖切换并兼容平板
- 有关BlackBerry 5.0上的屏幕切换动画.
- 在选择屏幕的标准应用工具条上增加自定义按钮(-)
- POJ 2049 Finding Nemo ( BFS)
- LVS+Keepalived实现MySQL从库读操作负载均衡
- POJ 2955:Brackets
- 二阶切比雪夫多项式实现(scala版、python版)
- 转:HTTP长连接和短连接原理浅析
- Fragment 在屏幕切换上的应用
- 八皇后问题
- ZOJ 2100 Seeding(dfs)
- div width 100% 左右滑动条问题
- 【西祠日志】【15】周二
- android音乐播放器的音频焦点控制
- android Handler & Looper 源码解读
- JAVA网络编程之——URL类
- 秒杀多线程第十篇 生产者消费者问题