【问题标题】:如何使用保存实例状态保存活动状态?
【发布时间】:2010-09-14 04:27:51
【问题描述】:

我一直在 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 分钟左右,但没有发现任何明显的东西。

【问题讨论】:

  • 什么时候是 savedInstanceState == null 什么时候不是 null ?
  • 您明确地破坏了您的活动 - 正如您所说,导航远离它,例如按回。实际上,使用此“savedInstanceState”的场景是 Android 销毁您的活动以进行娱乐。例如:如果您在活动运行时更改手机的语言(因此需要加载项目中的不同资源)。另一种非常常见的情况是,当您将手机旋转到一边,以便重新创建活动并以横向显示。
  • 要获取第二条消息,请在开发选项中启用“不保留活动”。按下主页按钮并从最近访问中返回。
  • 你可以这样做:onSaveInstanceState(Bundle savedInstanceState)

标签: android android-activity application-state


【解决方案1】:

您需要覆盖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");
}

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

【讨论】:

  • 这在手机上是否可行,但在模拟器中不行?我似乎无法获得非空的 savedInstanceState。
  • 小心:在将值添加到 Bundle 之前,您需要调用 super.onSaveInstanceState(savedInstanceState),否则它们将在该调用中被清除(Droid X And​​roid 2.2)。
  • 小心:官方文档指出,您应该在 onPause-Method 中保存重要信息,因为 onsaveinstance-method 不是 android 生命周期的一部分。 developer.android.com/reference/android/app/Activity.html
  • 这一事实实际上使onSaveInstanceState 几乎毫无用处,除非只是在屏幕方向更改的情况下。在几乎所有其他情况下,您永远不能依赖它,并且需要手动将您的 UI 状态保存在其他地方。或者通过覆盖 BACK 按钮行为来防止您的应用程序被杀死。我不明白他们为什么一开始就这样实现它。完全不直观。除了这种非常特殊的方法,你不能让系统给你的那个 Bundle 来保存东西。
  • 请注意,对于已分配 ID 的Views,自动 将 UI 状态保存到 / 从 Bundle 中保存/恢复。来自onSaveInstanceState 文档:“默认实现通过在层次结构中具有 id 的每个视图上调用 onSaveInstanceState() 并保存当前聚焦视图的 id,为您处理大部分 UI 实例状态(全部由onRestoreInstanceState(Bundle)的默认实现恢复)"
【解决方案2】:

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

要获得更长久的状态,请考虑使用 SQLite 数据库、文件或首选项。见Saving Persistent State

【讨论】:

  • 什么时候是 savedInstanceState == null 什么时候不是 null ?
  • savedInstanceState 在系统正在创建您的 Activity 的新实例时为空,在恢复时不为空。
  • ...这引发了何时系统需要创建一个新的Activity实例的问题。退出应用程序的某些方法不会创建捆绑包,因此必须创建新实例。这是根本问题;这意味着不能依赖 bundle 的存在,而必须采用一些替代方法来持久存储。 onSave/onRestoreInstanceState 的好处是它是一种系统可以突然执行的机制,而不会消耗太多系统资源。因此,最好支持这一点,并拥有持久存储以便更优雅地退出应用程序。
【解决方案3】:

请注意,根据@987654321,将onSaveInstanceStateonRestoreInstanceState 用于持久数据安全的@

文档状态(在“活动生命周期”部分):

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

换句话说,将持久数据的保存/恢复代码放入onPause()onResume()

为了进一步说明,这里是onSaveInstanceState() 文档:

这个方法在一个activity可能被杀死之前被调用,这样当它被杀死时 未来一段时间回来,它可以恢复其状态。为了 例如,如果活动 B 在活动 A 之前启动,并且在某个 点activity A 被杀死以回收资源,activity A 将拥有 有机会通过这个保存其用户界面的当前状态 方法,以便当用户返回活动 A 时, 用户界面可以通过onCreate(Bundle)onRestoreInstanceState(Bundle).

