如何使用保存实例状态保存活动状态?

How can I save an activity state using the save instance state?

提问人:Bernard 提问时间:9/30/2008 最后编辑:Ramesh RBernard 更新时间:9/13/2022 访问量:902580

问:

我一直在研究 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);
  }
}

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

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

android android-activity 应用程序状态

评论

9赞 Trojan.ZBOT 12/29/2013
什么时候 savedInstanceState == null,什么时候不为 null ?
102赞 villoren 1/4/2014
正如你所说,你通过远离它(例如按后退键)来明确地破坏你的活动。实际上,使用此“savedInstanceState”的场景是 Android 销毁您的活动以进行重新创建。对于意图:如果您在活动运行时更改了手机的语言(因此需要加载项目中的不同资源)。另一种非常常见的情况是,当您将手机旋转到一边时,活动将重新创建并以横向显示。
18赞 Yaroslav Mytkalyk 2/8/2014
若要获取第二条消息,请在开发选项中启用“不保留活动”。按主页按钮并从最近使用中返回。
5赞 Syed Raza Mehdi 7/14/2015
这对 developer.android.com/training/basics/activity-lifecycle/ 很有帮助......
6赞 VahidHoseini 9/4/2016
你可以用: onSaveInstanceState(Bundle savedInstanceState)

答:

457赞 Dave L. 9/30/2008 #1

仅用于保存与 Activity 的当前实例关联的状态,例如当前导航或选择信息,以便在 Android 销毁并重新创建 Activity 时,它可以像以前一样恢复。请参阅 onCreateonSaveInstanceState 的文档savedInstanceState

对于更长期的状态,请考虑使用 SQLite 数据库、文件或首选项。请参阅保存持久状态

评论

5赞 Trojan.ZBOT 12/30/2013
什么时候 savedInstanceState == null,什么时候不为 null ?
7赞 Gabriel Câmara 1/15/2014
当系统创建 Activity 的新实例时,savedInstanceState 为 null,而在还原时则不为 null。
8赞 ToolmakerSteve 9/19/2015
...这就提出了一个问题,即系统何时需要创建一个新的 Activity 实例。退出应用程序的某些方法不会创建捆绑包,因此必须创建一个新实例。这是根本问题;这意味着不能依赖 bundle 的存在,而必须采取一些替代的持久存储方式。onSave/onRestoreInstanceState 的好处是,它是一种系统可以突然执行的机制,而不会消耗太多系统资源。因此,最好支持这一点,并拥有持久存储,以便更优雅地退出应用程序。
2724赞 Reto Meier 9/30/2008 #2

您需要重写要更改的应用程序状态值并将其写入参数,如下所示: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");
}

或者来自片段。

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(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");
}

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

评论

30赞 Adam Jack 11/19/2009
这有可能在手机上工作,但在模拟器中不起作用吗?我似乎无法获得非空 savedInstanceState。
513赞 Jonathan Schneider 4/14/2011
注意:在将值添加到 Bundle 之前,您需要调用 super.onSaveInstanceState(savedInstanceState),否则它们将在该调用中被清除 (Droid X Android 2.2)。
131赞 schlingel 6/19/2012
注意:官方文档指出,您应该将重要信息保存在 onPause-Method 中,因为 onsaveinstance-method 不是 android 生命周期的一部分。developer.android.com/reference/android/app/Activity.html
39赞 chakrit 7/5/2013
这一事实实际上使得除了屏幕方向改变的情况外,几乎毫无用处。在几乎所有其他情况下,您永远不能依赖它,并且需要手动将 UI 状态保存在其他地方。或者通过重写 BACK 按钮行为来防止应用被终止。我不明白为什么他们一开始就这样实施它。完全不直观。而且你不能让系统给你的捆绑来保存东西,除非在这种非常特殊的方法中。onSaveInstanceState
18赞 Vicky Chijwani 6/25/2015
请注意,对于已分配 ID 的视图,会自动处理将 UI 状态保存/恢复到 Bundle 或从 Bundle 恢复 UI 状态。来自文档:“默认实现通过调用层次结构中具有 id 的每个视图,并保存当前聚焦视图的 id(所有这些都由onSaveInstanceStateonSaveInstanceState()onRestoreInstanceState(Bundle))"
103赞 Fedor 5/7/2010 #3

