提问



我一直在Android SDK平台上工作,有点不清楚如何保存应用程序的状态。因此,给出了Hello,Android示例的这种小型重新工具:


package com.android.hello;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class HelloAndroid extends Activity {

  private TextView mTextView = null;

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mTextView = new TextView(this);

    if (savedInstanceState == null) {
       mTextView.setText("Welcome to HelloAndroid!");
    } else {
       mTextView.setText("Welcome back.");
    }

    setContentView(mTextView);
  }
}


我认为这对于最简单的情况就足够了,但无论我如何离开应用程序,它总是会响应第一条消息。


我确信解决方案就像覆盖onPause或类似的东西一样简单,但我已经在文档中捅了大约30分钟左右并且没有发现任何明显的东西。

最佳参考


您需要覆盖onSaveInstanceState(Bundle savedInstanceState)并将要更改的应用程序状态值写入Bundle参数,如下所示:


@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
  super.onSaveInstanceState(savedInstanceState);
  // Save UI state changes to the savedInstanceState.
  // This bundle will be passed to onCreate if the process is
  // killed and restarted.
  savedInstanceState.putBoolean("MyBoolean", true);
  savedInstanceState.putDouble("myDouble", 1.9);
  savedInstanceState.putInt("MyInt", 1);
  savedInstanceState.putString("MyString", "Welcome back to Android");
  // etc.
}


Bundle本质上是一种存储NVP(名称 - 值对)映射的方式,它将被传递到onCreate()以及onRestoreInstanceState(),你可以在这里提取这样的值:


@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
  super.onRestoreInstanceState(savedInstanceState);
  // Restore UI state from the savedInstanceState.
  // This bundle has also been passed to onCreate.
  boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
  double myDouble = savedInstanceState.getDouble("myDouble");
  int myInt = savedInstanceState.getInt("MyInt");
  String myString = savedInstanceState.getString("MyString");
}


您通常会使用此技术来存储应用程序的实例值(选择,未保存的文本等)。

其它参考1


savedInstanceState仅用于保存与Activity的当前实例相关联的状态,例如当前导航或选择信息,因此如果Android销毁并重新创建Activity,则它可以像之前一样返回。参见onCreateonSaveInstanceState [119] [120]的文档


对于更长寿的状态,请考虑使用SQLite数据库,文件或首选项。见保存持久状态。[121]

其它参考2


请注意,根据有关Activity的文档, NOT 可以安全地使用onSaveInstanceStateonRestoreInstanceState 作为持久数据 http://developer.android.com/reference/android/app/Activity.html。[122]中的州


该文件说明(在Activity生命周期部分):



  请注意,保存很重要
  onPause()中的持久数据
  onSaveInstanceState(Bundle)
  因为后者不属于
  生命周期回调,所以不会
  在所描述的每种情况下都会召唤
  在其文件中。



换句话说,在onPause()onResume()中输入持久数据的保存/恢复代码!


编辑:有关进一步说明,请参阅onSaveInstanceState()文档:



  在激活某个Activity之前调用此方法,以便在此时执行
  在未来的某个时候,它可以恢复其状态。对于
  例如,如果ActivityB在ActivityA前面,在某些Activity中发布
  点ActivityA被杀死以回收资源,ActivityA将有
  有机会通过它保存其用户界面的当前状态
  方法使用户返回ActivityA时的状态
  用户界面可以通过onCreate(Bundle)
  onRestoreInstanceState(Bundle)


其它参考3


我的同事写了一篇文章解释Android设备上的应用程序状态,包括对Activity生命周期和状态信息的解释,如何存储状态信息,以及保存到状态BundleSharedPreferences并在这里查看。[123]


本文介绍了三种方法:


使用实例状态包

存储应用程序生命周期(即临时)的本地变量/UI控制数据

