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

1,394 阅读8分钟
原文链接: www.jianshu.com

就目前而言,我所知道的 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 内部也是通过反射来实现消息的通信,肯定也是会对性能有一定的影响。

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

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

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

/**
 * Created by Alpha on 2017/3/15.
 * 可观察接口,通知事件的发出者
 */

public interface Observable{

    void registerObserver(Observer observer);

    void removeObserver(Observer observer);

    void notifyObservers();
}

观察者接口:

/**
 * Created by Alpha on 2017/3/15.
 * 观察者接口,通知事件的接受者
 */

public interface Observer {

    void update(String msg);

}

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

/**
 * 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 中实例化一个时间管理者来发送消息:

    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 只需要继承基类的对应的构造方法,同时由基类来实现观察者接口就可以完成观察者的注册。这样不需要在每一个实现类中进行接口实现,又省了很多力啊。具体的代码如下:

/**
 * 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 的引用,例如:

    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 实现类:

    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;
        }
    }

本文最早发布于 alphagao.com/2017/03/15/…