默认情况下,横竖屏切换时 Android 会销毁当前 Activity
然后重启它。这时某些运行时状态可能会丢失,因此我们要根据我们的需求防止这些状态的丢失。Android 给我们提供了两种选择:
- 将这些状态保存到
Activity
实例之外的地方:Activity
依然会重启,在重启时将这些状态值传给新的Activity
; - 我们自己处理横竖屏切换:
Activity
不会重启,我们可以根据需要在回调函数中自己处理状态变化。
1. Retaining an Object During a Orientation Change
横竖屏切换时,Activity
会重启,因此需要一个不依赖 Activity
实例的对象保存状态值。Android 官方文档推荐了两种方式,简单数据可保存到一个 Bundle 对象中;而复杂状态可保存在一个特殊的 Fragment
中。
1.1 Restore with Bundle
在横竖屏切换销毁 Activity
时,系统会将之前的状态以键值对的方式存储到一个 Bundle 对象中,默认只会保存布局中每个 View
的一些信息,要储存我们自己的状态信息,只用在相关的回调函数中去做就可以。
1.1.1 Save activity state
系统准备开始销毁 Activity
时会调用 onSaveInstanceState()
方法,因此我们要存储更多的状态就需要重写此方法。
static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
...
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state
savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
1.1.2 Restore activity state
Activity
被重新创建时,可以在 onCreate()
和 onRestoreInstanceState()
方法中读取之前存储的状态。onCreate()
在每次创建时都会被调用,非重启情况下 Bundle
为空。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
} else {
// Probably initialize members with default values for a new instance
}
...
}
onRestoreInstanceState()
方法会在 onStart()
之后被调用,并且只有在有存储状态时才会被调用。
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
// Restore state members from saved instance
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
}
1.2 Manage complex Object inside a Retained Fragment
一般情况下,Fragment
和会 Activity
一起被销毁,但是在 API level 11 之后,可以标记 Fragment
在其父 Activity
销毁时保留下来。利用这一特性,我们就可以将状态保存在一个特殊的 Fragment
中,需要做以下几步:
- 扩展
Fragment
类用于保存我们需要的状态值; - 创建
Fragment
时调用 setRetainInstance(boolean)方法; - 将
Fragment
添加到Activity
中; -
Activity
重启时使用 FragmentManager 获取Fragment
。
定义 Fragment
如下:
public class RetainedFragment extends Fragment {
// data object we want to retain
private MyDataObject 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(MyDataObject data) {
this.data = data;
}
public MyDataObject getData() {
return data;
}
}
然后在 Activity
中使用 Fragment
保存状态, 重启时使用 FragmentManager 获取 Fragment
,如下:
public class MyActivity extends Activity {
private static final String TAG_RETAINED_FRAGMENT = "RetainedFragment";
private RetainedFragment mRetainedFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(TAG_RETAINED_FRAGMENT);
// create the fragment and data the first time
if (mRetainedFragment == null) {
// add the fragment
mRetainedFragment = new RetainedFragment();
fm.beginTransaction().add(mRetainedFragment, TAG_RETAINED_FRAGMENT).commit();
// load data from a data source or perform any calculation
mRetainedFragment.setData(loadMyData());
}
// the data is available in mRetainedFragment.getData() even after
// subsequent configuration change restarts.
...
}
}
当我们不再需要保存的状态时,可在 onPause()
中检查 isFinishing()
并移除 Fragment
。
@Override
public void onPause() {
// perform other onPause related actions
...
// this means that this activity will not be recreated now, user is leaving it
// or the activity is otherwise finishing
if(isFinishing()) {
FragmentManager fm = getFragmentManager();
// we will not need this fragment anymore, this may also be a good place to signal
// to the retained fragment object to perform its own cleanup.
fm.beginTransaction().remove(mDataFragment).commit();
}
}
2. Handling the Orientation Change Yourself
如果由于性能等因素重启 Activity
太慢,并且应用没有资源依赖横竖屏状态,那么可以选择在 AndroidManifest.xml 中为 Activity
设置 android:configChanges
属性为 "orientation|screenSize"
。例如:
<activity android:name=".MyActivity"
android:configChanges="orientation|screenSize" />
现在,切换横竖屏不会再导致 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();
}
}
3. Summary
每种方法各有优缺,我们要根据场景选择合适的方法,如无必要,尽量不要选择最后一种方法。因为还有一些其它会导致 Activity
重启的情况,在这些情况下状态信息没有保存,也就无法恢复到之前的状态。
网友评论