[Code sample – Store State in State Bundle]
@Override
public void onSaveInstanceState(Bundle savedInstanceState) 
{
  // Store UI state to the savedInstanceState.
  // This bundle will be passed to onCreate on next call.  EditText txtName = (EditText)findViewById(R.id.txtName);
  String strName = txtName.getText().toString();

  EditText txtEmail = (EditText)findViewById(R.id.txtEmail);
  String strEmail = txtEmail.getText().toString();

  CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);
  boolean blnTandC = chkTandC.isChecked();

  savedInstanceState.putString(“Name”, strName);
  savedInstanceState.putString(“Email”, strEmail);
  savedInstanceState.putBoolean(“TandC”, blnTandC);

  super.onSaveInstanceState(savedInstanceState);
}


使用共享首选项

在应用程序实例之间(即永久地)存储本地变量/UI控制数据

[Code sample – Store State in SharedPreferences]
@Override
protected void onPause() 
{
  super.onPause();

  // Store values between instances here
  SharedPreferences preferences = getPreferences(MODE_PRIVATE);
  SharedPreferences.Editor editor = preferences.edit();  // Put the values from the UI
  EditText txtName = (EditText)findViewById(R.id.txtName);
  String strName = txtName.getText().toString();

  EditText txtEmail = (EditText)findViewById(R.id.txtEmail);
  String strEmail = txtEmail.getText().toString();

  CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);
  boolean blnTandC = chkTandC.isChecked();

  editor.putString(“Name”, strName); // value to store
  editor.putString(“Email”, strEmail); // value to store
  editor.putBoolean(“TandC”, blnTandC); // value to store    
  // Commit to storage
  editor.commit();
}


使用保留的非配置实例

,在应用程序生命周期内的Activity之间保持对象实例在内存中存活

[Code sample – store object instance]
private cMyClassType moInstanceOfAClass;// Store the instance of an object
@Override
public Object onRetainNonConfigurationInstance() 
{
  if (moInstanceOfAClass != null) // Check that the object exists
      return(moInstanceOfAClass);
  return super.onRetainNonConfigurationInstance();
}

其它参考4


这是Android开发的经典问题。这里有两个问题:



  • 有一个微妙的Android Framework错误,它使开发期间的应用程序堆栈管理变得非常复杂,至少在旧版本上是这样(不完全确定是否/何时/如何修复)。我将在下面讨论这个错误。

  • 管理此问题的正常或预期方式本身相当复杂,具有onPause/onResume和onSaveInstanceState/onRestoreInstanceState的二元性



浏览所有这些线程,我怀疑开发人员大多数时间同时讨论这两个不同的问题......因此所有混淆和报告这对我来说并不适用。


首先,澄清预期行为:onSaveInstance和onRestoreInstance是脆弱的,仅适用于瞬态。预期用途(afaict)用于在电话旋转(方向改变)时处理Activity娱乐。换句话说,预期的用法是当您的Activity仍然在逻辑上在顶部时,但仍然必须由系统重新实例化。保存的Bundle不会在进程/memory/gc之外保留,因此如果您的Activity进入后台,则无法真正依赖它。是的,也许你的Activity的记忆将在它的背景之旅中存活并逃脱GC,但这不可靠(也不可预测)。


因此,如果您的应用程序启动之间存在有意义的用户进度或状态,则指导是使用onPause和onResume。您必须自己选择并准备持久性商店。


但是 - 有一个非常混乱的错误使所有这一切变得复杂。细节在这里:


http://code.google.com/p/android/issues/detail?id=2373[124]


http://code.google.com/p/android/issues/detail?id=5277[125]


基本上,如果您的应用程序是使用SingleTask标志启动的,然后您从主屏幕或启动器菜单启动它,那么后续调用将创建一个新任务...您将有效地拥有两个不同的应用实例居住在相同的堆栈中...这非常非常快。这似乎发生在您在开发过程中启动您的应用程序时(即从Eclipse或Intellij),因此开发人员遇到了很多。但也通过一些应用程序商店更新机制(因此它也会影响您的用户)。


在我意识到我的主要问题是这个错误,而不是预期的框架行为之前,我在这些线程中奋战了几个小时。一个很好的写法和解决方法(更新:见下文)似乎来自用户@kaciula在这个答案中:


家庭按键行为


