Android 小话设计模式 之 从观察者到EventBus和RxJava

  设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

前言

软件工程中的23种设计模式,相信大家耳熟能详,在大Java中完美适用,直接上图(自己画的):


Design Pattern.png

  这里主要给大家介绍一下神奇且常见的观察者模式,及其引申出的一些强大用法:CallBack,EventBus,RxJava。

观察者模式

观察者模式(Observer)完美的将观察者和被观察的对象分离开,定义了对象间的一种一对多的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
  简单的来说,打个比方,就类似于狱警时时刻刻盯着犯人,犯人作出一点小动作,那么狱警便立马做出回应,反之则相安无事。在Android中的点击事件监听setOnClickListener( )便完成对接这一概念,即:观察者将自己注册到被观察对象中,被观察对象将观察者存放在一个容器里。被观察对象发生了某种变化时,从容器中得到所有注册过的观察者,将变化通知观察者从而达到一个时时监听的作用。
  观察者(Observer)模式,又叫做发布订阅(Publish/Subscribe)模式,下面给出一张经典的观察者模式的关系对应图,看完大致都明白了吧~


Observer Pattern.png

强大的应用之处

观察者模式相信大家都有了一个了解,在Android中最基本的实现就是对点击事件的监听了,相信网上关于观察者与setOnClickListener( )的关系介绍有很多很多咯,这里介绍两种更高级的应用场景,看完你便会完美体会到其强大之处!


Observer Relation.png
  • EventBus

EventBus是Android下高效的发布/订阅事件总线机制。是基于JVM内部的数据传输系统,其核心对象为Event和EventHandler。作用是可以代替传统的Intent,Handler,Broadcast或接口函数在Fragment,Activity,Service,线程之间传递消息,优点是开销小,代码更优雅,以及将发送者和接收者解耦。
  看完概念是不是感觉强大的EventBus已经无所不能了!那么就从源码层面分析下其强大的原因:
  汗!在看源码之前还是先看一下用法吧:

0、Prepare

  Gradle:
        compile 'org.greenrobot:eventbus:3.0.0'
  Maven:
        <dependency>
              <groupId>org.greenrobot</groupId>
              <artifactId>eventbus</artifactId>
              <version>3.0.0</version>
        </dependency>

1、定义关系:写一个类描述被观察者和观察者之间统一事件关系,可以为空,也可以加上参数并通过get/set方法绑定。

  public class Event { 
        public Event() {
              // TODO
        } 
  }

2、接收事件页面:注册与销毁。

  @Override  
  protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        // 注册EventBus  
        EventBus.getDefault().register(this);  
  }

  // TODO

  @Override  
  protected void onDestroy(){  
        super.onDestroy();  
        // 销毁EventBus  
        EventBus.getDefault().unregister(this);
  }  

3、发布事件页面:将之前定义在事件关系的类发布。

  EventBus.getDefault().post(new Event());

4、接收事件页面:重写onEvent...( )方法,通过注解的方法鉴别类型,接收消息(4种方式)。

  @Subscribe(threadMode = ThreadMode.MainThread) 
  //在ui线程执行 
  public void onEvent(Event event) { 
          // TODO
  }

  @Subscribe(threadMode = ThreadMode.BackgroundThread) 
  //在后台线程执行 
  public void onEvent(Event event) { 
        // TODO
  }

  @Subscribe(threadMode = ThreadMode.Async) 
  //强制在后台执行 
  public void onEvent(Event event) { 
        // TODO
  }

  @Subscribe(threadMode = ThreadMode.PostThread) 
  //默认方式, 在发送线程执行 
  public void onEvent(Event event) { 
        // TODO
  }

综上,一个Event就对应一个关系,在发布与接收者之间通过该标识的Event就可以自动识别作出响应。参考一下3.0之前在接收页面的接收方法,对应上面的(注:在EventBus3.0已经将原有的方法改版):

   - onEventMainThread():
        不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行。
        在Android中只能在UI线程中更新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。

  - onEventBackgroundThread():
        如果事件是在UI线程中发布出来的,那么onEventBackground就会在子线程中运行。
        如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。

  - onEventAsync():
        无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync()。

  - onEvent():
        发布事件和接收事件线程在同一个线程。
        使用这个方法时,在onEvent方法中不能执行耗时操作。
        如果执行耗时操作容易导致事件分发延迟。

下面简单分析一下源码,参考了鸿洋大神的帖子,从register( )方法入手,先看getDefault( ):

  /** Convenience singleton for apps using a process-wide EventBus instance. */
  public static EventBus getDefault() {
        if (defaultInstance == null) {
              synchronized (EventBus.class) {
                    if (defaultInstance == null) {
                          defaultInstance = new EventBus();
                    }
              }
        }
        return defaultInstance;
  }