onSaveInstanceState当系统需要内存并终止应用程序时调用。当用户刚刚关闭应用程序时,不会调用它。所以我认为应用程序状态也应该保存在 .onPause

它应该保存到一些持久性存储中,如或SQLite。Preferences

评论

42赞 moveaway00 3/28/2012
对不起,这不太正确。onSaveInstanceState 在需要重新创建活动之前被调用。即每次用户旋转设备时。它用于存储瞬态视图状态。当 android 强制关闭应用程序时,onSaveInstanceState 实际上不会被调用(这就是为什么它对于存储重要的应用程序数据不安全)。但是,onPause 保证在活动被终止之前被调用,因此它应该用于在 preferences 或 Squlite 中存储永久信息。正确的答案,错误的理由。
46赞 u-foka 5/24/2010 #4

当 Activity 进入后台时,将调用 really。onSaveInstanceState()

引用文档: “此方法在活动可能被终止之前调用,以便当它在未来某个时候返回时,它可以恢复其状态。”

446赞 Steve Moseley 5/26/2010 #5

请注意,根据有关活动的文档,使用和持久性数据安全的。onSaveInstanceStateonRestoreInstanceState

该文档指出(在“活动生命周期”部分):

请注意,保存很重要 改为持久化数据 的,因为后者不是 生命周期回调,所以不会 如上所述,在每种情况下都应调用 在其文档中。onPause()onSaveInstanceState(Bundle)

换言之,将持久性数据的保存/恢复代码放入 和 !onPause()onResume()

如需进一步说明,以下是文档:onSaveInstanceState()

此方法在活动可能被终止之前调用,以便当它 在将来的某个时候回来,它可以恢复其状态。为 例如,如果活动 B 在活动 A 之前启动,并且在某些 点活动 A 被杀死以回收资源,活动 A 将有 有机会通过此保存其用户界面的当前状态 方法,以便当用户返回活动 A 时,状态 用户界面可以通过 或 恢复。onCreate(Bundle)onRestoreInstanceState(Bundle)

评论

58赞 Pontus Gagge 6/24/2010
吹毛求疵:这也不是不安全的。这只取决于你想保留什么以及保存多长时间,@Bernard在他最初的问题中并不完全清楚。InstanceState 非常适合保留当前 UI 状态(输入到控件中的数据、列表中的当前位置等),而暂停/恢复是长期持久存储的唯一可能性。
31赞 Felix 7/11/2010
这应该被否决。使用起来不安全(保存|Restore)InstanceState 类似于生命周期方法(即在其中执行除保存/恢复状态之外的任何其他操作)。它们非常适合保存/恢复状态。另外,您想如何在onPause和onResume中保存/恢复状态?在那些可以使用的方法中,你没有得到 Bundle,所以你必须在数据库、文件等中使用一些其他的状态保存,这是愚蠢的。
148赞 AZ_ 11/26/2010
我们不应该对这个人投反对票,至少他努力浏览了文档,我认为我们这些人在这里实际上建立了一个知识渊博的社区,并互相帮助,而不是投反对票。所以 1 投票支持努力,我会要求你们不要投反对票,而是投票赞成或不投票......这个人清除了人们在浏览文档时希望产生的困惑。1票赞成:)
21赞 GSree 1/5/2011
我不认为这个答案值得投反对票。至少他努力回答,并引用了doco的一节。
36赞 JBM 6/16/2011
这个答案是绝对正确的,值得向上投票,而不是向下投票!让我为那些没有看到它的人澄清一下各州之间的区别。GUI 状态(如选定的单选按钮和输入字段中的某些文本)远不如数据状态重要,例如添加到 ListView 中显示的列表中的记录。后者必须存储在 onPause 中的数据库中,因为它是唯一有保证的调用。如果将其放在 onSaveInstanceState 中,则在未调用该实例时可能会丢失数据。但是,如果出于同样的原因没有保存单选按钮选择 - 这没什么大不了的。
223赞 Martin Belcher - AtWrk 8/27/2010 #6

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

本文介绍了三种方法:

使用实例状态捆绑包在应用程序生命周期内(即临时)存储局部变量/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();
}

使用保留的非配置实例在应用程序生存期内的活动之间保持对象实例在内存中处于活动状态

[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();
}

评论

