onSaveInstanceState()和onRestoreInstanceState()使用详解


前言

虽然现在 Android 中也有其他状态保存和恢复的方式,比如 ViewModel ,但 onSaveInstanceState() 和onRestoreInstanceState() 仍然是重要的状态保存和恢复方法,特别适用于一些简单的状态数据保存和恢复场景。

一、简介

如果系统由于系统约束(而不是正常的应用程序行为)而破坏了Activity,那么尽管实际 Activity实例已经消失,但是系统还是会记住它已经存在,这样如果用户导航回到它,系统会创建一个新的实例的Activity使用一组保存的数据来描述Activity在被销毁时的状态。系统用于恢复以前状态的已保存数据称为“实例状态”,是存储在Bundle对象中的键值对的集合。

onSaveInstanceState()和onRestoreInstanceState()就是这样的背景下大展身手了。

注意

1、如果是用户自动按下返回键,或程序调用finish()退出程序,是不会触发onSaveInstanceState()和onRestoreInstanceState()的。
2、每次用户旋转屏幕时,您的Activity将被破坏并重新创建。当屏幕改变方向时,系统会破坏并重新创建前台Activity,因为屏幕配置已更改,您的Activity可能需要加载替代资源(例如布局)。即会执行onSaveInstanceState()和onRestoreInstanceState()的。

默认情况下,系统使用Bundle实例状态来保存有关View中Activity布局每个对象的信息(例如输入到EditText对象中的文本值)。因此,如果您的Activity实例被销毁并重新创建,则布局状态会自动恢复到之前的状态。但是,您的Activity可能包含更多要恢复的状态信息,例如跟踪Activity中用户进度的成员变量。

为了让您为Activity添加额外的数据到已保存的实例状态,Activity生命周期中还有一个额外的回调方法,这些回调方法在前面的课程中没有显示。该方法是onSaveInstanceState(),系统在用户离开Activity时调用它。当系统调用此方法时,它将传递Bundle将在您的Activity意外销毁的事件中保存的对象,以便您可以向其中添加其他信息。然后,如果系统在被销毁之后必须重新创建Activity实例,它会将相同的Bundle对象传递给您的Activity的onRestoreInstanceState()方法以及您的onCreate() 方法。
如图所示:
在这里插入图片描述

当系统开始停止您的Activity时,它会调用onSaveInstanceState(),以便您可以指定要保存的其他状态数据,以防Activity必须重新创建实例。如果Activity被破坏并且必须重新创建相同的实例,则系统将中定义的状态数据传递给onCreate()方法和onRestoreInstanceState()方法。

1.1 保存Activity状态

当您的Activity开始停止时,系统会调用,onSaveInstanceState()以便您的Activity可以使用一组键值对来保存状态信息。此方法的默认实现保存有关Activity视图层次结构状态的信息,例如EditText小部件中的文本或ListView的滚动位置。

为了保存Activity的附加状态信息,您必须实现onSaveInstanceState()并向对象添加键值对Bundle。例如:

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
...

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // 保存用户自定义的状态
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
    savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
    
    // 调用父类交给系统处理,这样系统能保存视图层次结构状态
    super.onSaveInstanceState(savedInstanceState);
}

1.2 恢复Activity状态

当您的Activity在之前被破坏后重新创建时,您可以从Bundle系统通过您的Activity中恢复您的保存状态。这两个方法onCreate()和onRestoreInstanceState()回调方法都会收到Bundle包含实例状态信息的相同方法。

因为onCreate()调用该方法是否系统正在创建一个新的Activity实例或重新创建一个以前的实例,所以您必须Bundle在尝试读取之前检查该状态是否为空。如果它为空,那么系统正在创建一个Activity的新实例,而不是恢复之前被销毁的实例。

例如,下面是如何恢复一些状态数据onCreate():

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // 记得总是调用父类
   
    // 检查是否正在重新创建一个以前销毁的实例
    if (savedInstanceState != null) {
        // 从已保存状态恢复成员的值
        mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
        mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // 可能初始化一个新实例的默认值的成员
    }
    ...
}

