ViewModel 的 Saved State 模块   Android Jetpack 的一部分。

保存 UI 状态 中所述,ViewModel 对象可以处理配置更改,因此您无需担心旋转或其他情况下的状态。但是,如果您需要处理系统触发的进程终止,则可能需要使用 SavedStateHandle API 作为备份。

UI 状态通常存储在 ViewModel 对象中或在其中引用,而不是活动中,因此使用 onSaveInstanceState()rememberSaveable 需要一些样板代码,saved state 模块 可以为您处理这些代码。

使用此模块时,ViewModel 对象通过其构造函数接收 SavedStateHandle 对象。此对象是一个键值映射,允许您写入和检索保存状态中的对象。这些值在系统终止进程后仍然存在,并且可以通过相同的对象获得。

保存的状态与您的任务栈相关联。如果您的任务栈消失,您的保存状态也会消失。这可能发生在强制停止应用、从最近使用的应用菜单中删除应用或重新启动设备时。在这种情况下,任务栈会消失,您无法恢复保存状态中的信息。在 用户触发的 UI 状态关闭 场景中,不会恢复保存的状态。在 系统触发的 场景中,会恢复保存的状态。

设置

Fragment 1.2.0或其传递依赖项Activity 1.1.0开始,您可以将SavedStateHandle作为构造函数参数传递给您的ViewModel

Kotlin

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle state;

    public SavedStateViewModel(SavedStateHandle savedStateHandle) {
        state = savedStateHandle;
    }

    ...
}

然后,您可以在没有任何其他配置的情况下检索 ViewModel 的实例。默认的 ViewModel 工厂会将适当的 SavedStateHandle 提供给您的 ViewModel

Kotlin

class MainFragment : Fragment() {
    val vm: SavedStateViewModel by viewModels()

    ...
}

Java

class MainFragment extends Fragment {
    private SavedStateViewModel vm;

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        vm = new ViewModelProvider(this).get(SavedStateViewModel.class);

        ...


    }

    ...
}

在提供自定义ViewModelProvider.Factory 实例时,您可以通过扩展AbstractSavedStateViewModelFactory来启用 SavedStateHandle 的使用。

使用 SavedStateHandle

SavedStateHandle 类是一个键值映射,允许您通过set()get() 方法将数据写入和检索到保存的状态。

通过使用SavedStateHandle,查询值在进程死亡后得以保留,确保用户在重新创建前后看到相同的一组过滤数据,而无需活动或片段手动保存、恢复并将该值转发回ViewModel

SavedStateHandle 还具有与键值映射交互时可能期望的其他方法

此外,您可以使用可观察的数据持有者从 SavedStateHandle 中检索值。支持的类型列表如下

LiveData

使用getLiveData()从包装在LiveData 可观察对象中的 SavedStateHandle 中检索值。当键的值更新时,LiveData 会接收新值。大多数情况下,值是由于用户交互(例如,输入查询以过滤数据列表)而设置的。然后,可以使用此更新后的值来转换 LiveData

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    val filteredData: LiveData<List<String>> =
        savedStateHandle.getLiveData<String>("query").switchMap { query ->
        repository.getFilteredData(query)
    }

    fun setQuery(query: String) {
        savedStateHandle["query"] = query
    }
}

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle savedStateHandle;
    public LiveData<List<String>> filteredData;
    public SavedStateViewModel(SavedStateHandle savedStateHandle) {
        this.savedStateHandle = savedStateHandle;
        LiveData<String> queryLiveData = savedStateHandle.getLiveData("query");
        filteredData = Transformations.switchMap(queryLiveData, query -> {
            return repository.getFilteredData(query);
        });
    }

    public void setQuery(String query) {
        savedStateHandle.set("query", query);
    }
}

StateFlow

使用getStateFlow()从包装在StateFlow 可观察对象中的 SavedStateHandle 中检索值。当您更新键的值时,StateFlow 会接收新值。大多数情况下,您可能会由于用户交互(例如,输入查询以过滤数据列表)而设置值。然后,您可以使用其他Flow 运算符转换此更新后的值。

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    val filteredData: StateFlow<List<String>> =
        savedStateHandle.getStateFlow<String>("query")
            .flatMapLatest { query ->
                repository.getFilteredData(query)
            }

    fun setQuery(query: String) {
        savedStateHandle["query"] = query
    }
}

实验性 Compose 的状态支持

lifecycle-viewmodel-compose 工件提供了实验性的saveable API,允许在 SavedStateHandle 和 Compose 的Saver 之间进行互操作,以便您可以通过使用自定义 SaverrememberSaveable 保存的任何 State 也可以使用 SavedStateHandle 保存。

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {

    var filteredData: List<String> by savedStateHandle.saveable {
        mutableStateOf(emptyList())
    }

    fun setQuery(query: String) {
        withMutableSnapshot {
            filteredData += query
        }
    }
}

支持的类型

保存在 SavedStateHandle 中的数据与活动或片段的其余savedInstanceState一起作为Bundle保存和恢复。

