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


当前回答

解决这个问题的简单快捷方法是使用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);
  }
}

它适用于Activities、Fragments或任何需要在Bundle上序列化其状态的对象(例如,迫击炮的ViewPresenter)

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
}

其他回答

哪些要保存,哪些不保存?

有没有想过,当方向改变时,为什么EditText中的文本会自动保存?这个答案是给你的。

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

实例状态是存储在Bundle对象中的键值对的集合。

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

EditText中的文本在ListView中滚动位置等。

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

例如,GameActivity中的int currentScore

有关保存数据时onSavedInstanceState(Bundle savedinstaneState)的详细信息

@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);默认行为无法工作,即EditText中的文本无法保存。

要选择哪一项来恢复“活动”状态?

 onCreate(Bundle savedInstanceState)

OR

onRestoreInstanceState(Bundle savedInstanceState)

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

有关onRestoreInstanceState(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)。例如,您正在使用App X,突然接到一个电话。移动到调用方应用程序并返回到应用程序X。在这种情况下,将调用onSaveInstanceState(Bundle savedInstanceState)方法。

但如果用户按下后退按钮,请考虑这一点。假设用户不打算返回“活动”,因此在这种情况下,系统不会调用onSaveInstanceState(Bundle savedInstanceState)。重要的是,在保存数据时,您应该考虑所有情况。

相关链接:

演示默认行为Android官方文档。

科特林

必须重写onSaveInstanceState和onRestoreInstanceState以存储和检索要持久化的变量

生命周期图

存储变量

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
}

请注意,根据Activity上的文档,对持久数据使用onSaveInstanceState和onRestoreInstanceState是不安全的。

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

请注意,保存改为onPause()中的持久数据的onSaveInstanceState(捆绑包)因为后者不是生命周期回调,因此不会在描述的每种情况下调用在其文档中。

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

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

在活动被终止之前调用此方法,以便在未来的某个时候,它可以恢复其状态。对于例如,如果活动B在活动A之前启动点活动A被杀死以回收资源,活动A将具有通过此选项保存用户界面的当前状态方法,以便当用户返回活动A时用户界面可以通过onCreate(Bundle)或onRestoreInstanceState(捆绑包)。

使用Android ViewModel和SavedStateHandle持久化可序列化数据

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
        binding.setViewModel(new ViewModelProvider(this).get(ViewModel.class));
        binding.setLifecycleOwner(this);
        setContentView(binding.getRoot());
    }

    public static class ViewModel extends AndroidViewModel {

        //This field SURVIVE the background process reclaim/killing & the configuration change
        public final SavedStateHandle savedStateHandle;

        //This field NOT SURVIVE the background process reclaim/killing but SURVIVE the configuration change
        public final MutableLiveData<String> inputText2 = new MutableLiveData<>();


        public ViewModel(@NonNull Application application, SavedStateHandle savedStateHandle) {
            super(application);
            this.savedStateHandle = savedStateHandle;
        }
    }
}

在布局文件中

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="viewModel"
            type="com.xxx.viewmodelsavedstatetest.MainActivity.ViewModel" />
    </data>

    <LinearLayout xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">


        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:autofillHints=""
            android:hint="This field SURVIVE the background process reclaim/killing &amp; the configuration change"
            android:text='@={(String)viewModel.savedStateHandle.getLiveData("activity_main/inputText", "")}' />

        <SeekBar
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="100"
            android:progress='@={(Integer)viewModel.savedStateHandle.getLiveData("activity_main/progress", 50)}' />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="This field SURVIVE the background process reclaim/killing &amp; the configuration change"
            android:text='@={(String)viewModel.savedStateHandle.getLiveData("activity_main/inputText", "")}' />

        <SeekBar
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="100"
            android:progress='@={(Integer)viewModel.savedStateHandle.getLiveData("activity_main/progress", 50)}' />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="This field NOT SURVIVE the background process reclaim/killing but SURVIVE the configuration change"
            android:text='@={viewModel.inputText2}' />

    </LinearLayout>
</layout>

测试:

1. start the test activity
2. press home key to go home
3. adb shell kill <the test activity process>
4. open recent app list and restart the test activity

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

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

}