也可以选择执行onRestoreInstanceState(),而不是在系统调用onStart()方法之后恢复状态。系统onRestoreInstanceState()只有在存在保存状态的情况下才会恢复,因此您不需要检查是否Bundle为空:

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // 总是调用超类,以便它可以恢复视图层次超级
    super.onRestoreInstanceState(savedInstanceState);
   
    // 从已保存的实例中恢复状态成员
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
    mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

二、调用时机及大小限制

2.1 onSaveInstanceState(Bundle outState)的调用时机

onSaveInstanceState(Bundle outState)会在以下情况被调用:

  • 1、当用户按下HOME键时。
  • 2、从最近应用中选择运行其他的程序时。
  • 3、按下电源按键(关闭屏幕显示)时。
  • 4、从当前activity启动一个新的activity时。
  • 5、屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)。

Activity的onSaveInstanceState回调时机,取决于app的targetSdkVersion:
targetSdkVersion低于11的app,onSaveInstanceState方法会在Activity.onPause之前回调;
targetSdkVersion低于28的app,则会在onStop之前回调;
28之后,onSaveInstanceState在onStop回调之后才回调。
演示项目的targetSDK大于28,所以全部都是在onStop之后调用的:

//************************调用 onSaveInstanceState() ***************************************
/**
 * 按home键:
 * 2024-01-29 16:30:29.493 27040-27040/com.example.saveinstancetest D/buder: onCreate
 * 2024-01-29 16:30:29.494 27040-27040/com.example.saveinstancetest D/buder: onStart
 * 2024-01-29 16:30:29.496 27040-27040/com.example.saveinstancetest D/buder: onResume
 * 2024-01-29 16:30:35.313 27040-27040/com.example.saveinstancetest D/buder: onPause
 * 2024-01-29 16:30:35.340 27040-27040/com.example.saveinstancetest D/buder: onStop
 * 2024-01-29 16:30:35.343 27040-27040/com.example.saveinstancetest E/buder: onSaveInstanceState
 *
 */
/**
 * 从显示onResume时,进入到最近任务:
 * 2024-01-29 16:31:30.103 27040-27040/com.example.saveinstancetest D/buder: onResume
 * 2024-01-29 16:31:43.253 27040-27040/com.example.saveinstancetest D/buder: onPause
 * 2024-01-29 16:31:43.274 27040-27040/com.example.saveinstancetest D/buder: onStop
 * 2024-01-29 16:31:43.275 27040-27040/com.example.saveinstancetest E/buder: onSaveInstanceState
 */
/**
 * 从显示onResume时,按电源键:
 * 2024-01-29 16:33:29.434 27040-27040/com.example.saveinstancetest D/buder: onResume
 * 2024-01-29 16:33:33.045 27040-27040/com.example.saveinstancetest D/buder: onPause
 * 2024-01-29 16:33:33.049 27040-27040/com.example.saveinstancetest D/buder: onStop
 * 2024-01-29 16:33:33.050 27040-27040/com.example.saveinstancetest E/buder: onSaveInstanceState
 */
/**
 * 从本activity进入到另外一个activity时:
 * 2024-01-29 16:35:12.927 27040-27040/com.example.saveinstancetest D/buder: onResume
 * 2024-01-29 16:35:15.492 27040-27040/com.example.saveinstancetest D/buder: onPause
 * 2024-01-29 16:35:16.054 27040-27040/com.example.saveinstancetest D/buder: onStop
 * 2024-01-29 16:35:16.055 27040-27040/com.example.saveinstancetest E/buder: onSaveInstanceState
 */