2013年6月更新:几个月后,我终于找到了正确的解决方案。您不需要自己管理任何有状态的startupApp标记,您可以从框架中检测到这一点并适当地保释。我在LauncherActivity.onCreate的开头附近使用它:


if (!isTaskRoot()) {
    Intent intent = getIntent();
    String action = intent.getAction();
    if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {
        finish();
        return;
    }
}

其它参考5


当系统需要内存并杀死应用程序时,会调用onSaveInstanceState。用户刚刚关闭应用程序时不会调用它。所以我认为应用程序状态也应该保​​存在onPause中。它应该保存到某些持久存储器,如PreferencesSqlite

其它参考6


这两种方法都是有用且有效的,并且最适合不同的场景:



  1. 用户终止应用程序并在以后重新打开它,但应用程序需要从上一个会话重新加载数据 - 这需要持久的存储方法,例如使用SQLite。

  2. 用户切换应用程序然后回到原始状态并想要从中断处继续 - 在onSaveInstanceState()onRestoreInstanceState()中保存和恢复捆绑数据(例如应用程序状态数据)足够的。



如果以持久方式保存状态数据,则可以在onResume()onCreate()中重新加载(或实际在任何生命周期调用中)。这可能是也可能不是期望的行为。如果将它存储在InstanceState中的一个包中,则它是瞬态的,仅适用于存储用于同一用户会话的数据(我使用术语会话松散)但不适用于会话之间。


并不是说一种方法比另一种方法更好,就像所有方法一样,了解您需要的行为并选择最合适的方法非常重要。

其它参考7


