我一直在安卓SDK平台上工作,现在还不清楚如何保存应用程序的状态。因此,考虑到“你好,Android”示例的这个小的重新设计:

package com.android.hello;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class HelloAndroid extends Activity {

  private TextView mTextView = null;

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mTextView = new TextView(this);

    if (savedInstanceState == null) {
       mTextView.setText("Welcome to HelloAndroid!");
    } else {
       mTextView.setText("Welcome back.");
    }

    setContentView(mTextView);
  }
}

我认为这对于最简单的情况来说已经足够了,但无论我如何离开应用程序,它总是以第一条消息来响应。

我确信解决方案就像重写onPause之类的东西一样简单,但我已经在文档中翻了大约30分钟,没有发现任何明显的东西。


当前回答

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

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

浏览所有这些线程,我怀疑很多时候开发人员都在同时讨论这两个不同的问题。。。因此,所有的困惑和“这对我不起作用”的报道。

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

因此,如果您有一个场景,其中在应用程序的“启动”之间应该保持有意义的“用户进度”或状态,则指南是使用onPause和onResume。你必须自己选择并准备一个持久性商店。

但是,有一个非常令人困惑的错误,它使所有这一切变得复杂。详情如下:

当从Eclipse启动应用程序时,活动堆栈在第一次运行期间的行为不正确(#369007463)市场/浏览器应用程序安装程序允许第二个实例脱离应用程序(#36911210)

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

我在这些线程中挣扎了几个小时,才意识到我的主要问题是这个bug,而不是预期的框架行为。一个很好的总结和解决方法(更新:见下文)似乎来自用户@kaciula的回答:

主页键按下行为

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

其他回答

您需要重写onSaveInstanceState(Bundle savedInstanceState),并将要更改的应用程序状态值写入Bundle参数,如下所示:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
  super.onSaveInstanceState(savedInstanceState);
  // Save UI state changes to the savedInstanceState.
  // This bundle will be passed to onCreate if the process is
  // killed and restarted.
  savedInstanceState.putBoolean("MyBoolean", true);
  savedInstanceState.putDouble("myDouble", 1.9);
  savedInstanceState.putInt("MyInt", 1);
  savedInstanceState.putString("MyString", "Welcome back to Android");
  // etc.
}

Bundle本质上是一种存储NVP(“名称-值对”)映射的方法,它将被传递到onCreate()和onRestoreInstanceState(),然后在那里您可以从如下活动中提取值:

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
  super.onRestoreInstanceState(savedInstanceState);
  // Restore UI state from the savedInstanceState.
  // This bundle has also been passed to onCreate.
  boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
  double myDouble = savedInstanceState.getDouble("myDouble");
  int myInt = savedInstanceState.getInt("MyInt");
  String myString = savedInstanceState.getString("MyString");
}

或者从碎片中提取。

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(savedInstanceState);
    // Restore UI state from the savedInstanceState.
    // This bundle has also been passed to onCreate.
    boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
    double myDouble = savedInstanceState.getDouble("myDouble");
    int myInt = savedInstanceState.getInt("MyInt");
    String myString = savedInstanceState.getString("MyString");
}

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

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

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

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

onSaveInstanceState(bundle)和onRestoreInstanceState(bundle)方法仅在旋转屏幕(方向更改)时用于数据持久化。在应用程序之间切换时,它们甚至都不好(因为调用了onSaveInstanceState()方法,但不会再次调用onCreate(捆绑包)和onRestoreInstanceState(捆绑)。要获得更多持久性,请使用共享首选项。阅读这篇文章

基本上有两种方法可以实现这一改变。

使用onSaveInstanceState()和onRestoreInstance State()。在manifestandroid: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); 

}

您可以使用JetPack中的生命周期实时数据和视图模型Handel。参见本参考:

https://developer.android.com/topic/libraries/architecture/livedata