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