使用观察者模式解决单 Activity 与多个 Fragment 通信

就目前而言,我所知道的 activity 与 fragment 之间通信方式还是很多的。比如:

  1. Handler 方式
  2. 接口方式
  3. 公有方法
  4. 广播方案
  5. EventBus

稍微分析下这五种方法,Handler 方式是了解了 Handler 的人最容易想到的,但是 Handler 不仅会增加各个模块之间的耦合性,而且只能单向通信,例如如果在 Activity 中实例化 Handler ,那么就只能由 Fragment 向 Activity 发送消息,而如果反过来 Activity 向 Fragment 发送消息则不易实现,既然不能双向,那么 Fragment 之间的通信也就无法实现了。

接口方式是最简单易用的方式,当我确定 Handler 方式无法满足需求的时候,我立刻想到了接口。但当我经过一番实践,发现接口方式仍然不行,如果只是一对一通信,那么接口或许是最好的方案,不仅轻便,而且丝毫不影响性能。但如果是一(Activity)对多(Fragment)通信,那么只能是由 Activity 来实现接口,Fragment 调用该接口,向 Activity 发送消息;反过来就不行了,需要定义很多个接口,每个 Fragment 需要实现不同的接口,然后 Activity 实例化每一个接口来调用,如果有数十个 Fragment 需要向 Activity 发送信息,那么就得让 Activity 实现数十个接口,这得是多么愚蠢的事情,并且使用接口也还是无法实现 Fragment 之间的通信。

至于公有方法,也就是互相调用对方被公开的方法。比如可以在 Fragment 中调用宿主 Activity 中的公有方法,也可以通过宿主 Activity 查找到其他 Fragment ,然后可以在 Activity 中稍作判断,继而再调用本身或者其他 Fragment 中的方法。然而这样会大大增加程序之间的耦合性,不利于以后的维护和扩展。

然后是广播,广播是一种能够面向系统中所有应用程序发送通知的机制,使用广播来完成单一 APP 内部的消息通信,有点杀鸡焉用牛刀的感觉,另外通过广播需要被发送的对象实现序列化接口,会略微影响性能,所以一般不使用广播来进行通信。

最后是 EventBus,EventBus 的核心也是观察者模式,通过订阅事件来收取通知信息,同时通过反射找到对应的方法名来实现消息的通信,肯定也是会对性能有一定的影响(3.0 版本已经不再使用反射,而是通过注解实现,免去了反射查找方法的诸多弊端),如果代码混淆的时候保留对 EventBus 的混淆,则会暴露方法名,给逆向留下口子,可如果强制混淆,又会导致无法通过反射来找到对应的方法。(本段修改于 3 月 22日)

既然说这么多,好像每种方法都不太理想,那还有什么方法呢。我虽然都对以上几种方法做出了一些负面的评价,但并不是说就不用这些方法了。我自己所用的方法,其实是 Handler 与接口的结合。用 Handler 来向 Activity 发送信息,用观察模式中的接口向 Fragment 发送信息。虽然使用 Handler 确实还是会致使 Activity 与 Fragment 之间存在一定的耦合性,但毕竟影响会最小化,如果要扩展或者修改,只需要修改 Fragment 中的代码即可。而接口的使用是完全解耦的。因此,目前还是个比较适合,又相对方便的方法。

Handler 的使用就与正常使用一般,在 Activity 中 new 一个 Handler,当然这个 Handler 不能是 static 的。当 Fragment 需要向 Activity 或者其他 Fragment 发送一个消息或者通知,就可以通过已经绑定的 Handler 来发送消息,消息中会携带一个 key,表示发这条消息的用意是什么,Activity 拿到消息后就会根据 key 来作出响应,可以调用自身的方法,也可以通过『可观察者接口』发出调整通知,当然这条通知也会携带 key 信息,可观察者与观察者中间有一个中转站,当中转站收到来自『可观察者』的更新,就会把更新内容发送到每一个注册到中转站的『观察者』,最后所有实现了『观察者』接口的 Fragment 都会收到更新的消息,然后各自对消息进行判断,作出合理的响应。这就是 Handler 与观察者模式结合的方法。对于我目前的应用架构而言,这是最好的解决的方案。

下面上代码,首先是可观察者接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Created by Alpha on 2017/3/15.
* 可观察接口,通知事件的发出者
*/

public interface Observable{

void registerObserver(Observer observer);

void removeObserver(Observer observer);

void notifyObservers();
}

观察者接口:

1
2
3
4
5
6
7
8
9
10
/**
* Created by Alpha on 2017/3/15.
* 观察者接口,通知事件的接受者
*/

public interface Observer {

void update(String msg);

}

由于我的应用内部之间只需要互相发出 key 通知即可,所以观察者接口里的方法只有一个参数,你也根据自己的需要添加参数,比如 Obkect 或者泛型来增项适应性。
看一下消息管理中心是如何实现的:

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
37
38
39
40
41
42
43
44
45
/**
* Created by Alpha on 2017/3/15.
* 事件处理中心
*/