直接支持的类型

默认情况下,您可以对 SavedStateHandle 调用 set()get(),以获取与 Bundle 相同的数据类型,如下所示

类型/类支持 数组支持
double double[]
int int[]
long long[]
字符串 String[]
byte byte[]
char char[]
CharSequence CharSequence[]
float float[]
Parcelable Parcelable[]
Serializable Serializable[]
short short[]
SparseArray
Binder
Bundle
ArrayList
Size(仅在 API 21+ 中)
SizeF(仅在 API 21+ 中)

如果类没有扩展上述列表中的任何一个,请考虑通过添加@Parcelize Kotlin 注释或直接实现Parcelable来使类可打包。

保存不可打包的类

如果某个类未实现 ParcelableSerializable,并且无法修改为实现其中一个接口,则无法将该类的实例直接保存到 SavedStateHandle 中。

Lifecycle 2.3.0-alpha03开始,SavedStateHandle 允许您通过提供自己的逻辑来保存任何对象,该逻辑使用setSavedStateProvider()方法将对象作为Bundle保存和恢复。SavedStateRegistry.SavedStateProvider是一个接口,它定义了一个唯一的saveState()方法,该方法返回一个包含您要保存的状态的Bundle。当SavedStateHandle准备好保存其状态时,它会调用saveState()以从SavedStateProvider检索Bundle,并为关联的键保存Bundle

考虑一个应用程序的示例,该应用程序通过ACTION_IMAGE_CAPTURE intent从相机应用程序请求图像,并传入一个临时文件作为相机应存储图像的位置。TempFileViewModel封装了创建该临时文件的逻辑。

Kotlin

class TempFileViewModel : ViewModel() {
    private var tempFile: File? = null

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel() {
    }


    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }
}

为了确保如果活动的进程被终止并随后恢复,临时文件不会丢失,TempFileViewModel可以使用SavedStateHandle来持久化其数据。为了允许TempFileViewModel保存其数据,请实现SavedStateProvider并将其设置为ViewModelSavedStateHandle上的提供程序

Kotlin

private fun File.saveTempFile() = bundleOf("path", absolutePath)

class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    private var tempFile: File? = null
    init {
        savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
            if (tempFile != null) {
                tempFile.saveTempFile()
            } else {
                Bundle()
            }
        }
    }

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel(SavedStateHandle savedStateHandle) {
        savedStateHandle.setSavedStateProvider("temp_file",
            new TempFileSavedStateProvider());
    }
    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }

    private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
        @NonNull
        @Override
        public Bundle saveState() {
            Bundle bundle = new Bundle();
            if (tempFile != null) {
                bundle.putString("path", tempFile.getAbsolutePath());
            }
            return bundle;
        }
    }
}

要在用户返回时恢复File数据,请从SavedStateHandle中检索temp_file Bundle。这与saveTempFile()提供的包含绝对路径的Bundle相同。然后,可以使用绝对路径实例化一个新的File

Kotlin

private fun File.saveTempFile() = bundleOf("path", absolutePath)

private fun Bundle.restoreTempFile() = if (containsKey("path")) {
    File(getString("path"))
} else {
    null
}

class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    private var tempFile: File? = null
    init {
        val tempFileBundle = savedStateHandle.get<Bundle>("temp_file")
        if (tempFileBundle != null) {
            tempFile = tempFileBundle.restoreTempFile()
        }
        savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
            if (tempFile != null) {
                tempFile.saveTempFile()
            } else {
                Bundle()
            }
        }
    }

    fun createOrGetTempFile(): File {
      return tempFile ?: File.createTempFile("temp", null).also {
          tempFile = it
      }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel(SavedStateHandle savedStateHandle) {
        Bundle tempFileBundle = savedStateHandle.get("temp_file");
        if (tempFileBundle != null) {
            tempFile = TempFileSavedStateProvider.restoreTempFile(tempFileBundle);
        }
        savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider());
    }

    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }

    private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
        @NonNull
        @Override
        public Bundle saveState() {
            Bundle bundle = new Bundle();
            if (tempFile != null) {
                bundle.putString("path", tempFile.getAbsolutePath());
            }
            return bundle;
        }

        @Nullable
        private static File restoreTempFile(Bundle bundle) {
            if (bundle.containsKey("path") {
                return File(bundle.getString("path"));
            }
            return null;
        }
    }
}

测试中的 SavedStateHandle

要测试将SavedStateHandle作为依赖项的ViewModel,请使用其所需的测试值创建SavedStateHandle的新实例,并将其传递给您正在测试的ViewModel实例。

Kotlin

class MyViewModelTest {

    private lateinit var viewModel: MyViewModel

    @Before
    fun setup() {
        val savedState = SavedStateHandle(mapOf("someIdArg" to testId))
        viewModel = MyViewModel(savedState = savedState)
    }
}

其他资源

有关 ViewModel 的保存状态模块的更多信息,请参阅以下资源。

Codelab