Android Activity和Fragment状态保存

3,370 阅读4分钟

    我们在开发时需要保存Activity和Fragment的状态,以防他们被销毁。当我们的Activity不可见时,系统为了恢复内存而销毁某个Activity时,之后用户返回到这个Activity时,系统会重建这个Activity。这时如果不将状态保存下来,用户体验会非常差。Android解决这个问题,给开发者提供了一个回调方法onSaveInstanceState()来确保Activity状态的重要信息得到保留。系统会先调用onSaveInstanceState方法,然后再使Activity变得易于销毁。系统会向该方法传递一个Bundle,我们可以借用这个Bundle来保存重要信息。然后系统会终止此Activity,之后用户返回此Activity,系统会重建Activity,并将Bundle传递给onCreate方法和onRestoreInstanceState方法,我们可通过这两个方法来恢复状态。还有当我们Android手机配置发生变更时,如从原先的竖屏变为横屏,系统语言从中文变为英文等等。当发生这些变化时,Android为了让应用加载匹配的资源,默认会销毁旧Activity新建一个新的Activity(无论什么启动模式都是销毁后重建)。

1:Activity的状态保存

Activity保存和恢复状态涉及到三个方法:

onCreate(): 接收onSaveInstanceState方法传过来的Bundle。
onSaveInstanceState():将要保存的数据保存在Bundle中并传给onCreate和onRestoreInstanceState方法。
onRestoreInstanceState():接收onSaveInstanceState方法传过来的Bundle。

public class SaveActivity extends AppCompatActivity {
    private static final String TAG = "SaveActivity--lsc";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_save);
        //一般都在onCreate方法中恢复数据,
        if (savedInstanceState != null){
            //2:接收数据并恢复
            savedInstanceState.getString("saveString");
        }
        Log.d(TAG, "onCreate: Activity创建了");
    }

    /** 在onStop方法之前调用,
     * 无法保证系统会在销毁 Activity 前调用 onSaveInstanceState(),
     * 因为存在不需要保存状态的情况(例如用户使用“返回”按钮离开 Activity 时,
     * 因为用户的行为是在显式关闭 Activity)
     */
    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        //1:保存数据到Bundle中
        outState.putString("saveString","我是保存的字符串");
        Log.d(TAG, "onSaveInstanceState: ");
    }
    
    @Override
    protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.d(TAG, "onRestoreInstanceState: ");
    }

上述代码即可保存Activity状态。不过,即使我们什么都不做,不实现 onSaveInstanceState(),Activity 的 onSaveInstanceState() 默认实现也会恢复部分Activity状态。具体地讲,默认实现会为布局中的每个 View 调用相应的 onSaveInstanceState()方法,让每个视图都能提供有关自身的应保存信息。Android框架中几乎每个小部件都会根据需要实现此方法,以便在重建 Activity时自动保存和恢复对UI所做的任何可见更改。例如,EditText 小部件保存用户输入的任何文本,CheckBox小部件保存复选框的选中或未选中状态。我们只需为想要保存其状态的每个小部件提供一个唯一的ID(通过 android:id 属性)。如果小部件没有 ID,则系统无法保存其状态。

1.1:配置发生变更

这里要重点说明一下配置发生变更的情况,这个比较复杂一点。

1.1.1:系统默认行为

当配置发生变更时,Android会销毁当前Activity并立即重建当前Activity(此时这两个Activity对象不一样)。

1.2.2:限定屏幕方向

如果我们的应用只支持竖屏的话我们可以给Activity配置如下标签:

<activity android:name=".MainActivity"
    android:screenOrientation="portrait"/>
    <!-- portrait表示竖屏,landscap表示横屏 -->

此时屏幕方向发生变化,Android不会销毁并重建这个Activity。

1.2.3:自己处理变更

给Activity设置下列标签,屏幕方向变化的时候,activity不会销毁重建,但会变横屏。

<activity android:name=".MainActivity"
    android:configChanges="orientation|screenSize|keyboardHidder"/>
    <!-- 屏幕方向变化,宽高变化,键盘可见性。 -->

此时Activity会回调onConfigurationChanged方法,我们可以在这里处理这些变更。但一般情况下我们还是让系统处理这些变更,自己处理会让逻辑变得复杂。

我们自定义控件时,若是也想保存视图状态需要重写onSaveInstanceState和onRestoreInstanceState方法。
用户按home键时回调onSaveInstanceState()
用户来电话时回调onSaveInstanceState()
用户按返回键时不会回调onSaveInstanceState()

2:保存Fragment状态

当Activity销毁和新建时,必然会关联到依附于Activity的Fragment的销毁和创建。若是不做处理Fragment也会随着Activity销毁和新建。我们实际应用中有两种方式进行保存:1.保存Fragment对象。2.保存数据创建新的Fragment对象。

2.1:保存Fragment对象

public class SaveActivity extends AppCompatActivity {
    private static final String TAG = "SaveActivity--lsc";
    private Fragment mSaveFragment;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_save);
        
        FragmentManager fragmentManager = getSupportFragmentManager();
        //根据tag获取fragment对象。
        mSaveFragment = fragmentManager.findFragmentByTag("SaveFragment");
        if (mSaveFragment == null) {
            mSaveFragment = new SaveFragment();
            //给Fragment对象添加tag “SaveFragment”
            fragmentManager.beginTransaction().replace(R.id.fragment_layout, mSaveFragment, "SaveFragment").commit();
        }
        Log.d(TAG, "onCreate: Activity创建了");
    }
}

2.2:保存数据创建新的fragment

保存数据创建新的fragment这种方式涉及到三个方法:

onSaveInstanceState():保存数据到Bundle中。
onActivityCreated(): 读取Bundle。
onCreate(): 读取Bundle。

public class SaveFragment extends Fragment {
    private static final String TAG = "SaveFragment--lsc";
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate: ");
        if (savedInstanceState != null){
            
        }
    }
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.d(TAG, "onActivityCreated: ");
    }
    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.d(TAG, "onSaveInstanceState: ");
    }
}

若是不涉及Fragment更改加载资源,推荐使用保存Fragment对象的方式。