就我而言,保存状态至关重要。如果你需要保存持久数据,只需使用SQLite数据库.Android使 SOOO 变得容易。[[[127]


像这样的东西:


import java.util.Date;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class dataHelper {

    private static final String DATABASE_NAME = "autoMate.db";
    private static final int DATABASE_VERSION = 1;

    private Context context;
    private SQLiteDatabase db;
    private OpenHelper oh ;

    public dataHelper(Context context) {
        this.context = context;
        this.oh = new OpenHelper(this.context);
        this.db = oh.getWritableDatabase();
    }

    public void close()
    {
        db.close();
        oh.close();
        db = null;
        oh = null;
        SQLiteDatabase.releaseMemory();
    }


    public void setCode(String codeName, Object codeValue, String codeDataType)
    {
        Cursor codeRow = db.rawQuery("SELECT * FROM code WHERE codeName = '"+  codeName + "'", null);
        String cv = "" ;

        if (codeDataType.toLowerCase().trim().equals("long") == true)
        {
            cv = String.valueOf(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("int") == true)
        {
            cv = String.valueOf(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("date") == true)
        {
            cv = String.valueOf(((Date)codeValue).getTime());
        }
        else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
        {
            String.valueOf(codeValue);
        }
        else
        {
            cv = String.valueOf(codeValue);
        }

        if(codeRow.getCount() > 0) //exists-- update
        {
            db.execSQL("update code set codeValue = '" + cv +
                "' where codeName = '" + codeName + "'");
        }
        else // does not exist, insert
        {
            db.execSQL("INSERT INTO code (codeName, codeValue, codeDataType) VALUES(" +
                    "'" + codeName + "'," +
                    "'" + cv + "'," +
                    "'" + codeDataType + "')" );
        }
    }

    public Object getCode(String codeName, Object defaultValue)
    {
        //Check to see if it already exists
        String codeValue = "";
        String codeDataType = "";
        boolean found = false;
        Cursor codeRow  = db.rawQuery("SELECT * FROM code WHERE codeName = '"+  codeName + "'", null);
        if (codeRow.moveToFirst())
        {
            codeValue = codeRow.getString(codeRow.getColumnIndex("codeValue"));
            codeDataType = codeRow.getString(codeRow.getColumnIndex("codeDataType"));
            found = true;
        }

        if (found == false)
        {
            return defaultValue;
        }
        else if (codeDataType.toLowerCase().trim().equals("long") == true)
        {
            if (codeValue.equals("") == true)
            {
                return (long)0;
            }
            return Long.parseLong(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("int") == true)
        {
            if (codeValue.equals("") == true)
            {
                return (int)0;
            }
            return Integer.parseInt(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("date") == true)
        {
            if (codeValue.equals("") == true)
            {
                return null;
            }
            return new Date(Long.parseLong(codeValue));
        }
        else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
        {
            if (codeValue.equals("") == true)
            {
                return false;
            }
            return Boolean.parseBoolean(codeValue);
        }
        else
        {
            return (String)codeValue;
        }
    }


    private static class OpenHelper extends SQLiteOpenHelper {

        OpenHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE IF  NOT EXISTS code" +
            "(id INTEGER PRIMARY KEY, codeName TEXT, codeValue TEXT, codeDataType TEXT)");
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        }
    }
}


之后是一个简单的电话


dataHelper dh = new dataHelper(getBaseContext());
String status = (String) dh.getCode("appState", "safetyDisabled");
Date serviceStart = (Date) dh.getCode("serviceStartTime", null);
dh.close();
dh = null;

其它参考8


我想我找到了答案。让我用简单的话说出我的所作所为:


假设我有两个Activity,activity1和activity2,我从activity1导航到activity2(我已在activity2中完成了一些工作),再次通过单击activity1中的按钮返回到Activity1。现在在这个阶段我想回到activity2,我想在上次离开activity2时看到我的activity2处于相同的状态。


对于上面的场景,我所做的是在清单中我做了一些像这样的更改:


<activity android:name=".activity2"
          android:alwaysRetainTaskState="true"      
          android:launchMode="singleInstance">
</activity>


在按钮点击事件的activity1中我做了这样的事情:


Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.setClassName(this,"com.mainscreen.activity2");
startActivity(intent);


在按钮点击事件的activity2中我做了这样的事情:


Intent intent=new Intent();
intent.setClassName(this,"com.mainscreen.activity1");
startActivity(intent);


现在将会发生的事情是,我们在activity2中所做的任何更改都不会丢失,我们可以在与之前离开的状态相同的状态下查看activity2。


我相信这是答案,这对我来说很好。如果我错了,请纠正我。

其它参考9


onSaveInstanceState()用于瞬态数据(在onCreate()/onRestoreInstanceState()中恢复),onPause()用于持久数据(在onResume()中恢复)。
来自Android技术资源:



  如果Activity被停止并且可能在恢复之前被杀死,则Android会调用 onSaveInstanceState()!这意味着它应该存储重新启动Activity时重新初始化为相同条件所需的任何状态。它与onCreate()方法相对应,实际上传入onCreate()的savedInstanceState Bundle与onSaveInstanceState()方法中构造为outState的Bundle相同。

  
   onPause() onResume()也是免费方法。当Activity结束时总是调用onPause(),即使我们发起了这个(例如使用finish()调用)。我们将使用它将当前注释保存回数据库。好的做法是释放在onPause()期间可以释放的任何资源,以便在处于被动状态时占用更少的资源。


其它参考10


重新创建Activity


在某些情况下,您的Activity会因应用程序的正常行为而被销毁,例如当用户按下返回按钮或您的Activity通过调用finish()发出自己的破坏信号时。如果系统当前已停止并且长时间未使用或前台Activity需要更多资源,系统也可能会破坏您的Activity,因此系统必须关闭后台进程才能恢复内存。


当你的activity因为用户按下Back或activity结束而被销毁时,系统对Activity实例的概念将永远消失,因为行为表明不再需要Activity但是,如果系统由于系统约束(而不是正常的应用程序行为)而破坏Activity,那么虽然实际的Activity实例已经消失,但系统会记住它存在,如果用户导航回它,系统会创建一个Activity的新实例使用一组保存的数据来描述Activity状态destroyed。系统用来恢复先前状态的保存数据称为实例状态并且是一个集合存储在Bundle对象中的键值对。


要保存有关Activity状态的其他数据,必须覆盖onSaveInstanceState()回调方法。系统在用户离开您的Activity时调用此方法,并向其传递Bundle对象,该对象将在您的Activity意外销毁时保存。如果系统必须稍后重新创建Activity实例,它会将相同的Bundle对象传递给onRestoreInstanceState()onCreate()方法。
[128]


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


保存您的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) {
  // Save the user's current game state
  savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
  savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);

  // Always call the superclass so it can save the view hierarchy state
  super.onSaveInstanceState(savedInstanceState);
}


警告:始终调用onSaveInstanceState()的超类实现,以便默认实现可以保存视图层次结构的状态。


恢复您的Activity


在先前销毁Activity之后重新创建Activity时,您可以从系统传递Activity的Bundle中恢复已保存的状态。 onCreate()onRestoreInstanceState()回调方法都接收包含实例状态信息的相同Bundle


因为无论系统是创建Activity的新实例还是重新创建前一个实例,都会调用onCreate()方法,因此在尝试读取之前必须检查状态Bundle是否为null。如果它为null,则系统正在创建Activity的新实例,而不是恢复已销毁的先前实例。


例如,这里有如何在onCreate()中恢复某些状态数据:


 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState); // Always call the superclass first

 // Check whether we're recreating a previously destroyed instance
 if (savedInstanceState != null) {
    // Restore value of members from saved state
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
    mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
 } else {
    // Probably initialize members with default values for a new instance
 }

 }