3赞 Tom 11/27/2012
@MartinBelcher-Eigo 文章谈到 SharedPreferences 中的数据时说:“这些数据被写入设备上的数据库。我相信数据存储在文件系统应用程序目录中的文件中。
2赞 MaciejGórski 5/2/2013
@Tom SharefPrefs 数据写入 xml 文件。xml是一种数据库吗?我会说这是;)
78赞 Mike A. 6/24/2011 #7

就我而言,保存状态充其量只是一个麻烦。如果需要保存持久性数据,只需使用 SQLite 数据库即可。Android 让 SOOO 变得简单。

像这样的东西:

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;

评论

9赞 Tom 11/27/2012
因为加载 SQLite 数据库需要很长时间,考虑到这是向用户显示应用程序 UI 的关键路径。我实际上没有计时,所以我很高兴得到纠正,但加载和打开数据库文件肯定不会很快吗?
5赞 Noumenon 5/4/2013
非常感谢您提供解决方案,新手可以剪切并粘贴到他们的应用程序中并立即使用!@Tom 就速度而言,存储 1000 对大约需要 7 秒,但您可以在 AsyncTask 中完成。但是,您需要添加一个 finally { cursor.close() },否则它会在执行此操作时因内存泄漏而崩溃。
3赞 Stephen Tetreault 11/1/2014
我遇到了这个,虽然它看起来很整洁,但我犹豫是否要尝试在 Google Glass 上使用它,这是我最近正在使用/使用的设备。
79赞 David 6/28/2011 #8

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

  1. 用户终止应用程序并在以后重新打开它,但应用程序需要重新加载上一个会话中的数据 - 这需要一种持久存储方法,例如使用 SQLite。
  2. 用户切换应用程序,然后返回到原始应用程序,并希望从他们离开的地方继续 - 保存和恢复捆绑包数据(例如应用程序状态数据),并且通常就足够了。onSaveInstanceState()onRestoreInstanceState()

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

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

50赞 User 1/18/2012 #9

onSaveInstanceState()对于瞬态数据(在 / 中还原),对于持久性数据(在 中还原)。 来自 Android 技术资源:onCreate()onRestoreInstanceState()onPause()onResume()

onSaveInstanceState() 在 Activity 被停止时被 Android 调用,并且可能会在恢复之前被终止!这意味着,当活动重新启动时,它应存储重新初始化为相同条件所需的任何状态。它是 onCreate() 方法的对应物,事实上,传递给 onCreate() 的 savedInstanceState Bundle 与您在 onSaveInstanceState() 方法中构造为 outState 的 Bundle 相同。

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

66赞 roy mathew 2/5/2012 #10

我想我找到了答案。让我用简单的话来讲述我所做的事情:

假设我有两个活动,activity1 和 activity2,我正在从 activity1 导航到 activity2(我在 activity2 中做了一些工作),然后通过单击 activity1 中的按钮再次返回活动 1。现在,在这个阶段,我想回到 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。

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

评论

2赞 Stephen Tetreault 11/1/2014
@bagusflyer注意更具体一点???您的评论没有帮助,没有人可以基于此为您提供帮助。
2赞 ToolmakerSteve 9/27/2015
这是对不同情况的回答:同一应用程序中的两个活动。 OP 是关于离开应用程序(例如主页按钮,或其他切换到其他应用程序的方式)。
1赞 Pamela Sillah 10/3/2020
这正是我一直在寻找的答案!
40赞 stefan bachert 3/31/2012 #11

同时,我一般不再使用

Bundle savedInstanceState & Co

对于大多数活动来说,生命周期过于复杂且没有必要。

谷歌自己说,它甚至不可靠。

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

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

在某种程度上,SharedPreferences 的工作方式类似于 Bundles。 当然,首先必须从首选项中读取这些值。

对于复杂数据,您可以使用SQLite而不是使用首选项。

应用此概念时,活动只会继续使用上次保存的状态,无论它是初始打开并中间重新启动,还是由于后退堆栈而重新打开。

31赞 Mahorad 9/5/2012 #12

