美文网首页CodePath Android教程
CHA1-Structure——7.处理配置信息的变化

CHA1-Structure——7.处理配置信息的变化

作者: DarkAndroid | 来源:发表于2017-06-14 23:01 被阅读8次

    原文:Handling Configuration Changes

    —Screen Rotation

    概述


    存在这样一些情况:当屏幕方向旋转时,Activity实际上可以被销毁并从内存中移除,然后再从头重建一次。在这种情况下,最佳的处理方式是准备为将被重建的Activity,通过正确的方式保存并恢复状态。

    保存并恢复Activity状态


    当Activity将要停止时,系统调用onSaveInstanceState(),将Activity的状态信息保存在“键-值”对的集合中。该方法的默认实现会自动保存Activity的视图层次结构状态的信息,例如EditText组件中的文本或ListView的滚动位置。

    要保存Activity的其他状态信息,必须要实现onSaveInstanceState(),并向Bundle对象中添加“键-值”对。例如:

    public class MainActivity extends Activity {
        static final String SOME_VALUE = "int_value";
        static final String SOME_OTHER_VALUE = "string_value";
    
        @Override
        protected void onSaveInstanceState(Bundle savedInstanceState) {
            // 在bundle中保存自定义的value值
            savedInstanceState.putInt(SOME_VALUE, someIntValue);
            savedInstanceState.putString(SOME_OTHER_VALUE, someStringValue);
            // 总是调用父类的方法,来保存视图层次结构状态
            super.onSaveInstanceState(savedInstanceState);
        }
    }
    

    系统将在Activity被销毁前调用上述的方法,之后系统将会调用onRestoreInstanceState从bundle中恢复状态:

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        // 总是调用父类的方法,来恢复视图层次结构状态
        super.onRestoreInstanceState(savedInstanceState);
        // 从保存的实例中恢复状态成员
        someIntValue = savedInstanceState.getInt(SOME_VALUE);
        someStringValue = savedInstanceState.getString(SOME_OTHER_VALUE);
    }
    

    实例状态也可以在标准的Activity#onCreate方法中恢复,但在onRestoreInstanceState中操作起来更加方便,它可以确保所有的初始化都已完成,并允许子类决定是否使用默认实现。 更多细节请参阅 this stackoverflow post

    注意onSaveInstanceStateonRestoreInstanceState方法不能保证一起被调用。Android系统在Activity有可能被销毁时调用onSaveInstanceState()。有些情况下,onSaveInstanceState()被调用,但是Activity并没有被销毁,因此onRestoreInstanceState未被调用。

    更多内容请参考指南Recreating an Activity

    保存并恢复Fragment状态


    Fragment也有onSaveInstanceState方法,当它们的状态需要保存时该方法会被调用:

    public class MySimpleFragment extends Fragment {
        private int someStateValue;
        private final String SOME_VALUE_KEY = "someValueToSave";
       
        // 当配置更改或Fragment需要保存状态时触发
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            outState.putInt(SOME_VALUE_KEY, someStateValue);
            super.onSaveInstanceState(outState);
        }
    }
    

    我们可以从onCreateView中将保存的数据抽取出来:

    public class MySimpleFragment extends Fragment {
       // ...
    
       // 基于xml布局文件为Fragment填充视图
       @Override
       public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.my_simple_fragment, container, false);
            if (savedInstanceState != null) {
                someStateValue = savedInstanceState.getInt(SOME_VALUE_KEY);
                // 在必要时使用恢复的值
            }
            return view;
       }
    }
    

    为了正确地保存Fragment状态,我们必须确保当配置变化时,我们并不是在不必要地重建Fragment。这意味着当现有的Fragment已经存在时,不要再重新初始化。在Activity中初始化的任何Fragment,当配置变化之后,都要按标签查找

    public class ParentActivity extends AppCompatActivity {
        private MySimpleFragment fragmentSimple;
        private final String SIMPLE_FRAGMENT_TAG = "myfragmenttag";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            if (savedInstanceState != null) { // 保存实例状态,Fragment可能存在
               // 通过标签查找已存在的实例
               fragmentSimple = (MySimpleFragment)  
                  getSupportFragmentManager().findFragmentByTag(SIMPLE_FRAGMENT_TAG);
            } else if (fragmentSimple == null) { 
               // 仅在Fragment还未被初始化的情况下创建它们
               fragmentSimple = new MySimpleFragment();
            }
        }
    }
    

    这就要求我们在使用事务将Fragment放入Activity中时,注意包含查找标签

    public class ParentActivity extends AppCompatActivity {
        private MySimpleFragment fragmentSimple;
        private final String SIMPLE_FRAGMENT_TAG = "myfragmenttag";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            // ... 在这之上查找或初始化Fragment...
            // 对将要插入容器内的Fragment总是添加一个标签
            if (!fragmentSimple.isInLayout()) {
                getSupportFragmentManager()
                    .beginTransaction()
                    .replace(R.id.container, fragmentSimple, SIMPLE_FRAGMENT_TAG)
                    .commit();
            }
        }
    }
    

    通过这种简单的模式,我们可以正确地重新使用Fragment,并在配置变化时恢复它们的状态。

    保留Fragments


    在很多情况下,我们可以通过简单地使用Fragment重新创建Activity,来避免问题的出现。如果你的视图和状态都在Fragment中,当Activity重建时我们就可以轻松地保留Fragment:

    public class RetainedFragment extends Fragment {
        // 要保留的数据对象
        private MyDataObject data;
    
        // 该方法针对这个Fragment仅会被调用一次
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // 当Activity重建时保留这个Fragment
            setRetainInstance(true);
        }
    
        public void setData(MyDataObject data) {
            this.data = data;
        }
    
        public MyDataObject getData() {
            return data;
        }
    }
    

    这种方法可以防止Fragment在Activity的生命周期内被破坏。它们保留在Fragment Manager中。更多信息请参阅Android 官方文档。

    现在你可以在Fragment创建之前通过标签检测Fragment是否已存在,并且Fragment可以在配置变化期间保留它的状态。更多细节请参阅Handling Runtime Changes

    正确处理列表状态


    ListView

    通常在旋转屏幕时,应用程序都将失去滚动位置和屏幕上列表的其他状态。要正确地保留ListView的状态,你可以在onPause中存储实例的状态并从onViewCreated中恢复,如下所示:

    // YourActivity.java
    private static final String LIST_STATE = "listState";
    private Parcelable mListState = null;
    
    // 将List的状态值写入bundle
    @Override
    protected void onSaveInstanceState(Bundle state) {
        super.onSaveInstanceState(state);
        mListState = getListView().onSaveInstanceState();
        state.putParcelable(LIST_STATE, mListState);
    }
    
    // 从bundle中恢复list的状态值
    @Override
    protected void onRestoreInstanceState(Bundle state) {
        super.onRestoreInstanceState(state);
        mListState = state.getParcelable(LIST_STATE);
    }
    
    
    @Override
    protected void onResume() {
        super.onResume();
        loadData(); // 首先要确保数据已经重新加载到适配器中
        // 一旦将数据项加载到适配器中,立即调用这一部分
        // 例如,网络请求的成功回调
        if (mListState != null) {
            myListView.onRestoreInstanceState(mListState);
            mListState = null;
        }
    }
    

    查看这篇博客文章和[stackoverflow文章](stackoverflow post](http://stackoverflow.com/a/5688490)了解更多细节。
     请注意,在调用onRestoreInstanceState之前,必须先将数据项加载到是配置中。换句话说,在数据未从网络或者数据库加载回来之前,请不要在ListView上调用onRestoreInstance

    RecyclerView

    通常在旋转屏幕时,应用程序都将失去滚动位置和屏幕上列表的其他状态。要正确地保留RecyclerView的状态,你可以在onPause中存储实例的状态并从onViewCreated中恢复,如下所示:

    // YourActivity.java
    public final static int LIST_STATE_KEY = "recycler_list_state";
    Parcelable listState;
    
    protected void onSaveInstanceState(Bundle state) {
         super.onSaveInstanceState(state);
         // 保存list状态
         listState = mLayoutManager.onSaveInstanceState();
         state.putParcelable(LIST_STATE_KEY, mListState);
    }
    
    protected void onRestoreInstanceState(Bundle state) {
        super.onRestoreInstanceState(state);
        // 检索list状态和列表项的位置
        if(state != null)
            listState = state.getParcelable(LIST_STATE_KEY);
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        if (listState != null) {
            mLayoutManager.onRestoreInstanceState(listState);
        }
    }
    

    查看这篇博客文章stackoverflow文章 了解更多细节。

    锁定屏幕方向


    如果你想锁定应用中屏幕方向的变化,只需要在AndroidManifest.xml文件中给 <activity>标签设置android:screenOrientation属性即可:

    <activity
        android:name="com.techblogon.screenorientationexample.MainActivity"
        android:screenOrientation="portrait"
        android:label="@string/app_name" >
        <!-- ... -->
    </activity>
    

    现在,Activity总是被强制以“竖屏”方式展示。

    手动管理配置变化


    如果你的应用在特定的配置变化时不需要更新资源,并且对性能方面有诸多限制,避免Activity重新启动,你可以声明由Activity自己处理配置变化,这将防止系统重新启动Activity。
     然而,这种技术应该被当做避免Activity在配置变化时重启的一种不得已而为之的方式,在大多数应用中并不推荐。采用这种方法,你必须添加android:configChanges节点到AndroidManifest.xml中的Activity中:

    <activity android:name=".MyActivity"
              android:configChanges="orientation|screenSize|keyboardHidden"
              android:label="@string/app_name">
    

    现在,当配置变化时Activity并不会重启,而是会收到一个onConfigurationChanged()的调用:

    // 在收到这些变化的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();
        }
    }
    

    参考Handling the Change文档. 更多处理Activity中配置变化的内容,请参阅 android:configChanges文档和Configuration类.

    参考引用

    相关文章

      网友评论

        本文标题:CHA1-Structure——7.处理配置信息的变化

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