Handling Orientation Change in A

作者: xandeer | 来源:发表于2017-11-01 16:10 被阅读45次

默认情况下,横竖屏切换时 Android 会销毁当前 Activity 然后重启它。这时某些运行时状态可能会丢失,因此我们要根据我们的需求防止这些状态的丢失。Android 给我们提供了两种选择:

  1. 将这些状态保存到 Activity 实例之外的地方:Activity 依然会重启,在重启时将这些状态值传给新的 Activity
  2. 我们自己处理横竖屏切换: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 中,需要做以下几步:

  1. 扩展 Fragment 类用于保存我们需要的状态值;
  2. 创建 Fragment 时调用 setRetainInstance(boolean)方法;
  3. Fragment 添加到 Activity 中;
  4. 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 重启的情况,在这些情况下状态信息没有保存,也就无法恢复到之前的状态。

4. Reference

  1. Handling Configuration Changes
  2. Saving and restore activity state

相关文章

网友评论

    本文标题:Handling Orientation Change in A

    本文链接:https://www.haomeiwen.com/subject/luwwpxtx.html