和方法对于仅在旋转屏幕(方向更改)时的数据持久性很有用。
在应用程序之间切换时,它们甚至都不好(因为该方法被调用,但不会再次调用。
要获得更多持久性,请使用共享首选项。阅读此文章
onSaveInstanceState(bundle)onRestoreInstanceState(bundle)onSaveInstanceState()onCreate(bundle)onRestoreInstanceState(bundle)

评论

2赞 Vicky Chijwani 6/25/2015
在您的情况下,并且没有被调用,因为当您切换应用程序时根本没有被销毁,因此无需恢复任何内容。Android 调用以防 Activity 稍后被销毁(在旋转屏幕时会 100% 确定地发生这种情况,因为整个设备配置已更改,并且必须从头开始重新创建 Activity)。onCreateonRestoreInstanceStateActivityonSaveInstanceState
159赞 Mike Repass 10/20/2012 #13

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

  • 有一个微妙的 Android 框架错误,它使开发过程中的应用程序堆栈管理变得非常复杂,至少在旧版本上是这样(不完全确定它是否/何时/如何修复)。我将在下面讨论这个错误。
  • 管理此问题的“正常”或预期方法本身相当复杂,因为 onPause/onResume 和 onSaveInstanceState/onRestoreInstanceState 的双重性

浏览所有这些线程,我怀疑开发人员大部分时间都在同时谈论这两个不同的问题......因此,所有关于“这对我不起作用”的混乱和报告。

首先,澄清“预期”行为:onSaveInstance 和 onRestoreInstance 是脆弱的,仅适用于瞬态。预期用途(据我所知)是在手机旋转(方向更改)时处理活动再创建。换言之,预期用途是当您的 Activity 在逻辑上仍处于“顶部”,但仍必须由系统重新实例化时。保存的 Bundle 不会持久化在进程/内存/GC 之外,因此如果您的活动进入后台,您就无法真正依赖它。是的,也许你的 Activity 的记忆会在后台运行并逃脱 GC,但这并不可靠(也无法预测)。

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

但是 - 有一个非常令人困惑的错误使这一切复杂化。详情如下:

基本上,如果您的应用程序是使用 SingleTask 标志启动的,然后您稍后从主屏幕或启动器菜单启动它,那么后续调用将创建一个新任务......实际上,您的应用程序的两个不同实例将驻留在同一个堆栈中......这变得非常奇怪,非常快。当您在开发过程中(即从 EclipseIntelliJ)启动应用程序时,似乎会发生这种情况,因此开发人员经常会遇到这种情况。但也通过一些应用商店更新机制(所以它也会影响你的用户)。

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

Home 按键行为

2013 年 6 月更新:几个月后,我终于找到了“正确”的解决方案。您无需自行管理任何有状态的 startedApp 标志。您可以从框架中检测到这一点并适当地保释。我在 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;
    }
}
22赞 torwalker 4/17/2014 #14

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

mySavedInstanceState=savedInstanceState;

并在需要时使用它来获取变量的内容,如下所示:

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

我使用上面建议的 and,但我想我也可以或者或者使用我的方法在变量更改时保存变量(例如使用onSaveInstanceStateonRestoreInstanceStateputBoolean)

34赞 Jared Kells 1/21/2015 #15

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

只有在以下情况下,系统才会使用状态包重新创建您的活动:

  • 配置更改,例如更改方向或电话语言,这可能需要创建新的活动实例。
  • 在操作系统销毁活动后,从后台返回到应用。

Android 会在内存压力下或长时间处于后台状态后销毁后台活动。

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

  • 当您按下后退按钮时,活动完成。重新启动应用程序是一个全新的实例。您根本没有从后台恢复。
  • 当您按下主页按钮或使用任务切换器时,活动将进入后台。导航回应用程序时,仅当必须销毁 Activity 时,才会调用 onCreate。

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

在“设置”->“开发人员选项”下有一个选项,称为“不保留活动”。启用后,Android 将始终销毁 Activity,并在后台重新创建它们。这是在开发时保持启用状态的好选择,因为它模拟了最坏的情况。(一个低内存设备一直在回收你的活动)。

其他答案很有价值,因为它们教你存储状态的正确方法,但我不觉得它们真的回答了为什么你的代码没有按照你预期的方式工作。

40赞 Jared Rummler 8/20/2015 #16

为了帮助减少样板,我使用以下命令并读取/写入以保存实例状态。interfaceclassBundle


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

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 许可证获得许可。

16赞 Krishna Satwaji Khandagale 12/18/2015 #17

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

  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); 

}
21赞 Kevin Cronly 1/26/2016 #18

