首页 文章

使用“保存实例状态”保存Android活动状态

提问于
浏览
2322

我一直在研究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 或类似的东西一样简单,但我发现任何明显的东西 .

27 回答

  • 130

    您需要覆盖 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("Name-Value Pair") Map 的方式,它将被传递到 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");
    }
    

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

  • 27

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

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

    • 配置更改,例如更改方向或手机语言,这可能需要创建新的活动实例 .

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

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

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

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

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

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

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

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

  • 8

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

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

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

  • 2348

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

  • 5

    为了帮助减少样板,我使用以下 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);
        }
    
    }
    

    Note: 此代码改编自名为AndroidAutowire的库项目,该项目在MIT license下获得许可 .

  • 178

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

    mySavedInstanceState=savedInstanceState;
    

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

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

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

  • 26

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

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

  • 35

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

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

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

    • 'normal'或管理此问题的预期方式本身相当复杂,具有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在这个答案中:

    Home key press behaviour

    UPDATE June 2013 :几个月后,我终于找到了'correct'解决方案 . 您不需要自己管理任何有状态的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;
        }
    }
    
  • 5

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

  • 1

    简单快速解决这个问题就是使用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
    }
    
  • 392

    以下是来自 Steve Moseley 的答案(由 ToolmakerSteve )发表的评论,该评论将事情置于透视中(在整个onSaveInstanceState vs onPause,east cost vs west cost saga)

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

  • 14

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

    • 用户终止应用程序并在以后重新打开它,但应用程序需要从上一个会话重新加载数据 - 这需要持久存储方法,例如使用SQLite .

    • 用户切换应用程序,然后回到原始状态并想要从中断处继续 - 在 onSaveInstanceState() 中保存和恢复捆绑数据(例如应用程序状态数据)和 onRestoreInstanceState() 通常就足够了 .

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

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

  • 11

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

    假设我有两个活动,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 .

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

  • 7

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

  • 11

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

  • 379

    当Activity进入后台时,真的 onSaveInstance 状态callen

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

  • 39

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

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

  • 1

    请注意,根据http://developer.android.com/reference/android/app/Activity.html中活动状态的文档,使用 onSaveInstanceStateonRestoreInstanceState for persistent data 是安全的_117185 .

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

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

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

    EDIT :有关进一步说明,请参阅 onSaveInstanceState() 文档:

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

  • 52

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

    本文介绍了三种方法:

    使用Instance State Bundle存储应用程序生命周期(即临时)的本地varible / 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();
    }
    
  • 59

    就我而言,拯救国家充其量只是一个障碍 . 如果需要保存持久数据,只需使用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;
    
  • 27

    savedInstanceState 仅用于保存与当前活动实例关联的状态,例如当前导航或选择信息,因此,如果Android销毁并重新创建活动,则可以像以前一样返回 . 请参阅onCreateonSaveInstanceState的文档

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

  • 22

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

  • 49

    Recreating an Activity

    在某些情况下,您的活动会因应用程序的正常行为而被销毁,例如当用户按下“返回”按钮或您的活动通过调用 finish() 表示其自身的销毁时 . 如果长时间使用系统或前台活动需要更多资源,系统也可能会破坏您的活动,因此系统必须关闭后台进程才能恢复内存 .

    activity 因为用户按下Back或 activity 自行完成而被销毁时,系统对该 Activity 实例的概念将永远消失,因为该行为表明不再需要该活动 . 但是,如果系统因系统约束(而不是正常的应用程序行为)而破坏活动,那么虽然实际的Activity实例已经消失,但系统会记住它存在,如果用户导航回到它,系统会创建一个新的活动的一个实例,它使用一组保存的数据来描述 destroyed 时的活动状态 . 系统用于恢复先前状态的已保存数据称为"instance state",它是存储在Bundle对象中的键值对的集合 .

    要保存有关活动状态的其他数据,必须覆盖onSaveInstanceState()回调方法 . 系统在用户离开您的活动时调用此方法,并向其传递Bundle对象,该对象将在您的活动意外销毁时保存 . 如果系统必须稍后重新创建活动实例,它会将相同的Bundle对象传递给 onRestoreInstanceState()onCreate() 方法 .

    当系统开始停止您的活动时,它会调用 onSaveInstanceState() (1),以便您可以指定要保存的其他状态数据,以防必须重新创建Activity实例 . 如果活动被销毁并且必须重新创建相同的实例,则系统会将(1)中定义的状态数据传递给 onCreate() 方法(2)和 onRestoreInstanceState() 方法(3) .

    Save Your Activity State

    当您的活动开始停止时,系统会调用 onSaveInstanceState() ,以便您的活动可以使用一组键值对保存状态信息 . 此方法的默认实现保存有关活动视图层次结构状态的信息,例如 EditText 窗口小部件中的文本或 ListView 的滚动位置 .

    要保存活动的其他状态信息,必须实现 onSaveInstanceState() 并将键值对添加到Bundle对象 . 例如:

    static final String STATE_SCORE = "playerScore";
      static final String STATE_LEVEL = "playerLevel";
    
      @Override
      public void onSaveInstanceState(Bundle savedInstanceState) {
      // Save the user's current game state
      savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
      savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
    
      // Always call the superclass so it can save the view hierarchy state
      super.onSaveInstanceState(savedInstanceState);
    }
    

    注意:始终调用 onSaveInstanceState() 的超类实现,因此默认实现可以保存视图层次结构的状态 .

    Restore Your Activity State

    在先前销毁活动之后重新创建活动时,您可以从系统传递活动的Bundle中恢复已保存的状态 . onCreate()onRestoreInstanceState() 回调方法都接收包含实例状态信息的相同 Bundle .

    因为无论系统是创建活动的新实例还是重新创建前一个实例,都会调用 onCreate() 方法,因此在尝试读取之前必须检查状态Bundle是否为null . 如果它为null,则系统正在创建活动的新实例,而不是恢复已销毁的先前实例 .

    例如,以下是如何在 onCreate() 中恢复某些状态数据:

    @Override
     protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState); // Always call the superclass first
    
     // Check whether we're recreating a previously destroyed instance
     if (savedInstanceState != null) {
        // Restore value of members from saved state
        mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
        mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
     } else {
        // Probably initialize members with default values for a new instance
     }
    
     }
    

    您可以选择实现 onRestoreInstanceState() ,系统在 onStart() 方法之后调用,而不是在 onCreate() 期间恢复状态 . 仅当存在要恢复的已保存状态时,系统才会调用 onRestoreInstanceState() ,因此您无需检查Bundle是否为null:

    public void onRestoreInstanceState(Bundle savedInstanceState) {
      // Always call the superclass so it can restore the view hierarchy
      super.onRestoreInstanceState(savedInstanceState);
    
      // Restore state members from saved instance
      mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
      mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
    }
    
  • 5

    将LiveData(Android架构组件)添加到项目中

    添加以下依赖项

    implementation "android.arch.lifecycle:extensions:1.1.0"
    

    LiveData接收观察者并仅在数据更改处于STARTED或RESUMED状态时通知它 . LiveData的优势在于,当您的活动进入 STARTEDRESUMED 以外的任何状态时,它将不会调用 observer 上的 onChanged 方法 .

    private TextView mTextView;
    private MutableLiveData<String> mMutableLiveData;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mTextView = (TextView) findViewById(R.id.textView);
        mMutableLiveData = new MutableLiveData<>();
        mMutableLiveData.observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                mTextView.setText(s);
            }
        });
    
    }
    
  • 71

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

    • 使用 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); 
    
    }
    
  • 32

    同时我一般不再使用

    Bundle savedInstanceState & Co
    

    对于大多数活动而言,实时循环过于复杂且不必要 . 谷歌声称自己,它甚至不可靠 .

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

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

    在某种程度上,SharedPreferences的工作方式与Bundles类似 . 并且自然而且首先这些值必须是偏好的 .

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

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

相关问题