ChatGPT解决这个技术问题 Extra ChatGPT

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

我一直在研究Android SDK平台,有点不清楚如何保存应用程序的状态。因此,鉴于“你好,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)

Z
Zhar

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

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


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

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

对于更长寿的状态,请考虑使用 SQLite 数据库、文件或首选项。请参阅Saving Persistent State


什么时候是 savedInstanceState == null 什么时候不是 null ?
当系统正在创建您的 Activity 的新实例时,savedInstanceState 为空,而在恢复时不为空。
...这引发了系统何时需要创建新的 Activity 实例的问题。退出应用程序的某些方法不会创建捆绑包,因此必须创建新实例。这是根本问题;这意味着一个人不能依赖捆绑的存在,并且必须做一些替代的持久存储方式。 onSave/onRestoreInstanceState 的好处是它是一种系统可以突然做的机制,不会消耗太多系统资源。所以支持这一点是很好的,并且具有持久存储以便更优雅地退出应用程序。
P
Peter Mortensen

请注意,根据the documentation on Activity,将 onSaveInstanceStateonRestoreInstanceState 用于持久数据安全的时间>。

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

请注意,将持久数据保存在 onPause() 而不是 onSaveInstanceState(Bundle) 中很重要,因为后者不是生命周期回调的一部分,因此不会在其文档中描述的所有情况下都被调用。

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

如需进一步说明,请参阅 onSaveInstanceState() 文档:

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


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

我的同事写了一篇文章,解释了 Android 设备上的应用程序状态,包括对 Activity 生命周期和状态信息、如何存储状态信息以及保存到状态 BundleSharedPreferences 的说明。 Take 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是一种数据库吗?我会说它是;)
P
Peter Mortensen

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

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

管理此问题的“正常”或预期方式本身相当复杂,因为 onPause/onResume 和 onSaveInstanceState/onRestoreInstanceState 的双重性

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

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

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

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

从 Eclipse 启动应用程序时,Activity 堆栈在首次运行期间行为不正确 (#36907463)

市场/浏览器应用程序安装程序允许第二个实例关闭应用程序 (#36911210)

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

在我意识到我的主要问题是这个错误,而不是预期的框架行为之前,我在这些线程中挣扎了几个小时。一个很棒的文章和解决方法(更新:见下文)似乎来自用户@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;
    }
}

P
Peter Mortensen

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

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


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

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

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

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

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


E
Elio Lako

就我而言,保存状态充其量只是一个kludge。如果您需要保存持久数据,只需使用 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 上使用它,这是我最近正在使用/使用的设备。
S
Stephen Tetreault

我想我找到了答案。让我用简单的话告诉我我做了什么:

假设我有两个活动,活动 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 是关于离开应用程序(例如主页按钮,或其他方式切换到不同的应用程序)。
这正是我一直在寻找的答案!
C
Community

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

如果 Activity 正在停止并且可能在它恢复之前被杀死,Android 会调用 onSaveInstanceState()!这意味着它应该存储在重新启动 Activity 时重新初始化到相同条件所需的任何状态。它是 onCreate() 方法的对应物,实际上传递给 onCreate() 的 savedInstanceState Bundle 与您在 onSaveInstanceState() 方法中构造为 outState 的 Bundle 相同。 onPause() 和 onResume() 也是互补的方法。 onPause() 总是在 Activity 结束时被调用,即使是我们发起的(例如使用 finish() 调用)。我们将使用它来将当前笔记保存回数据库。好的做法是释放任何可以在 onPause() 期间释放的资源,以便在处于被动状态时占用更少的资源。


M
Manaus

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

来自文档的引用:“在可能杀死活动之前调用此方法,以便在将来某个时间返回时可以恢复其状态。” Source


J
Jared Rummler

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


P
Peter Mortensen

同时我一般不再使用

Bundle savedInstanceState & Co

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

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

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

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

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

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

应用此概念时,活动将继续使用上次保存的状态,无论它是初始打开并在其间重新启动,还是由于返回堆栈而重新打开。


J
Jared Kells

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

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

配置更改,例如更改方向或电话语言,可能需要创建新的活动实例。

操作系统销毁活动后,您从后台返回应用程序。

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

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

当您按下后退按钮时,活动完成。重新启动应用程序是一个全新的实例。你根本没有从后台恢复。

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

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

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

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


M
Mxyk

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


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

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

mySavedInstanceState=savedInstanceState;

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

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

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


K
Kevin Cronly

尽管接受的答案是正确的,但使用名为 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 保存其状态的对象。


M
Mansuu....

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

M
Mr Robot

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

使用 onSaveInstanceState() 和 onRestoreInstanceState()。在清单 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); 

}

s
samus

这是史蒂夫·莫斯利 (Steve Moseley) 的回答(由 ToolmakerSteve 撰写)的评论,该评论将事情放在了正确的位置(在整个 onSaveInstanceState 与 onPause,东部成本与西部成本传奇中)

@VVK - 我部分不同意。退出应用程序的某些方式不会触发 onSaveInstanceState (oSIS)。这限制了 oSIS 的实用性。对于最少的操作系统资源,它值得支持,但是如果一个应用程序想要将用户返回到他们所处的状态,那么无论应用程序是如何退出的,都需要使用持久存储方法来代替。我使用 onCreate 检查捆绑包,如果丢失,则检查持久存储。这集中了决策制定。我可以从崩溃中恢复,或者返回按钮退出或自定义菜单项退出,或者在很多天后回到用户所在的屏幕。 – ToolmakerSteve 2015 年 9 月 19 日在 10:38


R
Rafols

科特林代码:

节省:

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,请添加默认值


R
Rahul Sharma

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

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


S
Sazzad Hissain Khan

科特林

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

生命周期图

https://i.stack.imgur.com/Ts2F1.png

存储变量

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
}

P
Pang

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

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

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


T
THANN Phearum

使用 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 你的意思是自定义视图类,对吧?如果是自定义视图,我们可以像上面的 CustomView 示例一样覆盖 onSaveInstanceState 和 onRestoreInstanceState。
我的意思是视图类中的类对象,例如:class CustomView extends View{ @State ClassA a;} 或 class CustomView extends View { @State Inner class{}}
@THANNPhearum 我应该问另一个问题吗?
我懂了。如果是这样,您的 ClassA 应该是 Parcelable。正如它所提到的,它适用于活动、片段或任何需要在 Bundle 上序列化其状态的对象
M
M Abdul Sami

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


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

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

android:configChanges="orientation|screenSize"

它应该如下所示:

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

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

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


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

什么该保存,什么不该保存?

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

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

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

例如,默认情况下系统将视图对象保存在 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);
}

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

选择哪个来恢复 Activity 状态?

 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 时,系统才会调用 onSaveInstanceState(Bundle savedInstanceState)。例如,您正在使用 App X,突然接到一个电话。您移动到调用者应用程序并返回到应用程序 X。在这种情况下,将调用 onSaveInstanceState(Bundle savedInstanceState) 方法。

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

相关链接:

Demo on default behavior
Android Official Documentation


n
neelkanth_vyas

取而代之的是,您应该使用 ViewModel,它将保留数据直到活动生命周期。


U
Umut ADALI

现在在视图模型中做 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);
  }
}

你是对的,但这个库仍在发布中,所以我认为我们应该等待......