尽管公认的答案是正确的,但有一种更快、更简单的方法可以使用名为 Icepick 的库在 Android 上保存 Activity 状态。Icepick 是一个注释处理器,它负责为您保存和恢复状态时使用的所有样板代码。

用 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

7赞 THANN Phearum 8/8/2016 #19

简单快速地解决这个问题就是使用IcePick

首先,在app/build.gradle

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

现在,让我们看看下面的这个例子,如何在活动中保存状态

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、Fragment 或任何需要在 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
}

评论

1赞 THANN Phearum 9/14/2016
@ralphspoon是的,它适用于 Fragment 和自定义视图。请查看示例代码。我编辑了我的答案。我建议你去官方文档这里 github.com/frankiesardo/icepick 找到更多的代码示例。
0赞 THANN Phearum 5/17/2019
@ChetanMehra是指自定义视图类,对吧?如果是自定义视图,我们可以覆盖 onSaveInstanceState 和 onRestoreInstanceState,就像上面的 CustomView 示例一样。
0赞 Chetan Mehra 5/17/2019
我的意思是视图类中的类对象,例如:类 CustomView 扩展 View{ @State ClassA a;} 或类 CustomView 扩展视图 { @ State Inner class{}}
0赞 Chetan Mehra 5/17/2019
@THANNPhearum 我应该把它作为另一个问题问吗?
0赞 THANN Phearum 5/17/2019
明白了。如果是这样,您的 ClassA 应该是可包裹的。正如它所提到的,它适用于 Activity、Fragment 或任何需要在 Bundle 上序列化其状态的对象
8赞 ComeIn 8/9/2016 #20

不确定我的解决方案是否不受欢迎,但我使用绑定服务来持久化 ViewModel 状态。是将其存储在服务的内存中,还是保留并从 SQLite 数据库中检索它取决于您的要求。这就是任何风格的服务所做的事情,它们提供诸如维护应用程序状态和抽象公共业务逻辑之类的服务。

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

这种方法为您提供了强制执行关注点分离设计原则的额外好处,因为所有应用程序业务逻辑都可以移动到服务中,从而减少跨多个视图的重复逻辑,并允许视图强制执行另一个重要的设计原则,即单一责任。

10赞 iamabhaykmr 9/28/2016 #21

若要获取存储在 中的活动状态数据,首先必须通过重写方法将数据保存在 savedInstanceState 中。onCreate()SaveInstanceState(Bundle savedInstanceState)

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

18赞 Mansuu.... 1/23/2017 #22

创建活动时,会调用它的 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
    }

将您的值放在 “outState” Bundle 对象中,例如 outState.putString(“key”,“Welcome Back”),并通过调用 super 进行保存。 当活动被销毁时,它的状态会保存在 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");
    }
12赞 samus 2/7/2017 #23

以下是史蒂夫·莫斯利(Steve Moseley)的回答(由工具制造商史蒂夫(Steve))的评论,该评论将事情置于正确的角度(在整个onSaveInstanceState与onPause,东部成本与西部成本传奇中)

@VVK - 我部分不同意。退出应用的某些方式不会触发 onSaveInstanceState (oSIS)。这限制了 oSIS 的实用性。其 值得支持,对于最少的操作系统资源,但如果应用程序想要 将用户返回到他们所处的状态,无论应用如何 退出时,有必要改用持久存储方法。我使用 onCreate 检查捆绑包,如果缺少,则检查持久性存储。这集中了决策。我可以 从崩溃或后退按钮退出或自定义菜单项退出中恢复,或者 回到屏幕用户在很多天后。– 工具制造商史蒂夫·九月 19 '15 在 10:38

12赞 Rafols 2/13/2018 #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,请添加默认值

5赞 M Abdul Sami 8/27/2018 #25

现在 Android 提供了用于保存状态的 ViewModel,您应该尝试使用它而不是 saveInstanceState。

评论

3赞 Vyacheslav Martynenko 10/5/2018
事实并非如此。来自文档:“与保存的实例状态不同,ViewModel 在系统启动的进程死亡期间被销毁。这就是为什么您应该将 ViewModel 对象与 onSaveInstanceState() (或其他一些磁盘持久性)结合使用,将标识符存储在 savedInstanceState 中,以帮助视图模型在系统死亡后重新加载数据。
0赞 Brill Pappin 12/14/2018
刚刚在后台更改权限时遇到了这种情况。
0赞 Zhar 12/25/2019
我同意,来自文档“如果您需要处理系统启动的进程死亡,您可能希望使用 onSaveInstanceState() 作为备份。
3赞 Rohit Singh 1/10/2019 #26

