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

原文: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文件中给 标签设置android:screenOrientation属性即可:


    

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

手动管理配置变化


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


现在,当配置变化时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类.

参考引用

  • http://developer.android.com/guide/topics/resources/runtime-changes.html
  • http://developer.android.com/training/basics/activity-lifecycle/recreating.html
  • http://www.vogella.com/tutorials/AndroidLifeCycle/article.html#configurationchange
  • http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
  • http://www.intertech.com/Blog/saving-and-retrieving-android-instance-state-part-1/
  • http://sunil-android.blogspot.com/2013/03/save-and-restore-instance-state.html
  • https://medium.com/google-developers/activity-revival-and-the-case-of-the-rotating-device-167e34f9a30d#.nq3b23lxg

你可能感兴趣的:(CHA1-Structure——7.处理配置信息的变化)