在开发中发现一个问题:当一个我通过Intent开启一个前面已经打开的activty的界面时,
新打开的activity的状态会丢失。当时,当我直接按home减将acitvity置于后台,然后重新打开这个activity的时候,发现此时的activity的状态是退出之前的状态。但是,我现在我想达到的目的是,不管是以什么方式打开这个活动,我都想要他恢复到之前的acitvity状态,而不是新开一个新的activity。于是针对这现象,我寻求解决方案。
利用onSaveInstanceState和onRestoreInstanceState
在发现这个问题之后,我的第一感觉就是利用activity中的onRestoreInstanceState方法在打开新的activity时将保存的数据读取并写入到新的activity中,实现的原理就是我之前说的按下home键重新打开activity能恢复到原来的activity状态的原理。在这里我将展开讲解activity中数据的保存机制。
activity的生命周期
要想完全理解activity中的储存机制,就必须对activity的生命周期非常熟悉,在这里我就简单介绍一下acttivity的生命周期。这里直接给出一张官方的activity生命周期图:
如果对activity的生命周期还是不太了解,可以参考我后面代码里面对每个activity周期的方法的注释。这里就不做过多的解释。
### acitvity中的数据储存机制
当一个activity被paused或者stopped时,activity的状态可以被保存。 的确如此,因为 Activity 对象在paused或者stopped时仍然被保留在内存之中——它所有的成员信息和当前状态都仍然存活。 这样用户在activity里所作的改动全都还保存着,所以当activity返回到前台时(当它“resume“),那些改动仍然有效。
不过,如果系统是为了回收内存而销毁activity,则这个 Activity 对象就会被销毁,这样系统就无法简单地resume一下就能还原完整状态的activity。 如果用户要返回到这个activity的话,系统必须重新创建这个Activity 对象。可是用户并不知道系统是先销毁activity再重新创建了它的,所以,他很可能希望activity完全保持原样。 这种情况下,你可以保证activity状态的相关重要信息都由另一个回调方法保存下来了,此方法让你能保存activity状态的相关信息: onSaveInstanceState()。
在activity变得很容易被销毁之前,系统会调用 onSaveInstanceState()方法。 调用时系统会传入一个Bundle对象, 你可以利用 putString() 之类的方法,以键值对的方式来把activity状态信息保存到该Bundle对象中。 然后,如果系统杀掉了你的application进程并且用户又返回到你的activity,系统就会重建activity并将这个 Bundle 传入onCreate() 和onRestoreInstanceState() 中,你就可以从 Bundle 中解析出已保存信息并恢复activity状态。如果没有储存状态信息,那么传入的 Bundle 将为null(当activity第一次被创建时就是如此)
这里在给出官方的图片来说明activity状态的存储
或许这个能更加直观的表现 onSaveInstanceState()和onRestoreInstanceState()的调用
但是注意,这里的activity数据保存的情况只能是activity被==意外==的销毁了,而被认为是用户主动销毁的activity则不会保存数据则不会调用这些保存数据的方法。
那么哪些是==意外==销毁activity的情况?
* 用户按下HOME键的时候
* 长安HOME键,运行其他程序
* 按下电源键(关闭屏幕显示)
* 从一个activity A中启动一个新的activity
* 屏幕方向发生改变
所以在上述情况下,activity会默认调用onSaveInstanceState()来保存数据,至于调用顺序,请参照前面的图片。、
讲到这里,就不得不说说重写onSaveInstanceState()方法了。
如果需要保存额外的数据时, 就需要覆写onSaveInstanceState()方法。大家需要注意的是:onSaveInstanceState()方法只适合保存瞬态数据, 比如UI控件的状态, 成员变量的值等,而不应该用来保存持久化数据,持久化数据应该当用户离开当前的 activity时,在 onPause() 中保存(比如将数据保存到数据库或文件中)。说到这里,还要说一点的就是在onPause()中不适合用来保存比较费时的数据,所以这点要理解。
由于onSaveInstanceState()方法方法不一定会被调用, 因此不适合在该方法中保存持久化数据, 例如向数据库中插入记录等. 保存持久化数据的操作应该放在onPause()中。若是永久性值,则在onPause()中保存;若大量,则另开线程吧,别阻塞UI线程。
讲到这里,好像有点明了,那么只要我们在activity中显示的调用onSaveInstanceState(),不久可以很好的解决这个问题么,于是我写了如下的代码,这里是MainActivity的内容。
package com.create.activitydata;
import android.content.Intent;
import android.os.PersistableBundle;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;
/**
* 测试保存Activity中的数据
*/
public class MainActivity extends AppCompatActivity {
private EditText editText;
private CheckBox checkBox;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = (EditText)findViewById(R.id.text);
checkBox = (CheckBox)findViewById(R.id.check);
}
/**
* activity被停止后、又再次被启动之前调用。
下一个回调方法总是onStart()
*/
@Override
protected void onRestart() {
//这里可以手动添加读取Activity的UI状态代码
super.onRestart();
}
/**
* activity要显示给用户之前调用。
*如果activity进入前台,则下一个回调方法是onResume();
*如果进入隐藏状态,则下一个回调方法是onStop()。
*/
@Override
protected void onStart() {
super.onStart();
}
/**
* activity开始与用户交互之前调用。这时activity是在activity栈的顶端,用户可以向其中输入。
*下一个回调方法总是onPause()。
*/
@Override
protected void onResume() {
super.onResume();
}
/**
* 当系统准备启动另一个正在恢复的activity时调用。这个方法通常用于把未保
* 存的改动提交为永久数据、停止动画播放、以及其它可能消耗CPU的工作等等。 它应该非常迅速
* 地完成工作,因为下一个activity在本方法返回前是不会被恢复运行的。
* 如果activity返回前台,则下一个回调方法是onResume();如果进入用户不可见状态,则下一个是onStop()
*/
@Override
protected void onPause() {
super.onPause();
//在这里可以添加比较耗时,需要长久保存的数据。
}
/**
* 当activity不再对用户可见时调用。原因可能是它即将被销毁、或者其它activity(已有或新建的)
* 被恢复运行并要覆盖本activity。
*如果activity还会回来与用户交互,则下一个回调方法是onRestart();如果这个activity即将消失,
*则下一个回调方法是onDestroy()
*/
@Override
protected void onStop() {
//在这里添加保存Activity状态的代码
super.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
/**
* 读取数据
* 读取和储存Activity状态的两个方法并不在Activity生命周期中
* 但在Activity意外停止时系统会调用缺省的方法保存当前状态,并在
* 下次打开Activity是调用onSaveInstanceState以恢复到原来的状态
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.e(TAG,"onRestoreInstanceState");
}
/**
* 保存数据
* @param outState
* @param outPersistentState
*/
@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
Log.e(TAG,"onSaveInstanceState");
}
public void toNext(View view){
Intent intent = new Intent(MainActivity.this,NextActivity.class);
startActivity(intent);
}
}
但是在这里我想说,我试了很久也没能显示的调用onSaveInstanceState()这些方法,对activity里面的源代码并没有多大的理解,于是我放弃了用这种方法去恢复activity的状态
(ps,对了,在这里加一句,只有加了id的控件调用onSaveInstanceState()时才能正常保存,否则也会失败,至于如何使这个方法失效,可以通过把android:saveEnabled 设置为”false”,或者调用 setSaveEnabled() 方法)
利用activity的启动方式
在上面的方法失败之后,我又需求别人的帮助。给出的思路就是,当利用Intent开启一个已经被开启过的activity时,没必要再重新new一个activity了,而是直接打开之前的那么activity。想到这,焕然大悟:这个我可以直接利用activity的启动方式来时事当前的activity一直处于栈顶,当开启他的时候,直接打开它而不是new一个activity。这样就完美的解决了我的问题。仅仅一行代码。
首先activity的启动模式只有四种:
* standard
standard是活动默认的启动模式,在不进行显式指定的情况下,所有活动都会自动使用 这种启动模式。在 standard模式(即默认情 况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用 standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建 该活动的一个新的实例。
* singleTop
当活动的启动模式 指定为 singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用 它,不会再创建新的活动实例。
* singleTask
使用 singleTop模式可以很好地解决重复创建栈顶活动的问题,但是正如你在上一节所 看到的,如果该活动并没有处于栈顶的位置,还是可能会创建多个活动实例的。那么有没有 什么办法可以让某个活动在整个应用程序的上下文中只存在一个实例呢?这就要借助 singleTask模式来实现了。当活动的启动模式指定为 singleTask,每次启动该活动时系统首先 会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这 个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
* singleInstance
singleInstance模式的活动会启用一 个新的返回栈来管理这个活动(其实如果 singleTask模式指定了不同的 taskAffinity,也会启 动一个新的返回栈),在这种模式下会有一个单独的返回栈来管理这个活 动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实 例的问题。
留个漂亮的尾巴~