保存什么,不保存什么?

有没有想过为什么方向改变时会自动保存中的文本?好吧,这个答案是给你的。EditText

当 Activity 的实例被销毁并且系统重新创建新实例时(例如,配置更改)。它尝试使用一组旧活动状态(实例状态)的已保存数据重新创建它。

实例状态是存储在对象中的键值对的集合。Bundle

例如,默认情况下,System 将 View 对象保存在 Bundle 中。

  • 文本输入EditText
  • 滚动位置在 等。ListView

如果需要将另一个变量保存为实例状态的一部分,则应使用 OVERRIDE 方法。onSavedInstanceState(Bundle savedinstaneState)

例如,在 GameActivity 中int currentScore

保存数据时有关 onSavedInstanceState(Bundle savedinstaneState) 的更多详细信息

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);

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

因此,如果您忘记调用默认行为,就会出错 不起作用,即 EditText 中的文本不会保存。super.onSaveInstanceState(savedInstanceState);

选择哪个来恢复活动状态?

 onCreate(Bundle savedInstanceState)

onRestoreInstanceState(Bundle savedInstanceState)

这两种方法都获取相同的 Bundle 对象,因此在哪里编写还原逻辑并不重要。唯一的区别是,在方法中,您必须进行空检查,而在后一种情况下不需要它。其他答案已经有代码片段。您可以推荐它们。onCreate(Bundle savedInstanceState)

关于 onRestoreInstanceState(Bundle savedinstaneState) 的更多详细信息

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

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

始终调用,以便系统默认还原视图层次结构super.onRestoreInstanceState(savedInstanceState);

奖金

仅当用户打算返回 Activity 时,系统才会调用 。例如,您正在使用 App X,突然接到一个电话。移动到呼叫者应用,然后返回到应用 X。在这种情况下,将调用该方法。onSaveInstanceState(Bundle savedInstanceState)onSaveInstanceState(Bundle savedInstanceState)

但是,如果用户按下后退按钮,请考虑这一点。假设用户不打算返回 Activity,因此在这种情况下,系统不会调用。 重点是,在保存数据时,您应该考虑所有场景。onSaveInstanceState(Bundle savedInstanceState)

相关链接:

默认行为
演示 Android 官方文档

9赞 Sazzad Hissain Khan 6/1/2019 #27

Kotlin

您必须覆盖并存储和检索要持久化的变量onSaveInstanceStateonRestoreInstanceState

生命周期图

存储变量

public override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)

    // prepare variables here
    savedInstanceState.putInt("kInt", 10)
    savedInstanceState.putBoolean("kBool", true)
    savedInstanceState.putDouble("kDouble", 4.5)
    savedInstanceState.putString("kString", "Hello Kotlin")
}

检索变量

public override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)

    val myInt = savedInstanceState.getInt("kInt")
    val myBoolean = savedInstanceState.getBoolean("kBool")
    val myDouble = savedInstanceState.getDouble("kDouble")
    val myString = savedInstanceState.getString("kString")
    // use variables here
}
2赞 Sana Ebadi 9/21/2019 #28

您可以使用 和 For L From 。请参阅此参考:Live DataView Modelifecycle HandelJetPack

https://developer.android.com/topic/libraries/architecture/livedata

3赞 neelkanth_vyas 10/9/2019 #29

相反,您应该使用 ViewModel,它将保留数据直到活动生命周期。

4赞 IgniteCoders 10/21/2019 #30

有一种方法可以使 Android 在不实现任何方法的情况下保存状态。只需将以下行添加到活动声明中的清单中:

android:configChanges="orientation|screenSize"

它应如下所示:

<activity
    android:name=".activities.MyActivity"
    android:configChanges="orientation|screenSize">
</activity>

在这里,您可以找到有关此属性的更多信息。

建议让 Android 为您处理此问题,而不是手动处理。

评论

