EventBus的优美之道

<初见>
项目开发常常遇到子线程更新UI,但我有点厌烦了繁琐的Handler和runOnUiThread,于是乎邂逅了EventBus。

EventBus主要是做什么的呢?

主要功能是替代Intent、Handler、BroadCast在各组件、线程之间传递消息。他的优点是开销小,代码简洁,解耦代码。当然万事都有优略,EventBus也存在着一个观察者的通病:包含大量的接口。

先来个官宣

simplifies the communication between components
decouples event senders and receivers
performs well with Activities, Fragments, and background threads
avoids complex and error-prone dependencies and life cycle issues
makes your code simpler
is fast
is tiny (~50k jar)
is proven in practice by apps with 100,000,000+ installs
has advanced features like delivery threads, subscriber priorities, etc.

事件总线图.png

EventBus的使用步骤

  1. 首先在你的项目中添加eventbus的依赖
    implementation 'org.greenrobot:eventbus:3.1.1'
  1. 定义事件:
public class MessageEvent {
  public final String message;
  public MessageEvent(String message) {
      this.message = message;
  }
}
  1. 准备订阅者:声明并注释您的订阅方法,可选择指定一个线程模式
    注意:@subscribe注释的是公共 的方法
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent messageEvent) {
        content.setText(messageEvent.message);
    }

注册和注销您的订阅者。例如,在Android上,活动和片段通常应根据其生命周期进行注册:

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (EventBus.getDefault().isRegistered(this)) {
            //注销
            EventBus.getDefault().unregister(this);
        }
    }
  1. 最后发布活动:
    EventBus.getDefault().post(new MessageEvent("你要发布的信息"));

好的,在此我先举个网上已经炒熟的栗子

  • 这是一个简单的注册模块的实现,就两个页面,首先是一个登录页面,里面有个注册的按钮,点击后进入到注册页面,注册完信息后点击提交,则回到登录页面,并把之前的注册信息显示出来,功能比较简单,直接上代码。
public class LoginActivity extends AppCompatActivity {
    private Button btn_register;
    private TextView content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        content = findViewById(R.id.content);
        btn_register = findViewById(R.id.btn_register);
        //注册
        EventBus.getDefault().register(this);
        btn_register.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               //跳转到注册页面
                Intent intent = new Intent(LoginActivity.this, RegisterActivity.class);
                startActivity(intent);
            }
        });
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(RegisterEvent registerEvent) {
       //返回注册的信息并显示在布局中
        content.setText(" Name:" + registerEvent.userBean.getUserName()
                + "\n Password:" + registerEvent.userBean.getUserPassword());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (EventBus.getDefault().isRegistered(this)) {
            //注销
            EventBus.getDefault().unregister(this);
        }
    }
}
  • 注册页面只有2个输入框(分别输入用户名和密码)和一个提交按钮,布局内容我就不显示了。
public class RegisterActivity extends AppCompatActivity {
    private EditText name, password;
    private Button btn_ok;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_register);
        initView();
        btn_ok.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                UserBean userBean = new UserBean();
                userBean.setUserName(name.getText().toString().trim());
                userBean.setUserPassword(password.getText().toString().trim());
                EventBus.getDefault().post(new RegisterEvent(userBean));
                finish();
            }
        });
    }

    private void initView() {
        name = findViewById(R.id.name);
        password = findViewById(R.id.password);
        btn_ok = findViewById(R.id.btn_ok);
    }
}

EventBus用法之:粘性事件

EventBus的粘性事件是什么

During registration all sticky subscriber methods will immediately get the previously posted sticky event:

在注册期间,所有粘性订阅者方法将立即获得先前发布的粘性事件(也就是说我们可以先发布粘性事件,然后再注册粘性订阅者,照样可以获取先前发布的粘性事件)
如果不明白,没关系,往下看

回顾下我们刚才举的例子,我们是先订阅了事件,然后再发布信息,可是有时候我们希望先发布消息,再订阅,类似Intent带数据的页面跳转,在第一个页面发送一条数据,跳转到第二个页面的时候才做数据处理。很明显如果使用上面的方式是无法实现的, 主要是因为第一个页面发送数据的时候,第二个页面根本就还没有生成(也就是说发布者已经发了数据,但订阅者还未生成)怎么办呢?此时我们就可以使用EventBus的粘性事件。
粘性事件的发送(只是把post()替换成了postSticky())

 EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));

粘性订阅者

    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
    public void onEvent(MessageEvent event) {
       // Now do something with it
    }

举个例子呗
创建一个FirstActivity,里面只做一件事情: 发布一个粘性事件并跳转到SecondActivity。

public class FirstActivity extends AppCompatActivity {
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);
        button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));
                Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }
}

此时我们在创建一个SecondActivity,注册一个粘性订阅者,获取FirstActivity中传过来的数据,并显示到布局中