您可以选择实现onRestoreInstanceState(),而不是在onCreate()期间恢复状态,系统在onStart()方法之后调用。仅当存在要恢复的已保存状态时,系统才会调用onRestoreInstanceState(),因此您无需检查Bundle是否为空:


  public void onRestoreInstanceState(Bundle savedInstanceState) {
  // Always call the superclass so it can restore the view hierarchy
  super.onRestoreInstanceState(savedInstanceState);

  // Restore state members from saved instance
  mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
  mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

其它参考11


当Activity进入后台时,真的onSaveInstance状态callen


从文档引用:
在将Activity置于这样的背景状态之前调用方法onSaveInstanceState(Bundle)

其它参考12


同时我一般不再使用


Bundle savedInstanceState & Co


对于大多数Activity而言,生活周期过于复杂且不必要。
谷歌声称自己,它甚至不可靠。


我的方法是立即保存首选项中的任何更改


 SharedPreferences p;
 p.edit().put(..).commit()


在某种程度上,SharedPreferences的工作方式与Bundles类似。
并且自然而且首先这些值必须是偏好的。


在复杂数据的情况下,您可以使用Sqlite而不是使用首选项。


应用此概念时,Activity将继续使用上次保存的状态,无论它是初始打开还是重新启动,或者由于后端堆栈而重新打开。

其它参考13


为了帮助减少样板,我使用以下interfaceclass来读取/写入Bundle以保存实例状态。





首先,创建一个用于注释实例变量的接口:


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
        ElementType.FIELD
})
public @interface SaveInstance {

}


然后,创建一个类,其中将使用反射将值保存到包中:


import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;

import java.io.Serializable;
import java.lang.reflect.Field;

/**
 * Save and load fields to/from a {@link Bundle}. All fields should be annotated with {@link
 * SaveInstance}.</p>
 */
public class Icicle {

    private static final String TAG = "Icicle";

    /**
     * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
     *
     * @param outState
     *         The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
     *         Fragment#onSaveInstanceState(Bundle)}
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @see #load(Bundle, Object)
     */
    public static void save(Bundle outState, Object classInstance) {
        save(outState, classInstance, classInstance.getClass());
    }