2赞 Rachid Boudjelida 1/8/2020
这与保存状态无关,您只是放弃方向更改,请记住,您的应用程序可以随时重新启动、暂停和恢复不同的事件
1赞 IgniteCoders 1/8/2020
这个答案是为那些想在方向改变时保存状态并希望避免理解和实现复杂方式的人准备的
0赞 Rachid Boudjelida 1/8/2020
很公平,我明白你的意思了,我认为大多数努力保存状态的人都在使用片段,因为活动实际上保存了 UI 组件的统计信息,只要它们有 ID,但片段更特殊,我使用过一次片段,但我再也不会使用它们保存实例统计信息处理起来很痛苦
3赞 Umut ADALI 12/10/2019 #31

现在,在视图模型中执行 2 种方式是有意义的。 如果要将第一个实例另存为已保存的实例: 您可以在视图模型中添加状态参数,如下所示 https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate#java

或者,您可以在视图模型中保存变量或对象,在这种情况下,视图模型将保留生命周期,直到活动被销毁。

public class HelloAndroidViewModel extends ViewModel {
   public Booelan firstInit = false;

    public HelloAndroidViewModel() {
        firstInit = false;
    }
    ...
}

public class HelloAndroid extends Activity {

  private TextView mTextView = null;
  HelloAndroidViewModel viewModel = ViewModelProviders.of(this).get(HelloAndroidViewModel.class);
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mTextView = new TextView(this);

    //Because even if the state is deleted, the data in the viewmodel will be kept because the activity does not destroy
    if(!viewModel.firstInit){
        viewModel.firstInit = true
        mTextView.setText("Welcome to HelloAndroid!");
    }else{
       mTextView.setText("Welcome back.");
    }

    setContentView(mTextView);
  }
}

评论

0赞 Zhar 12/24/2019
你是对的,但这个库仍在发布中,所以我认为我们应该等待......
4赞 Jamil Hasnine Tamim 6/14/2020 #32

Kotlin 解决方案:对于自定义类保存,您可以将类转换为字符串并使用转换来恢复它,对于单值保存和恢复,如下所示。以下示例适用于 和 :onSaveInstanceStateJSONGsonString, Double, Int, LongFragmentActivity

对于活动:

对于输入数据:saveInstanceState

override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)

        //for custom class-----
        val gson = Gson()
        val json = gson.toJson(your_custom_class)
        outState.putString("CUSTOM_CLASS", json)

        //for single value------
        outState.putString("MyString", stringValue)
        outState.putBoolean("MyBoolean", true)
        outState.putDouble("myDouble", doubleValue)
        outState.putInt("MyInt", intValue)
    }

恢复数据:

 override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)

    //for custom class restore
    val json = savedInstanceState?.getString("CUSTOM_CLASS")
    if (!json!!.isEmpty()) {
        val gson = Gson()
        testBundle = gson.fromJson(json, Session::class.java)
    }

  //for single value restore

   val myBoolean: Boolean = savedInstanceState?.getBoolean("MyBoolean")
   val myDouble: Double = savedInstanceState?.getDouble("myDouble")
   val myInt: Int = savedInstanceState?.getInt("MyInt")
   val myString: String = savedInstanceState?.getString("MyString")
 }

您也可以在“活动”中恢复它。onCreate

对于片段:

对于把类放进去:saveInstanceState

 override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        val gson = Gson()
        val json = gson.toJson(customClass)
        outState.putString("CUSTOM_CLASS", json)
    }

恢复数据:

 override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        //for custom class restore
        if (savedInstanceState != null) {
            val json = savedInstanceState.getString("CUSTOM_CLASS")
            if (!json!!.isEmpty()) {
                val gson = Gson()
                val customClass: CustomClass = gson.fromJson(json, CustomClass::class.java)
            }
        }

      // for single value restore
      val myBoolean: Boolean = savedInstanceState.getBoolean("MyBoolean")
      val myDouble: Double = savedInstanceState.getDouble("myDouble")
      val myInt: Int = savedInstanceState.getInt("MyInt")
      val myString: String = savedInstanceState.getString("MyString")
    }
3赞 i30mb1 10/31/2020 #33

在2020年,我们有一些变化:

如果您希望在进程终止并重新启动后恢复其状态,您可能需要使用“保存状态”功能。以前,您需要覆盖 : 和 中的两个方法。您还可以在方法中访问还原的状态。同样,在 中,方法可用(还原状态在 、 和 方法中可用)。ActivityActivityonSaveInstanceStateonRestoreInstanceStateonCreateFragmentonSaveInstanceStateonCreateonCreateViewonActivityCreated

从 AndroidX SavedState 1.0.0 开始,它是 AndroidX Activity 和 AndroidX Fragment 的依赖项,您可以访问 .您可以从 Activity/Fragment 中获取,然后注册您的 :SavedStateRegistrySavedStateRegistrySavedStateProvider

class MyActivity : AppCompatActivity() {

  companion object {
    private const val MY_SAVED_STATE_KEY = "MY_SAVED_STATE_KEY "
    private const val SOME_VALUE_KEY = "SOME_VALUE_KEY "
  }
    
  private lateinit var someValue: String
  private val savedStateProvider = SavedStateRegistry.SavedStateProvider {    
    Bundle().apply {
      putString(SOME_VALUE_KEY, someValue)
    }
  }
  
  override fun onCreate(savedInstanceState: Bundle?) {    
    super.onCreate(savedInstanceState)
    savedStateRegistry.registerSavedStateProvider(MY_SAVED_STATE_KEY, savedStateProvider)
    someValue = savedStateRegistry.consumeRestoredStateForKey(MY_SAVED_STATE_KEY)?.getString(SOME_VALUE_KEY) ?: ""
  }
  
}

如您所见,强制您将密钥用于数据。这可以防止您的数据被附加到同一 .此外,您还可以将 your 提取到另一个类,通过使用您想要的任何抽象来使其与您的数据一起工作,并以这种方式在您的应用程序中实现干净的保存状态行为。SavedStateRegistrySavedStateProviderActivity/FragmentSavedStateProvider

4赞 Yessy 1/4/2021 #34

使用 Android ViewModel 和 SavedStateHandle 持久化可序列化数据

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
        binding.setViewModel(new ViewModelProvider(this).get(ViewModel.class));
        binding.setLifecycleOwner(this);
        setContentView(binding.getRoot());
    }

    public static class ViewModel extends AndroidViewModel {

        //This field SURVIVE the background process reclaim/killing & the configuration change
        public final SavedStateHandle savedStateHandle;

        //This field NOT SURVIVE the background process reclaim/killing but SURVIVE the configuration change
        public final MutableLiveData<String> inputText2 = new MutableLiveData<>();


        public ViewModel(@NonNull Application application, SavedStateHandle savedStateHandle) {
            super(application);
            this.savedStateHandle = savedStateHandle;
        }
    }
}

在布局文件中

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="viewModel"
            type="com.xxx.viewmodelsavedstatetest.MainActivity.ViewModel" />
    </data>

    <LinearLayout xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">


        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:autofillHints=""
            android:hint="This field SURVIVE the background process reclaim/killing &amp; the configuration change"
            android:text='@={(String)viewModel.savedStateHandle.getLiveData("activity_main/inputText", "")}' />

        <SeekBar
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="100"
            android:progress='@={(Integer)viewModel.savedStateHandle.getLiveData("activity_main/progress", 50)}' />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="This field SURVIVE the background process reclaim/killing &amp; the configuration change"
            android:text='@={(String)viewModel.savedStateHandle.getLiveData("activity_main/inputText", "")}' />

        <SeekBar
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="100"
            android:progress='@={(Integer)viewModel.savedStateHandle.getLiveData("activity_main/progress", 50)}' />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="This field NOT SURVIVE the background process reclaim/killing but SURVIVE the configuration change"
            android:text='@={viewModel.inputText2}' />

    </LinearLayout>
</layout>

测试:

1. start the test activity
2. press home key to go home
3. adb shell kill <the test activity process>
4. open recent app list and restart the test activity
3赞 Mala Rupareliya 11/29/2021 #35

现在,您可以使用实时数据和生命周期感知组件

https://developer.android.com/topic/libraries/architecture/lifecycle

评论

2赞 Community 11/29/2021
您的答案可以通过额外的支持信息得到改进。请编辑以添加更多详细信息,例如引文或文档,以便其他人可以确认您的答案是正确的。您可以在帮助中心找到有关如何写出好答案的更多信息。
0赞 Nur 12/3/2021
虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接的页面发生更改,仅链接的答案可能会失效。- 从评论