public class SecondActivity extends AppCompatActivity {
    private TextView content;

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

    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
    public void onEvent(MessageEvent event) {
       //获取FirstActivity中 发布的消息并显示在文本中
        content.setText(event.message);
    }

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

主动取消粘性事件的传递:

MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
// Better check that an event was actually posted before
if(stickyEvent != null) {
    // "Consume" the sticky event
    EventBus.getDefault().removeStickyEvent(stickyEvent);
    // Now do something with it
}

比如我们在SecondActivity又做了一次跳转,跳转到第三个页面ThirdActivity,理论上我们只需要在ThirdActivity也注册同样的粘性订阅者就可以再次接收到FirstActivity中传过来的数据,而此时如果我们在SecondActivity的订阅者中设置取消粘性事件,那么ThirdActivity就算注册粘性订阅者也无法再次接收到FirstActivity中传过来的数据了。

    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
    public void onEvent(MessageEvent event) {
        content.setText(event.message);
        //删除粘性事件,以便它们不再被传递
        MessageEvent stickyEvent = EventBus.getDefault().getStickyEvent(MessageEvent.class);
        if(stickyEvent!=null){
            EventBus.getDefault().removeStickyEvent(stickyEvent);
        }
    }

好了,简单的粘性事件的例子已经完成了,或许有人会说,这个我用Intent+Bundle 也可以实现,为什么要这么复杂的用EventBus,是的,但如果是一个Activity要同时对多个Activity或者Fragment传送信息,此时用EventBus的粘性事件是不是就简单了很多。

使用EventBus实现子线程中更新UI

如果说到子线程更新UI,你的脑子里浮现的是繁琐的Handler,runOnUiThread或者是View.post(),那么恭喜你,现在你又多了一个选择,而且更加简洁明了。

EventBus can handle threading for you: events can be posted in threads different from the posting thread. A common use case is dealing with UI changes. In Android, UI changes must be done in the UI (main) thread. On the other hand, networking, or any time consuming task, must not run on the main thread. EventBus helps you to deal with those tasks and synchronize with the UI thread (without having to delve into thread transitions, using AsyncTask, etc).

EventBus可以为您处理线程:事件可以发布在与发布线程不同的线程中。一个常见的用例是处理UI更改。在Android中,UI更改必须在UI(主)线程中完成。另一方面,网络或任何耗时的任务不得在主线程上运行。EventBus帮助您处理这些任务并与UI线程同步(无需深入研究线程转换,使用AsyncTask等)。

在创建订阅者的时候,我们可以使用以下几种不同的线程模式

如果没有选择默认为ThreadMode: POSTING

    @Subscribe()
    public void onMessageEvent(MessageEvent messageEvent) {
        content.setText(messageEvent.message);
    }
    @Subscribe(threadMode = ThreadMode.POSTING)
    public void onMessageEvent(MessageEvent messageEvent) {
        content.setText(messageEvent.message);
    }

关于以上几种线程模式,我就不再做过多的介绍,需要了解的朋友可直接点击进行官方查看。
接下来我们就以ThreadMode: MAIN模式来实现简单的子线程更新UI
先来看下官网对于ThreadMode: MAIN的介绍:

Subscribers will be called in Android’s main thread (sometimes referred to as UI thread). If the posting thread is the main thread, event handler methods will be called directly (synchronously like described for ThreadMode.POSTING). Event handlers using this mode must return quickly to avoid blocking the main thread.

很明显,如果我们选择在子线程中发布消息,并将订阅者设置为ThreadMode: MAIN模式,那么订阅者将自动切换回Android的主线程中进行调用,此时我们就可以在里面直接更新UI了。
举个简单的栗子:

public class MainActivity extends AppCompatActivity {
    private TextView content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        content = findViewById(R.id.content);
        //注册
        EventBus.getDefault().register(this);
        //启动一个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3*1000);
                    EventBus.getDefault().post(new MessageEvent("你好,我可以更新UI吗!"));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void UpdateUI(MessageEvent messageEvent) {
        content.setText(messageEvent.message);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (EventBus.getDefault().isRegistered(this)) {
            //注销
            EventBus.getDefault().unregister(this);
        }
    }

代码中,创建了一个关于修改TextView 中内容的方法UpdateUI(),并设置threadMode = ThreadMode.MAIN(在主线程中进行调用)

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void UpdateUI(MessageEvent messageEvent) {
        content.setText(messageEvent.message);
    }

然后开启一个简单的子线程,并让子线程在休眠3秒后,发布一个消息"你好,我可以更新UI吗!"

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3*1000);
                    EventBus.getDefault().post(new MessageEvent("你好,我可以更新UI吗!"));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

到此一个简单的子线程更新UI已经实现了,是不是简洁很多。
GitHub地址

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

推荐阅读更多精彩内容