    /**
     * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
     *
     * @param outState
     *         The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
     *         Fragment#onSaveInstanceState(Bundle)}
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @param baseClass
     *         Base class, used to get all superclasses of the instance.
     * @see #load(Bundle, Object, Class)
     */
    public static void save(Bundle outState, Object classInstance, Class<?> baseClass) {
        if (outState == null) {
            return;
        }
        Class<?> clazz = classInstance.getClass();
        while (baseClass.isAssignableFrom(clazz)) {
            String className = clazz.getName();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(SaveInstance.class)) {
                    field.setAccessible(true);
                    String key = className + "#" + field.getName();
                    try {
                        Object value = field.get(classInstance);
                        if (value instanceof Parcelable) {
                            outState.putParcelable(key, (Parcelable) value);
                        } else if (value instanceof Serializable) {
                            outState.putSerializable(key, (Serializable) value);
                        }
                    } catch (Throwable t) {
                        Log.d(TAG, "The field '" + key + "' was not added to the bundle");
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

    /**
     * Load all saved fields that have the {@link SaveInstance} annotation.
     *
     * @param savedInstanceState
     *         The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @see #save(Bundle, Object)
     */
    public static void load(Bundle savedInstanceState, Object classInstance) {
        load(savedInstanceState, classInstance, classInstance.getClass());
    }

    /**
     * Load all saved fields that have the {@link SaveInstance} annotation.
     *
     * @param savedInstanceState
     *         The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @param baseClass
     *         Base class, used to get all superclasses of the instance.
     * @see #save(Bundle, Object, Class)
     */
    public static void load(Bundle savedInstanceState, Object classInstance, Class<?> baseClass) {
        if (savedInstanceState == null) {
            return;
        }
        Class<?> clazz = classInstance.getClass();
        while (baseClass.isAssignableFrom(clazz)) {
            String className = clazz.getName();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(SaveInstance.class)) {
                    String key = className + "#" + field.getName();
                    field.setAccessible(true);
                    try {
                        Object fieldVal = savedInstanceState.get(key);
                        if (fieldVal != null) {
                            field.set(classInstance, fieldVal);
                        }
                    } catch (Throwable t) {
                        Log.d(TAG, "The field '" + key + "' was not retrieved from the bundle");
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

}





用法示例:



public class MainActivity extends Activity {

    @SaveInstance
    private String foo;

    @SaveInstance
    private int bar;

    @SaveInstance
    private Intent baz;

    @SaveInstance
    private boolean qux;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Icicle.load(savedInstanceState, this);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Icicle.save(outState, this);
    }

}





注意:此代码改编自名为AndroidAutowire的图书馆项目,该项目根据MIT许可证获得许可。[129] [130]

其它参考14


直接回答原始问题。 savedInstancestate为null,因为永远不会重新创建Activity。


只有在以下情况下,才会使用州捆绑包重新创建您的Activity:



  • 配置更改,例如更改方向或手机语言,可能需要创建新的Activity实例。

  • 操作系统销毁Activity后,您将从后台返回应用程序。



Android会在内存压力下或在他们长时间处于后台后破坏后台Activity。


在测试hello world示例时,有几种方法可以离开并返回Activity。



  • 当您按后退按钮时,Activity结束。重新启动应用程序是一个全新的实例。你根本不会从后台恢复。

  • 当您按主页按钮或使用任务切换器时,Activity将进入后台。导航回应用程序时,只有在必须销毁Activity时才会调用onCreate。



在大多数情况下,如果您只是按下主页然后再次启动应用程序,则不需要重新创建Activity。它已经存在于内存中,因此onCreate()不会被调用。


在设置 - >开发人员选项下有一个选项,名为不要保持Activity。启用后,Android将始终销毁Activity,并在他们后退时重新创建它们。这是一个很好的选择,在开发时保持启用,因为它模拟最坏的情况。(低内存设备一直在回收您的Activity)。


其他答案是有价值的,因为他们教你正确的存储状态的方法,但我没有觉得他们真的回答为什么你的代码没有像你期望的那样工作。

其它参考15


onSaveInstanceState(bundle)onRestoreInstanceState(bundle)方法仅在旋转屏幕(方向更改)时对数据持久性有用。

它们在应用程序之间切换时甚至不好(因为onSaveInstanceState()方法被调用但onCreate(bundle)onRestoreInstanceState(bundle)不再被调用。

对于更多持久性,请使用共享首选项阅读这篇文章[131]

其它参考16


我的问题是我只在应用程序生命周期中需要持久性(即单个执行包括在同一个应用程序中启动其他子Activity并旋转设备等)。我尝试了上述答案的各种组合,但在所有情况下都没有得到我想要的东西。最后,对我有用的是在onCreate期间获取对savedInstanceState的引用:


mySavedInstanceState=savedInstanceState;


并在我需要时使用它来获取变量的内容,类似于:


if (mySavedInstanceState !=null) {
   boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}


我按照上面的建议使用onSaveInstanceStateonRestoreInstanceState,但我想我也可以或者使用我的方法在变量发生时保存变量(例如使用putBoolean)

其它参考17


尽管接受的答案是正确的,但是使用名为Icepick的库在Android上保存Activity状态的方法更快更容易。 Icepick是一个注释处理器,负责处理为您保存和恢复状态时使用的所有样板代码。 [132]


用Icepick做这样的事情:


class MainActivity extends Activity {
  @State String username; // These will be automatically saved and restored
  @State String password;
  @State int age;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Icepick.restoreInstanceState(this, savedInstanceState);
  }

  @Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Icepick.saveInstanceState(this, outState);
  }
}


这样做是一样的:


class MainActivity extends Activity {
  String username;
  String password;
  int age;

  @Override
  public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    savedInstanceState.putString("MyString", username);
    savedInstanceState.putString("MyPassword", password);
    savedInstanceState.putInt("MyAge", age); 
    /* remember you would need to actually initialize these variables before putting it in the
    Bundle */
  }

  @Override
  public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    username = savedInstanceState.getString("MyString");
    password = savedInstanceState.getString("MyPassword");
    age = savedInstanceState.getInt("MyAge");
  }
}


Icepick将使用任何以Bundle保存其状态的对象。

其它参考18


实现此更改基本上有两种方法。



  1. 使用onSaveInstanceState()onRestoreInstanceState()

  2. 在清单android:configChanges="orientation|screenSize"中。



我真的不建议使用第二种方法。因为在我的一次经验中,它导致设备屏幕的一半在从纵向旋转到横向时变黑,反之亦然。


使用上面提到的第一种方法,我们可以在方向更改或任何配置更改发生时保留数据。
我知道一种可以在savedInstance状态对象中存储任何类型数据的方法。


示例:如果要保留Json对象,请考虑一个案例。
使用getter和setter创建一个模型类。


class MyModel extends Serializable{
JSONObject obj;

setJsonObject(JsonObject obj)
{
this.obj=obj;
}

JSONObject getJsonObject()
return this.obj;
} 
}


现在在onCreate和onSaveInstanceState方法的Activity中执行以下操作。它看起来像这样:


@override
onCreate(Bundle savedInstaceState){
MyModel data= (MyModel)savedInstaceState.getSerializable("yourkey")
JSONObject obj=data.getJsonObject();
//Here you have retained JSONObject and can use.
}


@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Obj is some json object 
MyModel dataToSave= new MyModel();
dataToSave.setJsonObject(obj);
oustate.putSerializable("yourkey",dataToSave); 

}

