对于 Activity 的 onSaveInstanceState 方法大家都不会陌生,当 Activity 在不正常销毁的情况下,就会调用 onSaveInstanceState 方法,并将 Activity 中需要保存的数据(比如 View 状态 或者我们自己的数据)保存到这个方法的参数 Bundle 中。
一、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键:
* 2021-01-29 16:30:29.493 27040-27040/com.example.saveinstancetest D/buder: onCreate
* 2021-01-29 16:30:29.494 27040-27040/com.example.saveinstancetest D/buder: onStart
* 2021-01-29 16:30:29.496 27040-27040/com.example.saveinstancetest D/buder: onResume
* 2021-01-29 16:30:35.313 27040-27040/com.example.saveinstancetest D/buder: onPause
* 2021-01-29 16:30:35.340 27040-27040/com.example.saveinstancetest D/buder: onStop
* 2021-01-29 16:30:35.343 27040-27040/com.example.saveinstancetest E/buder: onSaveInstanceState
*
*/
/**
* 从显示onResume时,进入到最近任务:
* 2021-01-29 16:31:30.103 27040-27040/com.example.saveinstancetest D/buder: onResume
* 2021-01-29 16:31:43.253 27040-27040/com.example.saveinstancetest D/buder: onPause
* 2021-01-29 16:31:43.274 27040-27040/com.example.saveinstancetest D/buder: onStop
* 2021-01-29 16:31:43.275 27040-27040/com.example.saveinstancetest E/buder: onSaveInstanceState
*/
/**
* 从显示onResume时,按电源键:
* 2021-01-29 16:33:29.434 27040-27040/com.example.saveinstancetest D/buder: onResume
* 2021-01-29 16:33:33.045 27040-27040/com.example.saveinstancetest D/buder: onPause
* 2021-01-29 16:33:33.049 27040-27040/com.example.saveinstancetest D/buder: onStop
* 2021-01-29 16:33:33.050 27040-27040/com.example.saveinstancetest E/buder: onSaveInstanceState
*/
/**
* 从本activity进入到另外一个activity时:
* 2021-01-29 16:35:12.927 27040-27040/com.example.saveinstancetest D/buder: onResume
* 2021-01-29 16:35:15.492 27040-27040/com.example.saveinstancetest D/buder: onPause
* 2021-01-29 16:35:16.054 27040-27040/com.example.saveinstancetest D/buder: onStop
* 2021-01-29 16:35:16.055 27040-27040/com.example.saveinstancetest E/buder: onSaveInstanceState
*/
/**
* 屏幕方向切换时:
* 2021-01-29 16:53:48.422 11050-11050/com.example.saveinstancetest D/buder: onResume
* 2021-01-29 16:53:50.186 11050-11050/com.example.saveinstancetest D/buder: onPause
* 2021-01-29 16:53:50.187 11050-11050/com.example.saveinstancetest D/buder: onStop
* 2021-01-29 16:53:50.188 11050-11050/com.example.saveinstancetest E/buder: onSaveInstanceState
* 2021-01-29 16:53:50.189 11050-11050/com.example.saveinstancetest D/buder: onDestroy
* 2021-01-29 16:53:50.236 11050-11050/com.example.saveinstancetest D/buder: onCreate
* 2021-01-29 16:53:50.237 11050-11050/com.example.saveinstancetest E/buder: onRestoreInstanceState方式一:恢复数据,onCreate的Restore,改变后的数据 change data
* 2021-01-29 16:53:50.242 11050-11050/com.example.saveinstancetest D/buder: onStart
* 2021-01-29 16:53:50.243 11050-11050/com.example.saveinstancetest E/buder: onRestoreInstanceState方式二:数据恢复 onRestore,改变后的数据 change data
* 2021-01-29 16:53:50.245 11050-11050/com.example.saveinstancetest D/buder: onResume
*/
二、onRestoreInstanceState什么时机被调用
onRestoreInstanceState(Bundle savedInstanceState)只有在activity确实是被系统回收,重新创建activity的情况下才会被调用。
onRestoreInstanceState(Bundle outState)会在以下情况被调用:
1、屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)
2、由于内存紧张导致后台运行的程序被kill掉时(这种不太好模拟)
//************************调用 onRestoreInstanceState() ***************************************
/**
* 屏幕方向切换时:
* 2021-01-29 16:53:48.422 11050-11050/com.example.saveinstancetest D/buder: onResume
* 2021-01-29 16:53:50.186 11050-11050/com.example.saveinstancetest D/buder: onPause
* 2021-01-29 16:53:50.187 11050-11050/com.example.saveinstancetest D/buder: onStop
* 2021-01-29 16:53:50.188 11050-11050/com.example.saveinstancetest E/buder: onSaveInstanceState
* 2021-01-29 16:53:50.189 11050-11050/com.example.saveinstancetest D/buder: onDestroy
* 2021-01-29 16:53:50.236 11050-11050/com.example.saveinstancetest D/buder: onCreate
* 2021-01-29 16:53:50.237 11050-11050/com.example.saveinstancetest E/buder: onRestoreInstanceState方式一:恢复数据,onCreate的Restore,改变后的数据 change data
* 2021-01-29 16:53:50.242 11050-11050/com.example.saveinstancetest D/buder: onStart
* 2021-01-29 16:53:50.243 11050-11050/com.example.saveinstancetest E/buder: onRestoreInstanceState方式二:数据恢复 onRestore,改变后的数据 change data
* 2021-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");
}
}
三、onCreate()里也有Bundle参数,可以用来恢复数据,它和onRestoreInstanceState有什么区别?
因为onSaveInstanceState 不一定会被调用,所以onCreate()里的Bundle参数可能为空,如果使用onCreate()来恢复数据,一定要做非空判断。
而onRestoreInstanceState的Bundle参数一定不会是空值,因为它只有在上次activity被回收了才会调用。
四、onSaveInstanceState 的数据存在哪里?为什么限制了大小?
在实际使用的时候你可能会发现当保存的数据过大的时候就会看到如下的 log 日志
|
甚至可能发生异常(在高版本下会抛出异常,低版本直接打印日志)
|
上面的信息都是表示 Bundle 传输的数据过大,那么问题来了, onSaveInstanceState 中 Bundle 的数据是存放在哪里,为什么又限制?
1.官网
在官网 有关于 Parcelables and Bundles 的一段介绍,其中就有提到 Bundles 的数据大小问题。
翻译一下: Binder 传输缓冲区是一个限制的大小的区域,大小为 1MB,这块缓冲区用于所有进程间的通信,也就是 Binder 通信。这些传输包括 onSaveInstanceState , startActivity 和其他与系统的交互,当传输的数据超过这个大小的时候就会抛出异常。
特别是 onSaveInstanceState 方法,因其需要在 Activity 返回的时候提供数据,官网建议是数据大小不大于 50K.
关于 startActivity 和其他系统交互需要使用 Binder 进行跨进程通信我们知道,但是你可能就有疑问 onSaveInstanceState 不是在自己进程中做 Activity 某些状态的保存,为什么需要 Binder 呢?
关于 Binder 机制中 Binder 缓冲区的限制,网上有很多文章,这里就不进行说明了。
2.系统源码追踪
ActivityThread
在追 onSaveInstanceState 的方法源码的时候,想要找到关于 Binder 方面的内容,就顺着Activity 的方法追,很多人都是到 Application 的 接口中就迷路了.
|
其实这是一个错误的方向,其实只要想到 onSaveInstanceState 有点类似于 Activity 中其他生命周期的方法,就可以发现 onSaveInstanceState 最开始也是有 ActivityThread 做统一管理的,那么 onSaveInstanceState 的调用 也就和 ActivityThread 有关。
Bundle 的创建
在 ActivityThread 的源码中可以看到有这么个方法
|
其中最后调用了 mInstrumentation.callActivityOnSaveInstanceState
那么就到 mInstrumentation 类中查找
|
这个方法中又调用了 activity.performSaveInstanceState 的方法
|
最后调用的是 Activity onSaveInstanceState 方法 ,显然 callCallActivityOnSaveInstanceState 的 r.state 就是 onSaveInstanceState 的方法的中的参数 Bundle .而 r 就是 ActivityThread 的内部类 ActivityClientRecord
|
到这里我们知道 Bundle 是怎样创建的,但是关于 Binder 传输的问题,这里也没有体现,那么究竟是在哪里涉及的 Binder 传输的呢?
onSaveInstanceState 调用时机
这里先补充一个点就是 onSaveInstanceState 的方法的调用时机
|
从注释中可以看到这个方法的调用会在 onStop 前,那么我们就去 ActivityTread 中看看再 onStop 前发生了什么。
|
StopInfo 是一个实现了 Runnable 接口的类,且内部也有Bundle 的引用 所以可以 将 ActivityClientRecord 的 Bundle 赋值给 StopInfo 的 Bundle
|
最后因为 StopInfo 被 Handle post 之后,那么就会执行其相应的 run 方法。
|
到这里就可以明白了 onSaveInstanceState 中保存的 Bundle 信息是存在内存中的,且因为是涉及到 Activity 的状态的保存,就需要交由 ActivityManager 进程去做一个管理,所以就需要 Binder 传输做一个跨进程的通信将 Bundle 的数据传递给 ActivityManager。因此 onSaveInstanceState 也涉及到了 Binder 传输,自然而然就受到 Binder 缓冲区大小的限制,到这里问题就解决了。