【讨论】:

  • 只是吹毛求疵:它也不是不安全的。这仅取决于您要保留的内容和保留时间,@Bernard 在他最初的问题中并不完全清楚。 InstanceState 非常适合保存当前 UI 状态(输入控件的数据、列表中的当前位置等),而暂停/恢复是长期持久存储的唯一可能性。
  • 这应该被否决。使用 on(Save|Restore)InstanceState 之类的生命周期方法是不安全的(即在其中执行任何其他操作,而不是保存/恢复状态)。它们非常适合保存/恢复状态。另外,您想如何在 onPause 和 onResume 中保存/恢复状态?您无法在可以使用的那些方法中获得 Bundle,因此您必须在数据库、文件等中使用其他一些状态保存,这很愚蠢。
  • 我们不应该对这个人投反对票,至少他努力阅读了文档,我认为我们在这里实际上是为了建立一个知识渊博的社区并互相帮助而不是投反对票。所以 1 对这项工作投赞成票,我会要求你们不要投反对票,而是投赞成票或不投票……这个人清除了人们在阅读文档时想要的困惑。 1 票赞成 :)
  • 我认为这个答案不值得反对。至少他努力回答并引用了 doco 的一段话。
  • 这个答案绝对正确,值得投赞成票,而不是反对!让我为那些看不到它的人澄清州之间的区别。 GUI 状态,如选中的单选按钮和输入字段中的一些文本,远不如数据状态重要,如添加到 ListView 中显示的列表的记录。后者必须在 onPause 中存储到数据库中,因为它是唯一有保证的调用。如果你把它放在 onSaveInstanceState 中,如果不调用它,你就有丢失数据的风险。但是如果单选按钮选择没有因为同样的原因被保存 - 这没什么大不了的。
【解决方案4】:

我的同事写了一篇文章解释了Android设备上的应用程序状态,包括对Activity生命周期和状态信息的解释,如何存储状态信息,以及保存到状态BundleSharedPreferencesTake a look at it here.

本文介绍了三种方法:

存储应用程序生命周期的局部变量/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();
}

【讨论】:

  • @MartinBelcher-Eigo 文章谈到 SharedPreferences 中的数据时说“此数据已写入设备上的数据库。”我相信数据存储在应用程序目录中的文件中系统。
  • @Tom SharefPrefs 数据写入 xml 文件。 xml是一种数据库吗?我会说是;)
【解决方案5】:

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

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

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

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

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

但是 - 有一个非常令人困惑的错误使这一切变得复杂。详情在这里:

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

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

Home key press behaviour

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