其它参考19


创建Activity时,会调用onCreate()方法。


   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }


savedInstanceState是Bundle类的一个对象,它第一次为null,但在重新创建时包含值。要保存Activity的状态,您必须覆盖onSaveInstanceState()。


   @Override
    protected void onSaveInstanceState(Bundle outState) {
      outState.putString("key","Welcome Back")
        super.onSaveInstanceState(outState);       //save state
    }


将您的值放在outStateBundle对象中,如outState.putString(key,Welcome Back)并通过调用super来保存。
当Activity被销毁时,它的状态被保存在Bundle对象中,并且可以在onCreate()或onRestoreInstanceState()之后恢复。在onCreate()和onRestoreInstanceState()中接收的Bundle是相同的。


   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

          //restore activity's state
         if(savedInstanceState!=null){
          String reStoredString=savedInstanceState.getString("key");
            }
    }


要么


  //restores activity's saved state
 @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
      String restoredMessage=savedInstanceState.getString("key");
    }

其它参考20


以下是来自史蒂夫·莫斯利的回答(通过 ToolmakerSteve )的评论,这些评论将事情置于透视中(在整个onSaveInstanceState vs onPause,east cost vs west cost saga)



  @VVK - 我部分不同意。退出应用程序的一些方法不会触发
  onSaveInstanceState(oSIS)。这限制了oSIS的有用性。它的
  值得支持,最小的操作系统资源,但如果一个应用程序想要
  无论应用程序如何,都将用户返回到他们所处的状态
  退出时,有必要使用持久存储方法。
  我使用onCreate检查包,如果缺少,请检查
  持久存储。这使决策集中化。我可以
  从崩溃或后退按钮退出或自定义菜单项退出恢复,或
  多天后回到屏幕用户。 - ToolmakerSteve Sep
  19点15分10:38


