EventBus是一个Github上著名的开源事件总线框架,想必很多人都使用过它。它实现了事件订阅者和事件发布者的解耦,让我们更加容易在actvity等组件间传递信息。
我们虽然不喜欢重复造轮子,但是不代表我们不需要了解轮子是怎么造的。
这篇文章通过这个简单的实例,给大家说明EventBus实现的原理,一起来打造一个简单的事件总线框架。如果你明白了这个框架的设计原理,那么EventBus也就相差不大,两者比起来只是后者更加完善和高效。
这也给看源码的朋友带来便利,大家可以先尝试看我这篇文章,再去看EventBus的源码哦!
由于是模仿EventBus写得,所以我也把这个“框架”称为EventBus,先来看一下我们怎么使用这个框架。
现在我们实现的一个效果是,有两个acivity如图
MainActivity:当点击"click"按钮的时候,就会跳转到secondActivity,而secondActivity里面有两个按钮,点击以后,可以改变MainActivity里面TextView的文字
改变以后如图:
这其实就是一个消息传递的过程,假设我们用EventBus,下面我们来看一下,代码是怎么写的
public class MainActivity extends FragmentActivity {
Button btn;
TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getInstance().register(this);
btn = (Button) findViewById(R.id.btn);
text = (TextView) findViewById(R.id.text);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,SecondActivity.class));
}
});
}
public void onEvent(Info i){
Log.i("cky", i.msg);
}
public void onEventMain(Info i){
text.setText(i.msg);
}
public void onEventMain(Info2 i){
text.setText(text.getText()+i.msg);
}
}
然后又两者函数,一个是onEvent(),这个方法里面的代码,会在一个子线程中执行
一个是onEventMain(),这个方法里面的代码,会在UI线程执行。在这里,我们改变了TextView中的文字。
使用过EventBus的朋友,现在看来使用方式是不是跟EventBus很像(我就是想实现EventBus的效果啊)。EventBus对事件在不同线程中处理,有四种方式,但是我们这里只写了简单的两者,主线程和非主线程,这样代码更加简单,而且原理是一样的,会写两个,就会写多个。
然后SecondActivity里面,大家一定知道是怎么回事,肯定是在点击里面调用了post()方法
public class SecondActivity extends Activity {
Button btn2;
Button btn3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
btn2 = (Button) findViewById(R.id.btn2);
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getInstance().post(new Info("信息1"));
}
});
btn3 = (Button) findViewById(R.id.btn3);
btn3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getInstance().post(new Info2("信息2"));
}
});
}
}
大家可能会好奇到底怎么样用简单的代码,实现这样的效果,下面我带大家看我的代码。!
先看EventBus
public class EventBus {
HashMap,ArrayList> subscriptionsByEventType =
new HashMap,ArrayList>();
MainThreadHandler mainThreadHandler = new MainThreadHandler(this,Looper.getMainLooper());
AsyThreadHandler asyThreadHandler = new AsyThreadHandler(this);
private final static EventBus instance = new EventBus();
public static EventBus getInstance(){
return instance;
}
private EventBus(){};
}
有几个地方:
1,将构造函数设为私有,使用了getInstance()来给外界提供EventBus实例,其实就是一个单例模式
2,有几个复杂的属性,首先是subscriptionsByEventType,这是一个map,key代表某个bean类。
使用过EventBus都知道,我们在传递信息的时候,要传递一个实体类,例如我上面的例子,就是Info这个类。
value值是一个ArrayList
剩下两个mainThreadHandler和asyThreadHandler,顾名思义就是分别用来处理主线程和子线程的,都是我创建的类。
看到这里可能大家有点糊涂,出现了三个类,别急,我一定会解释清楚。现在先让我们来看非常重要的register()方法。
public void register(Object subscriber){
Class> clazz = subscriber.getClass();//获取订阅者的类型
Method[] methods = clazz.getMethods();//获取该类的所有方法
for(Method m:methods){//遍历方法
String name = m.getName();//获取方法名
if(name.startsWith("onEvent")){//判断方法名是否以"onEvent"开头
Class>[] params = m.getParameterTypes();//获取该方法的参数类型
ArrayList arr;
if(subscriptionsByEventType.containsKey(params[0])){
arr = subscriptionsByEventType.get(params[0]);
}else{
arr = new ArrayList();
}
int len = name.substring("onEvent".length()).length();//截取方法除"onEvent"部分
Subscription sub;
if(len==0){//如果剩余长度为0,说明是子线程中执行
sub = new Subscription(subscriber,new SubscriberMethod(m,params[0],0));
}else{//否则,在主线程执行
sub = new Subscription(subscriber,new SubscriberMethod(m,params[0],1));
}
arr.add(sub);
subscriptionsByEventType.put(params[0],arr);
}
}
}
然后判断subscriptionsByEventType是否有以这些参数类型为key的数据,如果没有,新建一个ArrayList
然后我们先看Subscription
public class Subscription {
Object subscriber;
SubscriberMethod SubscriberMethod;
public Subscription(Object subscriber, SubscriberMethod SubscriberMethod) {
this.subscriber = subscriber;
this.SubscriberMethod = SubscriberMethod;
}
}
它代表一个订阅,拥有subsriber,也就是订阅者
还有一个SubscriberMethod,这是订阅方法类
public class SubscriberMethod {
Method m;
int type;
public SubscriberMethod(Method m, Class> param, int type) {
this.m = m;
this.type = type;
}
}
它有一个Method m属性,就是我们注册的方法,另外一个type就是标记,0代表m在子线程中执行,其余代表在主线程中执行。
所以总的来说register()通过遍历订阅者,找到订阅方法(onEvent(),onEventMain()),将方法包装成SubscriberMethod类,再将SubscriberMethod和订阅者一起,
包装成Subscription类。
而subscriptionsByEventType根据参数类型为key,所谓参数类型,在例子中就是Info.class和info2.class
为什么我们要用参数类型为键呢?我们来想,以后我们在post的时候,是这样调用post的:
EventBus.getINstance().post(new Info("信息"));
我们要传进去一个参数,而EventBus的任务就是,调用所有注册了这个参数类型的订阅方法。
所有显然,我们要根据参数类型,找到这些方法,这是key是参数类型的原因了。
Ok,register()方法下来,最重要的一件事就是我们得到了subscriptionsByEventType
接下来我们就可以看post方法了
public void post(Object event){
Class> clazz = event.getClass();
ArrayList arr = subscriptionsByEventType.get(clazz);
for(Subscription sub:arr){//遍历订阅
if(sub.SubscriberMethod.type==0){
asyThreadHandler.post(sub,event);
}else{
mainThreadHandler.post(sub,event);
}
}
}
对于Subscription,它有我们订阅类的所有信息。
首先根据type判断是在主线程还是子线程执行,然后调用一开始讲到的两个类的实例就好了。
我们先看asyThreadHandler,这个会在子线程执行
public class AsyThreadHandler {
private EventBus eventBus;
AsyThreadHandler(EventBus eventBus) {
this.eventBus = eventBus;
}
public void post(final Subscription sub, final Object event){
new Thread(){
@Override
public void run() {
eventBus.invoke(sub,event,sub.SubscriberMethod.m);
}
}.start();
}
}
代码很简单,其实它的post方法,就是启用了一个线程,然后在线程里面,调用了EventBus的invoke()方法。
我们看这个方法
public void invoke(final Subscription sub, final Object event,Method m){
try {
m.invoke(sub.subscriber,event);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
这样我们就在子线程中调用了这个方法了,相当于MainActivity主动调用这个方法。
那么在主线程中呢,大同小异
public class MainThreadHandler extends Handler{
private final EventBus eventBus;
MainThreadHandler(EventBus eventBus, Looper looper) {
super(looper);
this.eventBus = eventBus;
}
@Override
public void handleMessage(Message msg) {
EventBus.getInstance().invoke(sub,event,sub.SubscriberMethod.m);
}
Subscription sub;
Object event;
public void post(Subscription sub, Object event){
this.sub = sub;
this.event = event;
sendEmptyMessage(0);
}
}
可以看到继承了handler,表明在主线程中调用。
post方法传递了一个空message,在handlerMessage()方法里面,又是调用了EventBus的invoke()方法。
殊途同归,最后都是调用EventBus的invoke()方法,不过一个在子线程中调用,一个在主线程中调用。
就这样,我们轻而易举得实现了EventBus的基本功能哦!
是不是觉得代码很简单,其实上面的代码,已经将EventBus框架的思路将清楚了,大家明白这思路,再看EventBus就容易多了。
通过学习,我们也注意到,EventBus(我是这github上面的)存在缺点
1,就是订阅方法名固定,就像我上面,只能用onEvent(),onEventMain()方法,使用者不能自己规定方法名
2,根据参数类型来调用方法,也就是post(Info i),所有的订阅了Info类的订阅方法都会被调用。很多时候我们不希望这样,而是希望post给特定的订阅方法。
OK,代码就讲到这里,不明白的地方可以留言问我,或者直接看源码。
源码地址下载:http://download.csdn.net/detail/kangaroo835127729/8992551
转载请注明出处!http://blog.csdn.net/crazy__chen/article/details/47425779