【讨论】:

    【解决方案6】:

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

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

    【讨论】:

    • 抱歉,这不太正确。在需要重新制作活动之前调用 onSaveInstanceState。即每次用户旋转设备时。它用于存储瞬态视图状态。当 android 强制关闭应用程序时,实际上不会调用 onSaveInstanceState (这就是存储重要应用程序数据不安全的原因)。然而,onPause 保证在 Activity 被杀死之前被调用,因此它应该用于在首选项或 Squlite 中存储永久信息。正确的答案,错误的理由。
    【解决方案7】:

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

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

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

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

    【讨论】:

      【解决方案8】:

      就我而言,保存状态充其量只是一个杂项。如果您需要保存持久数据,只需使用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;
      

      【讨论】:

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

      我想我找到了答案。让我简单地说一下我做了什么:

      假设我有两个活动,活动 1 和活动 2,我正在从活动 1 导航到活动 2(我在活动 2 中做了一些工作),然后通过单击活动 1 中的按钮再次返回活动 1。现在在这个阶段,我想回到活动 2,我想看到我的活动 2 与我上次离开活动 2 时的状态相同。

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

      <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。

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

      【讨论】:

      • @bagusflyer 关心更具体???您的评论没有帮助,没有人可以据此为您提供帮助。
      • 这是对不同情况的回答:同一个应用程序中有两个活动。 OP 是关于离开应用程序(例如主页按钮,或切换到不同应用程序的其他方式)。
      • 这正是我正在寻找的答案!
      【解决方案10】:

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

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

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

      【讨论】:

        【解决方案11】:

        当 Activity 进入后台时,确实会调用 onSaveInstanceState()

        引用自文档: “这个方法在一个活动可能被杀死之前被调用,这样当它在未来某个时间回来时,它可以恢复它的状态。” Source

        【讨论】:

          【解决方案12】:

          为了帮助减少样板,我使用以下 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 license 下获得许可。

          【讨论】:

            【解决方案13】:

            同时我一般不会再使用

            Bundle savedInstanceState & Co
            

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

            而且谷歌自称,它甚至不可靠。

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

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

            在某种程度上,SharedPreferences 的工作方式与 Bundle 类似。 当然,首先必须从偏好中读取这些值。

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

            当应用这个概念时,Activity 只是继续使用上次保存的状态,不管它是初始打开并在其间重新启动,还是由于返回堆栈而重新打开。

            【讨论】:

              【解决方案14】:

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

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

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

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

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

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

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

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

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

              【讨论】:

                【解决方案15】:

                onSaveInstanceState(bundle)onRestoreInstanceState(bundle) 方法仅在旋转屏幕(方向更改)时用于数据持久性。
                在应用程序之间切换时它们甚至都不好(因为调用了 onSaveInstanceState() 方法,但没有再次调用 onCreate(bundle)onRestoreInstanceState(bundle)
                要获得更多持久性,请使用共享首选项。 read this article

                【讨论】:

                • 在您的情况下,onCreateonRestoreInstanceState 没有被调用,因为当您切换应用程序时,Activity 根本不会被破坏,因此无需恢复任何内容。 Android 会调用 onSaveInstanceState 以防 Activity 稍后被销毁(旋转屏幕时会发生 100% 的情况,因为整个设备配置已更改,必须从头开始重新创建 Activity)。
                【解决方案16】:

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

                mySavedInstanceState=savedInstanceState;
                

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

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

                我按照上面的建议使用onSaveInstanceStateonRestoreInstanceState,但我想我也可以或替代地使用我的方法在变量更改时保存变量(例如使用putBoolean

                【讨论】:

                  【解决方案17】:

                  虽然接受的答案是正确的,但有一种更快、更简单的方法可以使用名为 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 保存其状态的对象。

                  【讨论】:

                    【解决方案18】:

                    当一个活动被创建时,它的 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");
                        }
                    

                    【讨论】:

                      【解决方案19】:

                      基本上有两种方法可以实现这种变化。

                      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 方法的活动中执行以下操作。它看起来像这样:

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

                      【讨论】:

                        【解决方案20】:

                        这是 Steve Moseley 的回答(由 ToolmakerSteve 撰写)的评论,该评论将事情放在了正确的角度(在整个 onSaveInstanceState 与 onPause、东部成本与西部成本传奇中) )

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

                        【讨论】:

                          【解决方案21】:

                          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
                          

                          如果您不想使用 Optional,请添加默认值

                          【讨论】:

                            【解决方案22】:

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

                            当activity destroy SaveInstanceState(Bundle savedInstanceState) 方法被调用并在那里保存你想要保存的数据时。当活动重新启动时,你在onCreate() 中得到相同的结果。(savedInstanceState 不会为空,因为你在活动被破坏之前已经保存了一些数据)

                            【讨论】:

                              【解决方案23】:

                              科特林

                              您必须覆盖 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
                              }
                              

                              【讨论】:

                                【解决方案24】:

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

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

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

                                【讨论】:

                                  【解决方案25】:

                                  使用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'
                                  }
                                  

                                  现在,让我们看看下面这个例子如何在 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);
                                    }
                                  }
                                  

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

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

                                  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
                                  }
                                  

                                  【讨论】:

                                  • @ralphspoon 是的,它适用于片段和自定义视图。请检查示例代码。我编辑了我的答案。我建议你去官方文档github.com/frankiesardo/icepick 找到更多代码示例。
                                  • @ChetanMehra 你的意思是自定义视图类,对吧?如果是自定义视图,我们可以重写 onSaveInstanceState 和 onRestoreInstanceState,就像上面的 CustomView 示例一样。
                                  • 我的意思是视图类中的类对象,例如:class CustomView extends View{ @State ClassA a;} 或 class CustomView extends View { @State Inner class{}}
                                  • @THANNPhearum 我应该问另一个问题吗?
                                  • 我明白了。如果是这样,您的 ClassA 应该是 Parcelable。正如它所提到的,它适用于活动、片段或任何需要在 Bundle 上序列化其状态的对象
                                  【解决方案26】:

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

                                  【讨论】:

                                  • 这不是真的。从文档中:“与保存的实例状态不同,ViewModel 在系统启动的进程死亡期间被销毁。这就是为什么您应该将 ViewModel 对象与 onSaveInstanceState() (或其他一些磁盘持久性)结合使用,在 savedInstanceState 中存储标识符以帮助查看模型在系统死机后重新加载数据。”
                                  • 刚刚在后台更改权限时遇到了这个问题。
                                  • 我同意,来自文档“如果您需要处理系统启动的进程死亡,您可能希望使用 onSaveInstanceState() 作为备份。”
                                  【解决方案27】:

                                  有一种方法可以让 Android 在不实现任何方法的情况下保存状态。只需将此行添加到您的 Manifest in Activity 声明:

                                  android:configChanges="orientation|screenSize"
                                  

                                  应该是这样的:

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

                                  Here您可以找到有关此属性的更多信息。

                                  建议让Android为您处理而不是手动处理。

                                  【讨论】:

                                  • 这与保存状态无关,您只是放弃方向更改,请记住您的应用程序可以随时针对不同事件重新启动和暂停和恢复
                                  • 此答案适用于那些希望在方向改变时保存状态并希望避免理解和实施复杂方式的人
                                  • 很公平,我明白你的意思,我认为大多数努力保存状态的人都在使用片段,因为活动实际上只要它们有一个 ID,就会保存 UI 组件的状态,但片段更特别,我曾经使用过片段,但我再也不会使用它们了,保存实例的统计数据很难处理
                                  【解决方案28】:

                                  什么可以保存,什么不可以?

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

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

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

                                  默认情况下,系统将 View 对象保存在 Bundle 中。

                                  • EditText 中的文本
                                  • 滚动位置在ListView等中

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

                                  例如,int currentScore 在 GameActivity 中

                                  更多关于 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);
                                  }
                                  

                                  所以如果你忘记打电话是错误的 super.onSaveInstanceState(savedInstanceState);默认行为 将不起作用,即 EditText 中的文本将无法保存。

                                  选择哪个来恢复Activity状态?

                                   onCreate(Bundle savedInstanceState)
                                  

                                  onRestoreInstanceState(Bundle savedInstanceState)
                                  

                                  这两种方法都获得相同的 Bundle 对象,因此在哪里编写恢复逻辑并不重要。唯一的区别是在onCreate(Bundle savedInstanceState) 方法中,您必须进行空检查,而在后一种情况下不需要。其他答案已经有代码 sn-ps。你可以参考他们。

                                  关于 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);,以便系统默认恢复视图层次

                                  奖金

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

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

                                  相关链接:

                                  Demo on default behavior
                                  Android Official Documentation.

                                  【讨论】:

                                    【解决方案29】:

                                    使用 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
                                    

                                    【讨论】:

                                      【解决方案30】:

                                      您可以使用Live DataView Model For Lifecycle Handel From JetPack。请参阅此参考:

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

                                      【讨论】:

                                        猜你喜欢
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2013-04-15
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2019-03-28
                                        • 1970-01-01
                                        相关资源
                                        最近更新 更多