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
  • ThreadMode: MAIN
  • ThreadMode: MAIN_ORDERED
  • ThreadMode: BACKGROUND
  • ThreadMode: ASYNC

如果没有选择默认为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地址

你可能感兴趣的:(EventBus的优美之道)