如何使用保存实例状态保存Android Activity状态?

Bernard 2538 android application-state android-activity

我一直在研究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分钟左右,并且没有找到任何明显的东西.

Reto Meier.. 2503

您需要覆盖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.
}

该套件基本上是存储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");
}

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

  • 小心:你需要在将值添加到Bundle之前调用super.onSaveInstanceState(savedInstanceState),否则它们将在该调用中消失(Droid X And​​roid 2.2). (483认同)
  • 小心:官方文档说明你应该在onPause-Method中保存重要信息,因为o​​nsaveinstance方法不是android生命周期的一部分.http://developer.android.com/reference/android/app/Activity.html (116认同)
  • 这个事实有效地使`onSaveInstanceState`几乎无用,除了屏幕方向改变的情况.在几乎所有其他情况下,您永远不能依赖它,并且需要在其他地方手动保存您的UI状态.或者通过覆盖BACK按钮行为来防止您的应用被杀死.我不明白为什么他们甚至首先这样实现它.完全不直观.除了这个非常特殊的方法之外,你不能拥有系统允许你保存的Bundle. (29认同)
  • 这有可能在手机上运行,​​但不在模拟器中吗?我似乎无法获得非null的savedInstanceState. (21认同)
  • 请注意,保存/恢复到Bundle /从Bundle恢复UI状态*会自动*处理*已经分配了ids*的View`s.来自`onSaveInstanceState`文档:"默认实现通过在具有id的层次结构中的每个视图上调用`onSaveInstanceState()`并通过保存当前的id来为您处理大多数UI每实例状态聚焦视图(所有这些都是由`onRestoreInstanceState(Bundle)`的默认实现恢复的)" (10认同)
  • 我有一个点列表,如何保存此数组列表中的所有点,然后还原它们? (5认同)
  • @schlingel - 是的.但是什么时候我们得到一个保存的实例状态?总是?在电话轮换?按下主页按钮?等?这是我的问题.我是初学者,所以我不知道这些事情. (5认同)
  • @schlingel +1提到onSaveInstanceState()不是生命周期的一部分.我认为按下"后退"按钮时不会调用它.只有在按下"主页"按钮时才会调用它. (3认同)
  • @Shaun,看起来你发现[发布此评论](http://stackoverflow.com/questions/5412746/android-fragment-onrestoreinstancestate)后,片段也可以使用与`onRestoreInstanceState`等效的内容. (2认同)
  • 我在股票X8上遇到了一些麻烦.绝不会调用OnSaveInstanceState.不是在返回主屏幕时,也不是在按下后退按钮进入上一个活动时,也不是在退出应用程序的情况下.在被破坏时调用,但不在onSaveInstance状态. (2认同)

Dave L... 416

savedInstanceState只保存与活动的当前实例相关联的状态,例如当前导航或选择信息,因此,如果Android的破坏并重新创建一个活动,它可以回来,因为它以前.请参阅文件onCreateonSaveInstanceState

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

  • 当系统正在创建Activity的新实例时,savedInstanceState为null,而在恢复时不为null. (5认同)
  • ...当系统需要创建一个新的Activity实例时,会引发*的问题.退出应用程序的某些方法不会创建包,因此必须创建新实例.这是根本问题; 它意味着一个人不能*依赖于bundle的存在,并且必须做一些替代的持久存储方式.onSave/onRestoreInstanceState的好处是它是一个系统可以*突然*做的机制,而不会占用太多的系统资源.所以支持它是好的,并且具有持久存储以便从app更优雅地退出. (5认同)
  • 什么时候savedInstanceState == null,什么时候不为null? (2认同)

Steve Mosele.. 403

需要注意的是不要使用安全onSaveInstanceStateonRestoreInstanceState 持久性数据,根据在活动状态的文档http://developer.android.com/reference/android/app/Activity.html.

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

请注意,保存持久数据非常重要,onPause()而不是onSaveInstanceState(Bundle) 因为后者不是生命周期回调的一部分,因此不会在其文档中描述的每种情况下调用.

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

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

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

  • 我们不应该对这个人投票,至少他努力通过文档,我认为我们的人在这里实际上建立一个知识渊博的社区,并互相帮助,而不是DOWN VOTE.所以1投票支持这项工作,我会要求你们不要投票,而不是投票或不投票......这个人清除了人们在阅读文档时想要的混乱.1投票:) (136认同)
  • 只是为了挑剔:它也不是不安全的.这只取决于你想保留什么以及多长时间,@伯纳德在他原来的问题中并不完全清楚.InstanceState非常适合保留当前的UI状态(输入控件的数据,列表中的当前位置等),而Pause/Resume是长期持久存储的唯一可能性. (51认同)
  • 这个答案是绝对正确的,值得UP投票,而不是下来!让我澄清那些没有看到它的人之间的状态差异.GUI状态(如选定的单选按钮和输入字段中的某些文本)比数据状态重要得多,例如添加到ListView中显示的列表中的记录.后者必须存储在onPause数据库中,因为它是唯一保证的调用.如果你把它放在onSaveInstanceState中,那么如果没有调用数据则会丢失数据.但如果出于同样的原因没有保存单选按钮选择 - 这不是什么大问题. (32认同)
  • 这应该是低估的.使用(保存|恢复)InstanceState(如生命周期方法)是不安全的(即在其中执行除保存/恢复状态之外的任何其他操作).它们非常适合保存/恢复状态.另外,您想如何在onPause和onResume中保存/恢复状态?你没有在你可以使用的那些方法中获得Bundles,所以你必须在数据库,文件等中使用一些其他的状态保存,这是愚蠢的. (28认同)
  • 我认为这个答案不值得投票.至少他努力回答并引用了doco的一个部分. (18认同)
  • 你可以用一个例子编辑这篇文章,如何保存onPause()? (2认同)
  • @laplasz - 对于`onPause()`,你需要存储到`SharedPreferences`或数据库或云中.请参阅:[http://developer.android.com/guide/topics/data/data-storage.html#pref](http://developer.android.com/guide/topics/data/data-storage.html#例如,pref).他们使用`onStop()`但代码是相同的. (2认同)

Martin Belch.. 197

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

本文介绍了三种方法:

使用实例状态包存储应用程序生命周期(即临时)的本地变量/ 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();
}

  • 链接已经死了. (14认同)
  • 请注意,`editor.apply()`比`editor.commit()`更快. (6认同)
  • @ MartinBelcher-Eigo文章谈到SharedPreferences中的数据"这个数据被写入设备上的数据库.."我相信数据存储在文件系统的应用程序目录中的文件中. (2认同)

Mike Repass.. 140

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

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

浏览所有这些线程,我怀疑很多时候开发人员正在同时讨论这两个不同的问题......因此所有混淆和报告"这对我不起作用".

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

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

但是 - 有一个非常令人困惑的错误使所有这些变得复杂.细节在这里:

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

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

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

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

主页按键行为

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

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

  • 为什么Android再次成为第二选择的移动操作系统?*埋头在手* (2认同)

Fedor.. 80

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

  • 对不起,这不太正确.在需要重新生成活动之前调用onSaveInstanceState.即每次用户旋转设备.它用于存储瞬态视图状态.当android强制关闭应用程序时,实际上没有调用onSaveInstanceState(这就是为什么它存储重要的应用程序数据不安全).但是,保证在活动被杀死之前调用onPause,因此它应该用于在首选项或Squlite中存储永久信息.正确答案,错误的理由. (34认同)

小智.. 68

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

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

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

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


小智.. 65

就我而言,拯救国家充其量只是一个障碍.如果需要保存持久数据,只需使用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的关键路径.我实际上没有计时,所以我很高兴得到纠正,但是加载和打开数据库文件肯定不会很快? (6认同)
  • 非常感谢您提供的解决方案,让新手可以将其剪切并粘贴到他们的应用中并立即使用!@Tom就速度而言,大约需要七秒钟来存储1000对,但是您可以在AsyncTask中完成它。但是,您需要添加一个finally {cursor.close()},否则这样做会因内存泄漏而崩溃。 (3认同)

roy mathew.. 56

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

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

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

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

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

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

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

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

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

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


Ixx.. 42

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

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

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


小智.. 39

onSaveInstanceState()当活动进入后台时,确实说明了callen

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


Jared Rummle.. 35

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


stefan bache.. 33

同时我一般不再使用

Bundle savedInstanceState & Co

生命周期对于大多数活动而言过于复杂且不必要.

谷歌表示,它甚至不可靠.

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

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

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

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

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


Jared Kells.. 30

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

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

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

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

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

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

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

"设置" - >"开发者选项"下有一个名为"不要保留活动"的选项.当它启用时,Android将始终销毁活动并在它们背景时重新创建它们.这是在开发时保持启用的一个很好的选项,因为它模拟了最坏的情况.(低内存设备一直在回收您的活动).

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


Mahorad.. 27

仅在旋转屏幕(方向改变)时,onSaveInstanceState(bundle)onRestoreInstanceState(bundle)方法对于数据持久性是有用的.
他们不是在应用程序之间切换(因为即使是好的onSaveInstanceState()方法被调用,但onCreate(bundle)onRestoreInstanceState(bundle)没有再次调用.
欲了解更多持久使用共享偏好.阅读这篇文章

  • 在你的情况下,`onCreate`和`onRestoreInstanceState`没有被调用,因为当你切换应用程序时,`Activity`根本没有被销毁,所以没有必要恢复任何东西.Android调用`onSaveInstanceState`以防万一Activity在以后被销毁(在旋转屏幕时100%确定,因为整个设备配置已经改变,并且必须从头开始重新创建Activity). (2认同)

torwalker.. 19

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

mySavedInstanceState=savedInstanceState;

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

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

我使用onSaveInstanceState并且onRestoreInstanceState如上所述,但我想我也可以或者使用我的方法在变量时保存变量(例如使用putBoolean)


Kevin Cronly.. 18

虽然接受的答案是正确的,但是使用名为Icepick的库在Android上保存活动状态的方法更快更容易.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.


Mansuu...... 15

创建活动时,会调用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");
    }


小智.. 14

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

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

}


samis.. 10

以下是Steve Moseley的答案(由ToolmakerSteve撰写)的评论,该评论将事物放入视角(在整个onSaveInstanceState vs onPause,east cost vs west cost saga)

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


小智.. 9

Kotlin代码:

保存:

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

然后在onCreate()onRestoreInstanceState()

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

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


ascii_walker.. 8

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

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


THANN Phearu.. 6

简单快速解决这个问题就是使用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还可以为自定义视图生成实例状态代码:

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
}


ComeIn.. 6

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

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

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


Sazzad Hissa.. 5

科特林

您必须重写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
}


归档时间:

查看次数:

740807 次

最近记录:

8 月,2 周 前