使用了双重判断的方式,防止并发的问题,还能极大的提高效率。再看一下普通的register( )方法:

  public void register(Object subscriber) {
        register(subscriber, DEFAULT_METHOD_NAME, false, 0);
  }

  public void register(Object subscriber, int priority) {
        register(subscriber, DEFAULT_METHOD_NAME, false, priority);
  }

  public void registerSticky(Object subscriber) {
        register(subscriber, DEFAULT_METHOD_NAME, true, 0);
  }

  public void registerSticky(Object subscriber, int priority) {
        register(subscriber, DEFAULT_METHOD_NAME, true, priority);
  }

再点进去看看内部核心的register(subscriber...)方法:

  private synchronized void register(Object subscriber, String methodName, boolean sticky, int priority) {
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(), methodName);
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
              subscribe(subscriber, subscriberMethod, sticky, priority);
        }
  }

即遍历该类内部所有方法,然后根据methodName去匹配,匹配成功的封装成SubscriberMethod,最后返回一个List。继续深入的subscribe( )方法在这里就不贴代码了,有兴趣的童鞋可以自己无限往下点点看。
  register( )方法的核心便是:扫描了所有的方法,把匹配的方法最终保存在subscriptionsByEventType( )中,该方法中的eventType是我们方法参数的Class,Subscription中则保存着subscriber,subscriberMethod(method, threadMode, eventType),priority,包含了执行改方法所需的一切。
  其余的一些post( )方法等其实的本质与上方类似,关于整个EventBus的流程可以大致梳理如下:EventBus负责订阅对象与事件的管理,比如注册、注销以及发布事件等。在初始时将某个对象注册到EventBus中,EventBus会遍历该对象class中的所有方法,把参数数量为1且用了Subscriber注解标识的函数管理起来,以事件类型和订阅函数Subscriber的tag构建一个EventType作为一种事件类型,某个事件类型对应有一个接收者列表。当有事件发布时,EventBus会根据发布的事件类型与tag构建EventType,然后找到对应的订阅者列表,并且将这些事件投递给所有订阅者。SubscriberMethodHunter负责查找合适的EventType,而EventHandler则负责将这些订阅函数执行到相应的线程中。

  • RxJava

Reactive Extensions编程简称Rx编程,又叫响应式编程,提供一致的编程接口,帮助开发者更方便的处理异步数据流,使软件开发更高效、更简洁。其中,对于异步错误处理,传统的try/catch没办法处理异步计算,Rx提供了合适的错误处理机制轻松使用并发。而Rx的Observables和Schedulers让开发者可以摆脱底层的线程同步和各种并发问题。
  关于RxJava的基本实现很简单,看如下三步:

1、创建一个Observable对象,直接调用Observable.create( ):

  Observable<String> myObservable = Observable.create(
        new Observable.OnSubscribe<String>() {
              @Override
              public void call(Subscriber<? super String> sub) {
                    sub.onNext("Hello, world!");
                    sub.onCompleted();
              }
        }
  );

2、创建一个Subscriber来处理Observable对象发出的字符串:

  Subscriber<String> mySubscriber = new Subscriber<String>() {
        @Override
        public void onNext(String s) { 
              System.out.println(s); 
        }

        @Override
        public void onCompleted() {
              // TODO
        }

        @Override
        public void onError(Throwable e) {
              // TODO
        }
  };

3、通过subscribe函数就可以将myObservable对象和mySubscriber对象关联绑定起来,完成subscriber对observable的订阅:

  myObservable.subscribe(mySubscriber);

当然,强大的RxJava提供了一些方法使代码简化,如just( ),map( ),Action1类等等,如:

  Observable.just("Hello, world!")
        .subscribe(new Action1<String>() {
              @Override
              public void call(String s) {
                    System.out.println(s);
              }
        });

  Observable.just("Hello, world!")
        .map(new Func1<String, String>() {
              @Override
              public String call(String s) {
                    return s + "Ivor";
              }
        }).subscribe(new Action1<String>() {
              @Override
              public void call(String s) {
                    System.out.println(s);
              }
        });

关于RxJava的基本使用大致就介绍到这里,后续关于RxJava和Retrofit以及RxAndroid的引申下期会更新,大家也可以提前参考一下福生宝宝的RxJava 的使用与理解(二)

  • EventBus VS RxJava

RxJava要比EventBus的应用更广泛,EventBus仅仅是作为一种消息的传递工具,但是RxJava里面几乎可以做任何事情,只是对于简单的业务来说可能有些冗余,EventBus相对来说更加轻量,EventBus有个缺点就是凡是使用了EventBus的类都不能进行混淆了,否则Evnetbus就找不到OnEvent方法了。

尾声

关于观察者模式的应用有很多,这里先介绍这么两种强大的库,供大家参考学习~~请将强大的Observer与Listener发挥到极致:)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容