public class EventManager implements Observable {

/**
* 顺利将 inbox 添加至日程表,通知 inbox 界面删除被点击的 item 并更新显示
*/
public static final String TO_INBOX_FRAGMENT = "delete_and_update_inbox";

private List<Observer> observers;
private String message;

public void publishMessage(String message) {
this.message = message;
notifyObservers();
}

public EventManager(List<Observer> observers) {
this.observers = observers;
}


@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
int i = observers.indexOf(observer);
if (i > 0) {
observers.remove(i);
}
}

@Override
public void notifyObservers() {
for (Observer o : observers) {
o.update(message);
}
}
}

其实就是标准的观察者模式的写法。我在这里内部定义了常量为其他类对 key 的识别提供参考。事实上 Java 内部也是提供观察模式接口的,但是有一点不好的是 Java 内部为了实现观察者可以自由向可观察者取数据,而将可观察者定义为类,而不是接口,因此可扩展性变差,如果可观察者必须集成自其它类,那么就无法使用了,因此还是自己来写个观察者模式吧,反正代码也没多少。

观察者模式写好了,就可以在 Activity 中实例化一个时间管理者来发送消息:

1
2
3
4
5
6
7
8
9
10
11
12
public Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case REQUEST_NEW_AGENDA:
eventManager.publishMessage(EventManager.TO_INBOX_FRAGMENT);
eventManager.notifyObservers();
break;
}
super.handleMessage(msg);
}
};

方便起见,我在观察者接口中定义的方法参数类型为 Message ,这样就跟 Handler 消息机制做到一定程度的适配,不在需要另外定制消息类型。这样一个双向通信的消息机制就完成了,不过想对于我这种混搭风格,之前在网络上查资料的时候,还看到了这篇文章: 使用观察者模式完美解决 activity 与 fragment 通信问题,博主单纯使用观察者模式就实现了双向通信,并没有使用 Handler ,感觉非常厉害。但是说实话,以我现在的水平,最后的观察者模式的内容,我没看懂。。。看来我要走的路还很长啊。


更新于2017年3月16日

昨天写完文章后又自己完整的实现一遍,发现还是有一些点需要说一下:

BaseFragment

按照我昨天的想法,在 MainActivity 中实例化 EventManager 的实例,然后在每个 Fragment 中都需要添加带有 EventManager 参数的构造方法,并且要在每个 Fragment 里进行注册和反注册,这样一来就变得比较冗余了。后来有发现个比较简便的用法,我想大家 Fragment 多了肯定会有 BaseFragment 这个基类吧,那么只需要在 BaseFragment 中进行注册和反注册就可以了,具体的实现类 Fragment 只需要继承基类的对应的构造方法,同时由基类来实现观察者接口就可以完成观察者的注册。这样不需要在每一个实现类中进行接口实现,又省了很多力啊。具体的代码如下:

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
/**
* Created by Alpha on 2017/3/15.
*/

public class BaseFragment extends Fragment implements Observer {
protected EventManager eventManager;
protected Handler handler;

public BaseFragment(EventManager eventManager) {
this.eventManager = eventManager;
eventManager.registerObserver(this);
}

@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
this.handler = activity.mHandler;
}
}

@Override
public void onUpdate(Message msg) {
throw new RuntimeException("must override this method in Observer!");
}

@Override
public void onDestroy() {
super.onDestroy();
eventManager.removeObserver(this);
}
}

我同时在基类里拿到了 MainActivity 的 Handler 和 Eventmanager,并且访问性质为保护类型,方便其子类进行调用。由于接口中的方法在子类中编译器不会自动进行覆写,所以在基类中抛出异常,强制子类去重新实现该方法,也就是按照自己的需要进行消息的接受和判断。这样子类在实现基类 BaseFragment 的时候会被要求继承基类的构造方法,那么就可以拿到 EventManager 的引用,然后底层由基类去进行注册和反注册。因此就需要再 MainActivity 中实例化 Fragment 的时候传入 EventManager 的引用,例如:

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
private void initFragment() {
//预加载所有fragment
fragmentList = new ArrayList<>();
inboxFragment = new InboxFragment(eventManager);
eventFragment = new EventFragment(eventManager);
memoFragment = new MemoFragment(eventManager);
contextFragment = new ContextFragment(eventManager);
finishFragment = new FinishFragment(eventManager);
trashFragment = new TrashFragment(eventManager);
fragmentList.add(inboxFragment);
fragmentList.add(eventFragment);
fragmentList.add(memoFragment);
fragmentList.add(contextFragment);
fragmentList.add(finishFragment);
fragmentList.add(trashFragment);
fragmentManager = getSupportFragmentManager();
transaction = fragmentManager.beginTransaction();
transaction.add(R.id.content_frame, inboxFragment);
transaction.add(R.id.content_frame, eventFragment);
transaction.add(R.id.content_frame, memoFragment);
transaction.add(R.id.content_frame, contextFragment);
transaction.add(R.id.content_frame, finishFragment);
transaction.add(R.id.content_frame, trashFragment);
hideAllFragment(transaction);
transaction.show(inboxFragment);
fragPosition = 0;
transaction.commitAllowingStateLoss();
}

某个具体的 BaseFragmen 实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public InboxFragment(EventManager eventManager) {
super(eventManager);
}

@Override
public void onUpdate(Message msg) {
switch (msg.what) {
case InboxEvent.INBOX_ADD:
showTextDialog(msg.obj.toString());
break;
case InboxEvent.INBOX_UPDATE:
updateInboxData();
break;
}
}