美文网首页Android 设计模式安卓 解决方案Android知识
使用观察者模式解决单 Activity 与多个 Fragment

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

作者: AlphaGao | 来源:发表于2017-03-15 21:10 被阅读1430次

    就目前而言,我所知道的 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 与观察者模式结合的方法。对于我目前的应用架构而言,这是最好的解决的方案。

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

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

    本文最早发布于 http://alphagao.com/2017/03/15/using-observer-pattern-deal-event-between-activity-and-fragments/

    相关文章

      网友评论

      • fbdcef9a8b33:eventbus不是反射
        AlphaGao:@安珀 是的,从 3.0 版本开始换成了注解
      • 碧海鱼龙:据说eventbus对性能影响微乎其微,博主可以了解下!
        AlphaGao:@最后的大魔王 好的

      本文标题:使用观察者模式解决单 Activity 与多个 Fragment

      本文链接:https://www.haomeiwen.com/subject/mjyrnttx.html