代码之家  ›  专栏  ›  技术社区  ›  Bernard

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

  •  2359
  • Bernard  · 技术社区  · 16 年前

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

    26 回复  |  直到 5 年前
        1
  •  2374
  •   David Kariuki VonC    5 年前

    你需要重写 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");
    }
    

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

        2
  •  396
  •   Benoit Duffez    12 年前

    这个 savedInstanceState 仅用于保存与活动的当前实例相关联的状态,例如当前导航或选择信息,以便在Android破坏并重新创建活动时,它可以像以前一样恢复。参见文档 onCreate onSaveInstanceState

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

        3
  •  382
  •   Vicky Chijwani    9 年前

    注意它是 不是 安全使用 onSaveInstanceState onRestoreInstanceState 对于持久数据 ,根据活动状态的文档 http://developer.android.com/reference/android/app/Activity.html .

    文档说明(在“活动生命周期”部分中):

    请注意,保存 中的持久数据 onPause() 相反 属于 onSaveInstanceState(Bundle) 因为后者不是 生命周期回调,因此 在所描述的每种情况下调用 在其文档中。

    换句话说,将持久数据的保存/还原代码放入 OnPoAube() onResume() !

    编辑 :为了进一步澄清,这里是 onSaveInstanceState() 文档:

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

        4
  •  180
  •   Peter Mortensen rohan kamat    5 年前

    我的同事写了一篇文章解释Android设备上的应用程序状态,包括关于活动生命周期和状态信息的解释、如何存储状态信息以及保存到状态 Bundle SharedPreferences take a look at 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();
    }
    
        5
  •  132
  •   Community Ian Goodfellow    7 年前

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

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

    浏览所有这些线程时,我怀疑开发人员大部分时间都在同时讨论这两个不同的问题…因此,“这对我不管用”的所有困惑和报道。

    首先,要澄清“预期”行为:OnSaveInstance和OnRestoreRestance是脆弱的,并且只适用于瞬时状态。预期用途(AFAIT)是在手机旋转(方向改变)时处理活动娱乐。换句话说,当您的活动在逻辑上仍处于“顶层”状态,但仍必须由系统重新启动时,您就可以使用它了。保存的包不会在进程/memory/gc之外持久化,因此如果您的活动转到后台,您就不能真正依赖于它。是的,也许您的活动的内存将在其后台访问和退出GC之后继续存在,但这不可靠(也不可预测)。

    因此,如果您有一个场景,其中有意义的“用户进度”或状态应该在应用程序的“启动”之间保持,那么指导是使用onpause和onresume。您必须自己选择并准备一个持久存储。

    但是-有一个非常令人困惑的bug,它使所有这些复杂化了。详情如下:

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

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

    基本上,如果您的应用程序是用singletask标志启动的,然后从主屏幕或启动程序菜单启动它,那么随后的调用将创建一个新的任务…实际上,你的应用程序有两个不同的实例驻留在同一个堆栈中…很奇怪很快。当你在开发过程中启动你的应用程序时(比如从Eclipse或Intellij),开发人员会经常遇到这种情况。但也可以通过应用商店的一些更新机制(因此它也会影响您的用户)。

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

    Home key press behaviour

    2013年6月更新 :几个月后,我终于找到了“正确”的解决方案。您不需要自己管理任何有状态的StartedApp标记,您可以从框架中检测到这一点,并适当地保释。我在我的启动程序活动的开头使用这个。创建:

    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
  •  73
  •   Praveenkumar    12 年前

    onSaveInstanceState 当系统需要内存并终止应用程序时调用。当用户刚关闭应用程序时,不会调用它。所以我认为应用程序状态也应该保存在 onPause 它应该保存到一些持久的存储中,比如 Preferences Sqlite

        7
  •  62
  •   Amanda S    13 年前

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

    1. 用户终止应用程序并在以后重新打开它,但是应用程序需要从最后一个会话重新加载数据,这需要一种持久的存储方法,如使用sqlite。
    2. 用户切换应用程序,然后返回原始应用程序,并希望在它们停止的位置获取数据包数据(如应用程序状态数据)的保存和恢复 onSaveInstanceState() onRestoreInstanceState() 通常是足够的。

    如果以持久的方式保存状态数据,则可以在 onResume() onCreate() (或者实际上在任何生命周期调用中)。这可能是或可能不是所期望的行为。如果你把它放在 InstanceState ,则它是暂时的,仅适用于存储数据以供同一用户__session_(我松散地使用术语session)使用,但不适用于___session_之间。

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

        8
  •  54
  •   Peter Mortensen rohan kamat    10 年前

    就我而言,拯救国家充其量只是一种拼凑。如果需要保存持久数据,只需使用 SQLite 数据库。安卓做到了 SOOO 容易的。

    像这样:

    import java.util.Date;
    import android.content.Context;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
    
    public class dataHelper {
    
        private static final String DATABASE_NAME = "autoMate.db";
        private static final int DATABASE_VERSION = 1;
    
        private Context context;
        private SQLiteDatabase db;
        private OpenHelper oh ;
    
        public dataHelper(Context context) {
            this.context = context;
            this.oh = new OpenHelper(this.context);
            this.db = oh.getWritableDatabase();
        }
    
        public void close()
        {
            db.close();
            oh.close();
            db = null;
            oh = null;
            SQLiteDatabase.releaseMemory();
        }
    
    
        public void setCode(String codeName, Object codeValue, String codeDataType)
        {
            Cursor codeRow = db.rawQuery("SELECT * FROM code WHERE codeName = '"+  codeName + "'", null);
            String cv = "" ;
    
            if (codeDataType.toLowerCase().trim().equals("long") == true)
            {
                cv = String.valueOf(codeValue);
            }
            else if (codeDataType.toLowerCase().trim().equals("int") == true)
            {
                cv = String.valueOf(codeValue);
            }
            else if (codeDataType.toLowerCase().trim().equals("date") == true)
            {
                cv = String.valueOf(((Date)codeValue).getTime());
            }
            else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
            {
                String.valueOf(codeValue);
            }
            else
            {
                cv = String.valueOf(codeValue);
            }
    
            if(codeRow.getCount() > 0) //exists-- update
            {
                db.execSQL("update code set codeValue = '" + cv +
                    "' where codeName = '" + codeName + "'");
            }
            else // does not exist, insert
            {
                db.execSQL("INSERT INTO code (codeName, codeValue, codeDataType) VALUES(" +
                        "'" + codeName + "'," +
                        "'" + cv + "'," +
                        "'" + codeDataType + "')" );
            }
        }
    
        public Object getCode(String codeName, Object defaultValue)
        {
            //Check to see if it already exists
            String codeValue = "";
            String codeDataType = "";
            boolean found = false;
            Cursor codeRow  = db.rawQuery("SELECT * FROM code WHERE codeName = '"+  codeName + "'", null);
            if (codeRow.moveToFirst())
            {
                codeValue = codeRow.getString(codeRow.getColumnIndex("codeValue"));
                codeDataType = codeRow.getString(codeRow.getColumnIndex("codeDataType"));
                found = true;
            }
    
            if (found == false)
            {
                return defaultValue;
            }
            else if (codeDataType.toLowerCase().trim().equals("long") == true)
            {
                if (codeValue.equals("") == true)
                {
                    return (long)0;
                }
                return Long.parseLong(codeValue);
            }
            else if (codeDataType.toLowerCase().trim().equals("int") == true)
            {
                if (codeValue.equals("") == true)
                {
                    return (int)0;
                }
                return Integer.parseInt(codeValue);
            }
            else if (codeDataType.toLowerCase().trim().equals("date") == true)
            {
                if (codeValue.equals("") == true)
                {
                    return null;
                }
                return new Date(Long.parseLong(codeValue));
            }
            else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
            {
                if (codeValue.equals("") == true)
                {
                    return false;
                }
                return Boolean.parseBoolean(codeValue);
            }
            else
            {
                return (String)codeValue;
            }
        }
    
    
        private static class OpenHelper extends SQLiteOpenHelper {
    
            OpenHelper(Context context) {
                super(context, DATABASE_NAME, null, DATABASE_VERSION);
            }
    
            @Override
            public void onCreate(SQLiteDatabase db) {
                db.execSQL("CREATE TABLE IF  NOT EXISTS code" +
                "(id INTEGER PRIMARY KEY, codeName TEXT, codeValue TEXT, codeDataType TEXT)");
            }
    
            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            }
        }
    }
    

    之后打个简单的电话

    dataHelper dh = new dataHelper(getBaseContext());
    String status = (String) dh.getCode("appState", "safetyDisabled");
    Date serviceStart = (Date) dh.getCode("serviceStartTime", null);
    dh.close();
    dh = null;
    
        9
  •  51
  •   Stephen Tetreault    10 年前

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

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

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

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

    在活动1的按钮点击事件中,我这样做了:

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

    在Activity2 on Button Click事件中,我这样做了:

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

    现在将要发生的是,无论我们在活动2中所做的更改是什么,都不会丢失,并且我们可以以与以前相同的状态查看活动2。

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

        10
  •  37
  •   Jorgesys    9 年前

    onSaveInstanceState() 对于瞬态数据(恢复 onCreate() / onRestoreInstanceState() ) onPause() 对于永久性数据(恢复到 onResume() ) 来自Android技术资源:

    OnSaveInstanceState()。 如果活动正在停止并可能在恢复前被杀死,则由Android调用!这意味着当活动重新启动时,它应该存储重新初始化到相同条件所需的任何状态。它是onCreate()方法的对应项,事实上,传入onCreate()的savedinstanceState包与在onSaveInstanceState()方法中构造为outState的包是相同的。

    OnPoAube() OnReSuMe() 也是免费的方法。每次活动结束时都会调用onpause(),即使我们发起了这样的调用(例如使用finish()调用)。我们将使用它将当前注释保存回数据库。好的做法是释放任何可以在onpause()期间释放的资源,以便在被动状态下占用更少的资源。

        11
  •  34
  •   Praveenkumar    12 年前

    真的? onSaveInstance 活动转到后台时的状态调用方

    从文档中引用: “方法 onSaveInstanceState(Bundle) 在将活动置于这种后台状态之前调用“

        12
  •  29
  •   Jared Rummler mehmoodnisar125    9 年前

    为了帮助减少样板文件,我使用以下内容 interface class 读/写 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
  •  29
  •   Peter Mortensen rohan kamat    5 年前

    同时,我做的一般不再有用

    Bundle savedInstanceState & Co
    

    生命周期对于大多数活动来说太复杂,不必要。

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

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

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

    在某种程度上,共享引用的工作方式类似于捆绑包。 当然,首先,这些值必须从偏好中读取。

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

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

        14
  •  28
  •   Jared Kells    9 年前

    直接回答原始问题。savedinstanceState为空,因为从未重新创建您的活动。

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

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

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

    在测试Hello World示例时,有几种方法可以离开并返回活动。

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

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

    在“设置”->开发人员选项下有一个名为“不保留活动”的选项。当它被启用时,Android将总是破坏活动,并在活动被重新建立时重新创建它们。在开发时,这是一个很好的选择,因为它模拟最坏的情况。(一个低内存设备,可随时回收您的活动)。

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

        15
  •  24
  •   Mxyk Edda    12 年前

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

        16
  •  16
  •   torwalker    10 年前

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

    mySavedInstanceState=savedInstanceState;
    

    当我需要的时候,用它来获取变量的内容,沿着以下几行:

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

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

        17
  •  15
  •   Kevin Cronly    8 年前

    虽然接受的答案是正确的,但是有一种更快更简单的方法可以使用一个名为 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
  •  13
  •   Mansuu....    7 年前

    创建活动时,会调用onCreate()方法。

       @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }
    

    savedinstanceState是bundle类的对象,第一次为空,但在重新创建时包含值。要保存活动的状态,必须重写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()或onRestoreStanceState()中重新创建后恢复。OnCreate()和OnRestoreInstanceState()中接收的束是相同的。

       @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
  •  12
  •   Mr Robot CaseyB    8 年前

    基本上有两种方法来实现这个变更。

    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
  •  8
  •   samus    7 年前

    这是来自 史提夫莫塞利 的答案(通过) 工具匠史蒂夫 )这使事情有了透视(在整个OnSaveInstanceState与OnPause、East Cost与West Cost Saga中)

    @我不同意。退出应用程序的某些方法不会触发 OnSaveInstanceState(OSI)。这限制了肺结核的有效性。它的 值得支持,只需最少的操作系统资源,但如果应用程序想要 将用户返回到他们所处的状态,无论应用程序如何 退出时,必须使用持久存储方法。 我使用onCreate检查捆绑包,如果丢失,则检查 永久存储。 这集中了决策。我可以 从崩溃中恢复,或后退按钮退出或自定义菜单项退出,或 “返回屏幕”用户在许多天后打开。_ 19’15在10:38

        21
  •  7
  •   Rafols    6 年前

    科特林代码:

    保存:

    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
    

    如果不希望有选项,请添加默认值

        22
  •  5
  •   THANN Phearum    8 年前

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

    首先,在中设置库 app/build.gradle

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

    现在,让我们检查下面的示例如何在活动中保存状态

    public class ExampleActivity extends Activity {
      @State String username; // This will be automatically saved and restored
    
      @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Icepick.restoreInstanceState(this, savedInstanceState);
      }
    
      @Override public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Icepick.saveInstanceState(this, outState);
      }
    }
    

    它适用于需要在捆绑包上序列化其状态的活动、片段或任何对象(例如,迫击炮的视图演示者)

    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
    }
    
        23
  •  5
  •   Rahul Sharma Rashid Kurbanov    6 年前

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

    当活动破坏时 保存实例状态(bundle savedinstancestate) 方法被调用,然后保存要保存的数据。你也一样 OnCuto() 当活动重新启动时。(由于在活动被破坏之前已在其中保存了一些数据,因此savedinstancestate不会为空)

        24
  •  5
  •   Pang JB Nizet    6 年前

    不确定我的解决方案是否被拒绝,但我使用绑定服务来持久化ViewModel状态。无论是将它存储在服务的内存中,还是持久化并从sqlite数据库中检索它,都取决于您的需求。这就是任何风格的服务所做的,它们提供诸如维护应用程序状态和抽象公共业务逻辑等服务。

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

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

        25
  •  1
  •   M Abdul Sami    6 年前

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

        26
  •  0
  •   Rohit Singh Varun    5 年前

    保存什么和不保存什么?

    曾经想知道为什么 EditText 方向更改时自动保存?好吧,这个答案是给你的。

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

    实例状态是 键值 成对存储在 Bundle 对象。

    例如,默认情况下,系统将视图对象保存在包中。

    • 文本在 编辑文本
    • A中的滚动位置 ListView 等。

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

    例如, int currentScore 在游戏活动中

    保存数据时有关OnSavedInstanceState(Bundle SaveDinstanceState)的详细信息

    @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); 默认行为 将不工作,即编辑文本中的文本将不保存。

    恢复活动状态时选择哪一个?

     onCreate(Bundle savedInstanceState)
    

    onRestoreInstanceState(Bundle savedInstanceState)
    

    这两种方法都得到相同的bundle对象,所以在哪里编写恢复逻辑并不重要。唯一的区别是 onCreate(Bundle savedInstanceState) 方法在后一种情况下不需要空检查,但必须进行空检查。其他答案已经有代码片段了。你可以参考它们。

    有关OnRestoreStanceState(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) 只有当用户打算返回活动时,系统才会调用。例如,你正在使用应用程序X,突然你接到一个电话。您移动到呼叫者应用程序并返回到应用程序X。在这种情况下, OnSaveInstanceState(bundle savedinstanceState) 将调用方法。

    但如果用户按下后退按钮,就要考虑这个问题。假设用户不打算返回活动,因此在这种情况下 OnSaveInstanceState(bundle savedinstanceState) 系统不会调用。 关键是在保存数据时,您应该考虑所有的场景。

    这里是 most relevant link .