/**
 * 屏幕方向切换时:
 * 2024-01-29 16:53:48.422 11050-11050/com.example.saveinstancetest D/buder: onResume
 * 2024-01-29 16:53:50.186 11050-11050/com.example.saveinstancetest D/buder: onPause
 * 2024-01-29 16:53:50.187 11050-11050/com.example.saveinstancetest D/buder: onStop
 * 2024-01-29 16:53:50.188 11050-11050/com.example.saveinstancetest E/buder: onSaveInstanceState
 * 2024-01-29 16:53:50.189 11050-11050/com.example.saveinstancetest D/buder: onDestroy
 * 2024-01-29 16:53:50.236 11050-11050/com.example.saveinstancetest D/buder: onCreate
 * 2024-01-29 16:53:50.237 11050-11050/com.example.saveinstancetest E/buder: onRestoreInstanceState方式一:恢复数据,onCreate的Restore,改变后的数据 change data
 * 2024-01-29 16:53:50.242 11050-11050/com.example.saveinstancetest D/buder: onStart
 * 2024-01-29 16:53:50.243 11050-11050/com.example.saveinstancetest E/buder: onRestoreInstanceState方式二:数据恢复 onRestore,改变后的数据 change data
 * 2024-01-29 16:53:50.245 11050-11050/com.example.saveinstancetest D/buder: onResume
 */

2.2 onRestoreInstanceState的调用时机

onRestoreInstanceState(Bundle savedInstanceState)只有在activity确实是被系统回收,重新创建activity的情况下才会被调用。

onRestoreInstanceState(Bundle outState)会在以下情况被调用:

  • 1、屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)
  • 2、由于内存紧张导致后台运行的程序被kill掉时(这种不太好模拟)
//************************调用 onRestoreInstanceState() ***************************************
 
/**
 * 屏幕方向切换时:
 * 2024-01-29 16:53:48.422 11050-11050/com.example.saveinstancetest D/buder: onResume
 * 2024-01-29 16:53:50.186 11050-11050/com.example.saveinstancetest D/buder: onPause
 * 2024-01-29 16:53:50.187 11050-11050/com.example.saveinstancetest D/buder: onStop
 * 2024-01-29 16:53:50.188 11050-11050/com.example.saveinstancetest E/buder: onSaveInstanceState
 * 2024-01-29 16:53:50.189 11050-11050/com.example.saveinstancetest D/buder: onDestroy
 * 2024-01-29 16:53:50.236 11050-11050/com.example.saveinstancetest D/buder: onCreate
 * 2024-01-29 16:53:50.237 11050-11050/com.example.saveinstancetest E/buder: onRestoreInstanceState方式一:恢复数据,onCreate的Restore,改变后的数据 change data
 * 2024-01-29 16:53:50.242 11050-11050/com.example.saveinstancetest D/buder: onStart
 * 2024-01-29 16:53:50.243 11050-11050/com.example.saveinstancetest E/buder: onRestoreInstanceState方式二:数据恢复 onRestore,改变后的数据 change data
 * 2024-01-29 16:53:50.245 11050-11050/com.example.saveinstancetest D/buder: onResume
 */

模拟代码:

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
 
import androidx.appcompat.app.AppCompatActivity;
 
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "buder";
    private static final String STORE_KEY = "key_one";
    TextView mTextView;
    Button mBtn;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "onCreate");
        mTextView = findViewById(R.id.text);
        mBtn = findViewById(R.id.btn);
        mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(getApplicationContext(), MainActivity2.class);
                startActivity(intent);
            }
        });
 
        //方式一:数据恢复,从K-V中获取销毁之前的存储值
        if (savedInstanceState != null) {
            String test = savedInstanceState.getString(STORE_KEY);
            Log.e(TAG, "方式一:恢复数据,onCreate的Restore," + test);
            mTextView.setText(test);
        }
    }
 
    //在pause/stop之间进行界面上的数据存储,到K-V中
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.e(TAG, "onSaveInstanceState");
//        String change = mTextView.getText().toString();
        outState.putString(STORE_KEY, "改变后的数据 change data");
    }
 
    //方式二:数据恢复,从K-V中获取销毁之前的存储值
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        String test = savedInstanceState.getString(STORE_KEY);
        Log.e(TAG, "方式二:数据恢复 onRestore," + test);
        mTextView.setText(test);
    }
 
    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart");
    }
 
    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart");
    }
 
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");
    }
 
    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause");
    }
 
    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop");
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }
}

三、其他问题

3.1 onCreate()里也有Bundle参数,可以用来恢复数据,它和onRestoreInstanceState有什么区别?

因为onSaveInstanceState 不一定会被调用,所以onCreate()里的Bundle参数可能为空,如果使用onCreate()来恢复数据,一定要做非空判断。