其它参考21


不确定我的解决方案是否不赞成,但我使用绑定服务来保持ViewModel状态。是将它存储在服务的内存中还是保留并从SqlLite数据库中检索取决于您的要求。这是任何风格的服务,它们提供诸如维护应用程序状态和抽象通用业务逻辑之类的服务。


由于移动设备固有的内存和处理限制,我以类似于网页的方式处理Android视图。页面不维护状态,它纯粹是一个表示层组件,其唯一目的是呈现应用程序状态并接受用户输入。 Web应用程序体系结构的最新趋势采用了古老的模型,视图,控制器(MVC)模式,其中页面是视图,域数据是模型,控制器位于Web服务后面。在Android中可以使用相同的模式,View视图很好......视图,模型是您的域数据,Controller是作为Android绑定服务实现的。每当您希望视图与控制器交互时,在开始/恢复时绑定它并在停止/暂停时解除绑定。


这种方法为您提供了强制执行Separation of Concern设计原则的额外好处,因为您可以将所有应用程序业务逻辑移动到您的服务中,从而减少多个视图中的重复逻辑,并允许视图强制执行另一个重要的设计原则Single Responsibility。

其它参考22


简单快速解决这个问题就是使用IcePick [133]


首先,在app/build.gradle中设置库


repositories {
  maven {url "https://clojars.org/repo/"}
}
dependencies {
  compile 'frankiesardo:icepick:3.2.0'
  provided 'frankiesardo:icepick-processor:3.2.0'
}


现在,让我们在下面查看如何在Activity中保存状态的示例


public class ExampleActivity extends Activity {
  @State String username; // This will be automatically saved and restored

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Icepick.restoreInstanceState(this, savedInstanceState);
  }

  @Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Icepick.saveInstanceState(this, outState);
  }
}


它适用于Activity,碎片或需要在Bundle上序列化其状态的任何对象(例如迫击炮的ViewPresenters)


Icepick还可以为自定义视图生成实例状态代码:


class CustomView extends View {
  @State int selectedPosition; // This will be automatically saved and restored

  @Override public Parcelable onSaveInstanceState() {
    return Icepick.saveInstanceState(this, super.onSaveInstanceState());
  }

  @Override public void onRestoreInstanceState(Parcelable state) {
    super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state));
  }

  // You can put the calls to Icepick into a BaseCustomView and inherit from it
  // All Views extending this CustomView automatically have state saved/restored
}

其它参考23


要获取存储在onCreate()中的Activity状态数据,首先必须通过覆盖SaveInstanceState(Bundle savedInstanceState)方法将数据保存在savedInstanceState中。


当Activity销毁SaveInstanceState(Bundle savedInstanceState)方法被调用时,您将保存要保存的数据。当Activity重启时,你在onCreate()中得到相同的结果。(savedInstanceState不会为null,因为你在Activity被销毁之前已经保存了一些数据)

其它参考24


Kotlin代码:


保存:


override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState.apply {
        putInt("intKey", 1)
        putString("stringKey", "String Value")
        putParcelable("parcelableKey", parcelableObject)
    })
}


然后在onCreate()onRestoreInstanceState()


    val restoredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int
    val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
    val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable


如果您不想拥有Optionals,请添加默认值

其它参考25


现在Android提供ViewModel来保存状态,你应该尝试使用它而不是saveInstanceState。[134]