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

问:

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

答1:

huntsbot.com洞察每一个产品背后的需求与收益,从而捕获灵感

您需要覆盖 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) 的默认实现恢复)"

答2:

huntsbot.com – 高效赚钱,自由工作

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

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

什么时候是 savedInstanceState == null 什么时候不是 null ?

当系统正在创建您的 Activity 的新实例时,savedInstanceState 为空,而在恢复时不为空。

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

答3:

打造属于自己的副业,开启自由职业之旅,从huntsbot.com开始!

请注意,根据the documentation on Activity,将 onSaveInstanceState 和 onRestoreInstanceState 用于持久数据是不安全的时间>。

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

请注意,将持久数据保存在 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 中,如果不调用它,你就有丢失数据的风险。但是,如果出于同样的原因没有保存单选按钮选择 - 这没什么大不了的。

答4:

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

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

答5:

与HuntsBot一起,探索全球自由职业机会–huntsbot.com

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

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

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

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

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

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

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

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

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

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

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

huntsbot.com高效搞钱,一站式跟进超10+任务平台外包需求

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

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

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

答7:

huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式

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

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

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

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

答8:

huntsbot.com洞察每一个产品背后的需求与收益,从而捕获灵感

就我而言,保存状态充其量只是一个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 上使用它,这是我最近正在使用/使用的设备。

答9:

huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式

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

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

对于上述情况,我所做的是在清单中我进行了一些更改,如下所示:




在按钮单击事件的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:

保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com

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

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

答11:

huntsbot.com – 高效赚钱,自由工作

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

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

huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。

原文链接:https://www.huntsbot.com/qa/veXY/how-can-i-save-an-activity-state-using-the-save-instance-state?lang=zh_CN&from=csdn

huntsbot.com精选全球7大洲远程工作机会,涵盖各领域,帮助想要远程工作的数字游民们能更精准、更高效的找到对方。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值