public void onCreate(Bundle savedInstanceState) {
  ...
  if (savedInstanceState == null) {
      // init
  } else {
      //恢复数据
  }
  ...
 
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
}

而onRestoreInstanceState的Bundle参数一定不会是空值,因为它只有在上次activity被回收了才会调用。


3.2 onSaveInstanceState 的数据存在哪里?为什么限制了大小?

在实际使用的时候你可能会发现当保存的数据过大的时候就会看到如下的 log 日志

javabinder !!! FAILED BINDER TRANSACTION !!!

甚至可能发生异常(在高版本下会抛出异常,低版本直接打印日志)

android.os.TransactionTooLargeException

上面的信息都是表示 Bundle 传输的数据过大,那么问题来了, onSaveInstanceState 中 Bundle 的数据是存放在哪里,为什么又限制?

在官网 有关于 Parcelables and Bundles 的一段介绍,其中就有提到 Bundles 的数据大小问题。

在这里插入图片描述

Binder 传输缓冲区是一个限制的大小的区域,大小为 1MB,这块缓冲区用于所有进程间的通信,也就是 Binder 通信。这些传输包括 onSaveInstanceState , startActivity 和其他与系统的交互,当传输的数据超过这个大小的时候就会抛出异常。
特别是 onSaveInstanceState 方法,因其需要在 Activity 返回的时候提供数据,官网建议是数据大小不大于 50K.

关于 startActivity 和其他系统交互需要使用 Binder 进行跨进程通信我们知道,但是你可能就有疑问 onSaveInstanceState 不是在自己进程中做 Activity 某些状态的保存,为什么需要 Binder 呢?

系统源码追踪

3.2.1 ActivityThread

在追 onSaveInstanceState 的方法源码的时候,想要找到关于 Binder 方面的内容,就顺着Activity 的方法追,很多人都是到 Application 的 接口中就迷路了.

public interface ActivityLifecycleCallbacks {
void onActivityCreated(Activity activity, Bundle savedInstanceState);
void onActivityStarted(Activity activity);
void onActivityResumed(Activity activity);
void onActivityPaused(Activity activity);
void onActivityStopped(Activity activity);
void onActivitySaveInstanceState(Activity activity, Bundle outState);
void onActivityDestroyed(Activity activity);
}

其实这是一个错误的方向,其实只要想到 onSaveInstanceState 有点类似于 Activity 中其他生命周期的方法,就可以发现 onSaveInstanceState 最开始也是有 ActivityThread 做统一管理的,那么 onSaveInstanceState 的调用 也就和 ActivityThread 有关。

3.2.2 Bundle 的创建

在 ActivityThread 的源码中可以看到有这么个方法

    private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
        r.state = new Bundle();
        r.state.setAllowFds(false);
        if (r.isPersistable()) {
            r.persistentState = new PersistableBundle();
            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,
                    r.persistentState);
        } else {
            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
        }
    }

其中最后调用了 mInstrumentation.callActivityOnSaveInstanceState,
那么就到 mInstrumentation 类中查找

public void callActivityOnSaveInstanceState(Activity activity, Bundle outState,
PersistableBundle outPersistentState) {
activity.performSaveInstanceState(outState, outPersistentState);
}

这个方法中又调用了 activity.performSaveInstanceState 的方法

final void performSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
onSaveInstanceState(outState, outPersistentState);
saveManagedDialogs(outState);
storeHasCurrentPermissionRequest(outState);
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState +
", " + outPersistentState);
}
...
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
onSaveInstanceState(outState);
}

最后调用的是 Activity onSaveInstanceState 方法 ,显然 callCallActivityOnSaveInstanceState 的 r.state 就是 onSaveInstanceState 的方法的中的参数 Bundle .而 r 就是 ActivityThread 的内部类 ActivityClientRecord

static final class ActivityClientRecord {
IBinder token;
int ident;
Intent intent;
String referrer;
IVoiceInteractor voiceInteractor;
Bundle state; // 这个就是 r.state 也就是 onSaveInstanceState 的 Bundle
}

