HandlingRuntime Changes
官方:http://developer.android.com/guide/topics/resources/runtime-changes.html
相关博客:
简介:
程序运行中设备配置(如屏幕方向、软键盘状态、语言)可能会发生一些改变,此时,系统将会自动执行onDestory方法,然后执行onCreate方法,以重载资源,从而使程序去适应新的配置
程序重启中重要的是保存好之前的状态用于恢复。我们可以在Activity的生命周期中,调用onSaveInstanceState方法在Activity被销毁前保存这些状态,然后再onCreate、onRestoreInstanceState方法中去恢复这些状态。
如何测试程序在配置变化中是否能正常工作:我们可以在程序执行各种任务时,去触发一个配置改变,如改变屏幕方向。程序应该能在任何时候恢复并且不丢失用户数据及其他状态。程序应该能在配置变化或接听电话后尽快恢复。查看Activity生命周期可以帮助你了解如何去恢复Activity的状态。
但是,可能在重启应用或恢复数据时,会有巨大的花销,造成糟糕的用户体验,在这些情况下,我们有两个方法去处理:
1、 在配置发生变化时保留一个可以完全恢复之前状态的对象
2、 自己处理这些配置变化事件(不让系统去重启Activity)
当确定的配置变化时,阻止系统重启Activity,我们可以创建一个回调,在需要的时候主动地去更新Activity。
ü 在配置发生变化时保留一个可以完全恢复之前状态的对象
如果重启一个Activity需要大量的数据、重建一个网络连接或则其他耗时操作,这样可能会是一个糟糕的体验。同时,这也许不够现实去使用onSaveInstanceState(Bundle)恢复,因为Bundle被设计用来恢复小的数据量,保存在Bundle的数据需要被序列化然后反序列化,这将消耗许多内存,并且减缓配置变化。在这种情况下,我们可以通过保留一个Fragment(Activity销毁的时候,Fragment仍存在)来减轻负担,这个Fragment不包含UI界面,但是包含一些我们希望恢复的状态对象的引用。(在Fragment示例中有相关介绍)
设计这个RetainFragment的步骤如下:
Ø 继承Fragmentl类,并声明这些状态对象的引用;
Ø 在RetainFragment类的onCreate方法中调用setRetainInstance(boolean)
Ø 将RetainFragment对象添加至Activity
Ø 在activity重启时通过FragmentManager找到RetainFragment对象
如下所示:
public class RetainedFragment extends Fragment {
// 生命你想保留的数据对象
private MyDataObject data;
// 这个方法只在Fragment被创建时执行一次
@Override
public voidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
//设置这个Fragment是用来保留对象的
setRetainInstance(true);
}
//提供方法保存对象
public voidsetData(MyDataObject data) {
this.data =data;
}
//提供获取对象的方法
publicMyDataObject getData() {
returndata;
}
}
需要注意的:虽然我们可以通过fragment保留任何对象,但是注意,我们不应该保存那些与Activity或Context相关的对象,比如Drawable、view、Adapter,因为这会是垃圾回收器无法回收它们,从而引起内存泄漏。
按如下方法在Activity中去添加一个RetainFragment
public class MyActivity extends Activity {
privateRetainedFragment dataFragment;
@Override
public voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager fm = getFragmentManager();
//通过Tag来获取这个用于保存数据的Fragment对象
dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
//当这个对象不存在时则创建它,并保存当前状态
if(dataFragment == null) {
// addthe fragment
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, “data”).commit();
// loadthe data from the web
dataFragment.setData(loadMyData());
}
//如果这个对象存在则通过dataFragment.getData()方法获取保留的数据
if(dataFragment != null) {
MyDataObject mydataobject = dataFragment.getData();
...
}
}
@Override
public void onDestroy(){
super.onDestroy();
//更新oncreate中保存的状态
dataFragment.setData(collectMyLoadedData());
}
}
ü 自己处理配置变化事件
当某些确定的配置变化发生时,如果我们不需要去更新资源或则由于性能限制我们必须去避免Activity的重启。此时,我们可以声明Activity自己去处理,而阻止系统去重启Activity。
需要提醒的是:当我们阻止系统去重启Activity时,去更新一些其他资源将变得比较麻烦,因此这部被推荐到大多数应用中,而且不到万不得已的时候,不要采用这种方式。
我们需要在AndroidManifest中的activity标签内添加android:configChanges属性来声明不需要系统处理的配置变化事件。如:当在屏幕方向发生变化、软键盘切换、屏幕大小发生变化时,我们希望自己去处理,则我们添加以下属性:
android:configChanges="orientation|keyboardHidden|screenSize"
android:configChanges属性可能的值,可以查看官方文档:
http://developer.android.com/guide/topics/manifest/activity-element.html#config
我们通过在activity中实现onConfigurationChanged方法处理这些配置变化
@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // Checks the orientation of the screen 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(); } }
onConfigurationChanged中的参数newConfig不仅仅包含变化的这些配置,也包含其他所有的config配置。我们可以去判断这些状态,然后给出合适的处理。
我们不必确切的知道具体哪些配置发生了变化,而只需要去处理那些我们感兴趣的配置,然后采用适当的方法处理。比如一个图片资源发生了变化,那么我们可以调用imageview的setImageResource()方法去提供合适的图片资源。
当然,如果我们并不关心这些配置变化,我们也可以不去实现onConfigurationChanged方法,这样我们仍旧在之前的状态下工作。
但是我们必须保证程序能在关闭或重启后恢复到以前的状态,因为虽然你能在正常的生命周期内逃脱重启,但是你不能保证当一些你未声明的配置变化时或则程序在用户离开并在用户返回之前已经被销毁时仍能稳定运行。
查看更多Configuration的信息,可以查看官方文档:
http://developer.android.com/reference/android/content/res/Configuration.html