在Android应用程序中保存状态

今天,我想与大家分享开发Android应用程序时保持状态的另一种方法。 随时可以终止我们在后台的应用程序并不是什么秘密,而随着积极的节能措施的问世 ,这个问题变得更加紧迫-您好Oreo 。 同样,没有人取消手机上的配置更改:方向,语言更改等。 为了从后台打开应用程序并在最后状态下显示界面,我们需要注意保存它。 哦,这个onSaveInstanceState

onSaveInstanceState

他给我们带来了多少痛苦。

稍后将使用Clean AchitectureDagger2给出示例,因此请为此做好准备:)

根据任务维护状态的问题可以通过几种方式解决:

  1. 将主要数据保存主机(活动,片段)的onSaveInstanceState中 -例如页面标识符,用户等。 我们需要进行初始数据采集和页面显示。
  2. 将收到的数据保存在资源库中的交互式程序中(SharedPreference,数据库。
  3. 重新创建活动时,使用rethein片段保存和恢复数据。

但是,如果我们需要恢复ui的状态以及接口对用户操作的当前反应,该怎么办? 为简单起见,让我们使用一个真实的示例来考虑解决此问题的方法。 我们有一个登录页面-用户输入数据,单击按钮,然后他接到来电。 我们的应用程序进入后台。 系统杀死了他。 听起来很吓人,不是吗?)

用户返回到应用程序,他应该看到什么? 至少,继续登录操作并显示进度。 如果该应用程序在调用主机的onDestroy方法之前成功登录,则用户将看到导航到该应用程序的“开始”屏幕。 使用状态机可以轻松解决此问题。 Yandex的很好的报告 。 在同一篇文章中,我将尝试分享对这份报告的看法。

现在有一点代码:

基态

public interface BaseState<VIEW extends BaseView, OWNER extends BaseOwner> extends Parcelable{ /** * Get name * * @return name */ @NonNull String getName(); /** * Enter to state * * @param aView view */ void onEnter(@NonNull VIEW aView); /** * Exit from state */ void onExit(); /** * Return to next state */ void forward(); /** * Return to previous state */ void back(); /** * Invalidate view * * @param aView view */ void invalidateView(@NonNull VIEW aView); /** * Get owner * * @return owner */ @NonNull OWNER getOwner(); /** * Set owner * * @param aOwner owner */ void setOwner(@NonNull OWNER aOwner); } 

基础管理员

 public interface BaseOwner<VIEW extends BaseView, STATE extends BaseState> extends BasePresenter<VIEW>{ /** * Set state * * @param aState state */ void setState(@NonNull STATE aState); } 

BasestateImpl

 public abstract class BaseStateImpl<VIEW extends BaseView, OWNER extends BaseOwner> implements BaseState<VIEW, OWNER>{ private OWNER mOwner; @NonNull @Override public String getName(){ return getClass().getName(); } @Override public void onEnter(@NonNull final VIEW aView){ Timber.d( getName()+" onEnter"); //depend from realization } @Override public void onExit(){ Timber.d(getName()+" onExit"); //depend from realization } @Override public void forward(){ Timber.d(getName()+" forward"); onExit(); //depend from realization } @Override public void back(){ Timber.d(getName()+" back"); onExit(); //depend from realization } @Override public void invalidateView(@NonNull final VIEW aView){ Timber.d(getName()+" invalidateView"); //depend from realization } @NonNull @Override public OWNER getOwner(){ return mOwner; } @Override public void setOwner(@NonNull final OWNER aOwner){ mOwner = aOwner; } 

在我们的案例中,状态所有者将是演示者。

查看登录页面,可以区分三种独特的状态:

LoginInitStateLoginProgressingStateLoginCompleteState

因此,现在考虑在这些状态下会发生什么。

LoginInitState我们具有字段验证功能,如果验证成功,登录按钮将变为活动状态。

LoginProgressingState中发出一个Login请求,保存一个令牌,并作出其他请求以启动应用程序的主要活动。

LoginCompleteState导航到应用程序的主屏幕。

有条件的状态下的过渡可以显示在下图中:

登录状态图

如果登录操作在LoginCompleteState状态下成功 ,并且LoginInitState失败 ,则退出LoginProgressingState状态。 因此,当我们的观点脱离时,我们就可以完全确定演示者的状态。 我们必须使用标准的android机制onSaveInstanceState保存此状态。 为了使我们能够做到这一点,所有登录状态都必须实现Parcelable接口。 因此,我们正在扩展基本接口BaseState

接下来,我们有一个问题,如何将该状态从演示者转发到主持人? 最简单的方法是向演示者请求来自主机的数据,但是从体系结构的角度来看,这看起来不太好。 因此保留碎片会为我们提供帮助。 我们可以为缓存创建一个接口,并在此片段中实现它:

 public interface Cache{ /** * Save cache data * * @param aData data */ void saveCacheData(@Nullable Parcelable aData); @Nullable Parcelable getCacheData(); /** * Check that cache exist * * @return true if cache exist */ boolean isCacheExist(); } 

接下来,我们将缓存片段注入到交互器的构造函数中,例如Cache。 我们在集成器中添加方法以获取状态并将其保存在缓存中。 现在,随着演示者状态的每次更改,我们都可以将状态保存在交互器中,然后交互器又保存在缓存中。 一切都变得非常合乎逻辑。 首次加载主机时,演示者从交互器接收状态,交互器又从缓存接收数据。 这是演示者中状态更改方法的外观:

 @Override public void setState(@NonNull final LoginBaseState aState){ mState.onExit(); mState = aState; clearDisposables(); mState.setOwner(this); mState.onEnter(getView()); mInteractor.setState(mState); } 

我想指出这一点-通过缓存保存数据可以针对任何数据完成,而不仅限于状态。 您可能需要制作自己的独特代码段缓存来存储当前数据。 本文介绍了一种通用方法。 我还要指出,有关情况非常夸大。 在生活中,您必须解决更为复杂的问题。 例如,在我们的应用程序中,将三个页面组合在一起:登录,注册,密码恢复。 在这种情况下,状态图如下:

实际项目中的状态图

结果,使用本文中描述的状态模式和方法,我们能够使代码更具可读性和可维护性。 重要的是还原应用程序的当前状态。

完整的代码可以在存储库中查看。

Source: https://habr.com/ru/post/zh-CN420023/


All Articles