到这里我们知道 Bundle 是怎样创建的,但是关于 Binder 传输的问题,这里也没有体现,那么究竟是在哪里涉及的 Binder 传输的呢?

3.2.3 onSaveInstanceState 调用时机

这里先补充一个点就是 onSaveInstanceState 的方法的调用时机

* <p>If called, this method will occur before {@link #onStop}. There are
* no guarantees about whether it will occur before or after {@link #onPause}.
*
* @param outState Bundle in which to place your saved state.
*
* @see #onCreate
* @see #onRestoreInstanceState
* @see #onPause
*/
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mAutoFillResetNeeded) {
outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
getAutofillManager().onSaveInstanceState(outState);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}

从注释中可以看到这个方法的调用会在 onStop 前,那么我们就去 ActivityTread 中看看再 onStop 前发生了什么

private void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {
ActivityClientRecord r = mActivities.get(token);
....
StopInfo info = new StopInfo();
//
performStopActivityInner(r, info, show, true, "handleStopActivity");
...
info.activity = r;
//注意这里 将 r.state 赋值给 info.state
info.state = r.state;
info.persistentState = r.persistentState;
//然后调用 Handler 的 post 方法将这个 StopInfo 类传递给消息队列
mH.post(info);
mSomeActivitiesChanged = true;
}

StopInfo 是一个实现了 Runnable 接口的类,且内部也有Bundle 的引用 所以可以 将 ActivityClientRecord 的 Bundle 赋值给 StopInfo 的 Bundle

private static class StopInfo implements Runnable {
ActivityClientRecord activity;
Bundle state;
....
}

最后因为 StopInfo 被 Handle post 之后,那么就会执行其相应的 run 方法。

    private static class StopInfo implements Runnable {
        ActivityClientRecord activity;
        Bundle state;
        PersistableBundle persistentState;
        CharSequence description;

        @Override
        public void run() {
// Tell activity manager we have been stopped.
            try {
//注意到这里将 state 作为参数调用了 ActivityManager.getService() 方法。
                ActivityManager.getService().activityStopped(
                        activity.token, state, persistentState, description);
            } catch (RemoteException ex) {
                if (ex instanceof TransactionTooLargeException
                        && activity.loadedApk.getTargetSdkVersion() < Build.VERSION_CODES.N) {
                    Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex);
                    return;
                }
                throw ex.rethrowFromSystemServer();
            }
        }
    }

到这里就可以明白了 onSaveInstanceState 中保存的 Bundle 信息是存在内存中的,且因为是涉及到 Activity 的状态的保存,就需要交由 ActivityManager 服务去做一个管理,所以就需要 Binder 传输将 Bundle 的数据传递给 ActivityManager。因此 onSaveInstanceState 也涉及到了 Binder 传输,自然而然就受到 Binder 缓冲区大小的限制,到这里问题就解决了。


3.3 Fragment中的savedInstanceState

Fragment和Activity一样,onSaveInstanceState保存数据,但当activity中包含fragment时,fragment用onSaveInstanceState保存数据会导致保存数据丢失,比如Activity中初始化

if (mFragment == null) {
    mFragment = new CarSetFragment();
}

在Activty被销毁重建时,Fragment被重新创建,

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    mRootView = inflater.inflate(R.layout.layout_page_carset, null);
    if (savedInstanceState == null) {
        init();
    } else {
        //恢复数据
    }
    return mRootView;
}

Fragment中的savedInstanceState为null,fragment无法恢复保存的数据,因为actiity在重新创建时mFragment 优new了一个对象,当前显示的Fragment不是销毁前的Fragment,所以savedInstanceState 为null。解决办法就时在Activity销毁时保存Fragment

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    if (mFragment != null && mFragment.isAdded()) {
        getSupportFragmentManager().putFragment(savedInstanceState, "mFragment", mFragment);
    }
    super.onSaveInstanceState(savedInstanceState);
}

保存Fragment要判断是否已经Add,否则会报异常

IllegalStateException: Fragment <CarSetFragment> is not currently in the FragmentManager...

参考链接:
Android onSaveInstanceState和onRestoreInstanceState调用时机、及大小限制
Android 使用onSaveInstanceState保存数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值