State 状态模式在 Android 多弹窗的应用

序言

最近项目的首页弹窗进行调整,要加几个弹窗,而且还是要按顺序弹出的。原来的只有悬浮窗权限弹窗和存储权限弹窗,用一两个标志位就可以解决了。现在加了隐私协议弹窗和青少年模式弹窗,变成了四个弹窗,如果还是按照原来的方法,即加标志位解决,逻辑机会变得非常复杂,也很容易出 Bug.

经过调研,发现可以用 state 转态模式去解决这个问题。

下面我们先看看 state 转态模式

State 状态态模式

意图

State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类

State 模式与状态机有着类似的地方,都是从一个状态切换到另一个状态

State 模式的类图


图片来源 design-patterns: State

参与者

Context 上下文

  • 定义 Client 感兴趣的接口
  • 维护一个 ConcreteState 子类的实例,这个实例是当前的 state

State 状态

定义接口以封装与 Context 的一个特定状态相关的行为.

ConcreteState 具体状态类

每一个子类实现一个与 Context 的一个状态相关的行为

协作

  1. Context 将状态相关的请求委托给当前的 ConcreteState 对象处理

  2. Context 可以将自身作为一个参数传递给 state, 让 state 可以访问到 Context

  3. Context 是 client 使用的主要接口, 一般情况下 client 不需要直接与 state 打交道

  4. Context 或 ConcreteState 子类都可以决定 next state, 以及设置转态转换的条件

适用性

  1. 一个对象的行为取决于它的状态,并且必须在运行时刻根据状态改变它的行为

  2. 一个操作中含有庞大的分支条件,并且这些分支依赖该对象的状态

实现过程

  1. 确定 Context 上下文

  2. 确定 state 的接口

  3. 继承 state 接口,实现具体 state 类

  4. 在 Context 中,用一个成员变量指向当前的状态,并且提供对外方法可以设置这个值

  1. 初始化一个开始的 State 并传进 context。

State 模式在 Android 中的应用

回到我们文章开头的问题,我们想要把弹窗顺序的弹出,刚好和 state 模式中的状态切换是一致的。 第一个弹窗弹窗后,切换到另一个状态,下个弹窗是否弹出,完全取决于所在的状态。这样就可以减少一堆的标志位判断了。

这样说比较抽象,可以结合下面的例子来看;

需求:

在第一个弹窗之后,点击确认或者取消;
弹窗第二个弹窗,点击第二个弹窗的确认或者取消,弹窗第三个弹窗;
点击第三个弹窗,结束;

StateDialog 的设计

下面是类图

DialogContext 是上下文,用来存储当前 DialogState,在 nextDialogState 方法设置下个状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class DialogContext {

private BaseDialogState mCurrentDialogState;

private Activity mActivity;

public DialogContext(Activity activity) {
mActivity = activity;
}

public void nextDialogState(BaseDialogState baseDialogState) {
mCurrentDialogState = baseDialogState;
mCurrentDialogState.setDialogContext(this); // 将自身作为参数传递给 DialogState
mCurrentDialogState.handle(); // 同时调用 DialogState#handle 方法
}

public Activity getActivity() {
return mActivity;
}

public void onResume() {
if (mCurrentDialogState != null){
mCurrentDialogState.onResume();
}
}
}

BaseDialogState 是做弹窗的基类,提供 nextDialogState 和 handle 方法
在 handle 方法里面进行自身逻辑的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class BaseDialogState {
protected static final String TAG = "DialogState";
protected DialogContext mDialogContext;
protected Activity mActivity;

public void setDialogContext(DialogContext dialogContext) {
mDialogContext = dialogContext;
mActivity = dialogContext.getActivity();
}

// 进行自身逻辑处理
public abstract void handle();

/**
* 设置下一个 state
*/
protected abstract void nextDialogState();

public void onResume(){

}
}

IDialogStateManager 和它的实现类 DialogStateManager 是 Activity 连接 DialogContext 的中介,相当于 Client。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public interface IDialogStateManager {

void init(Activity activity);

void start();

void onResume();

}

// DialogStateManager.java
public class DialogStateManager implements IDialogStateManager{

private DialogContext mDialogContext;

private boolean mIsStarted; // 首次启动

@Override
public void init(Activity activity) {
mDialogContext = new DialogContext(activity);
}

@Override
public void start() {
if (mDialogContext != null && !mIsStarted){
mIsStarted = true;
// 设置第一个 state
mDialogContext.nextDialogState(new DialogOneState());
}
}

@Override
public void onResume() {
mDialogContext.onResume();
}
}

调用过程

在 MainActivity 调用 DialogStateManager,进行管理

调用的时序图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MainActivity extends AppCompatActivity {

private TextView mTvStartDialog;
private IDialogStateManager mDialogStateManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvStartDialog = (TextView) findViewById(R.id.tv_start);

// 创建 DialogStateManager 并进行初始化
mDialogStateManager = new DialogStateManager();
mDialogStateManager.init(this);

mTvStartDialog.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDialogStateManager.start(); // 启动状态
}
});
}

@Override
protected void onResume() {
super.onResume();
mDialogStateManager.onResume();
}
}

最后我们看调用的效果

完整的代码已上传到 github

参考

  • 《设计模式 可复用面向对象软件的基础》第 5 章, 5.8 State (转态)
  • design-patterns: State
yxhuang wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客