浅入浅出Android EventBus库


#Android#


2017-06-25

EventBus是一个事件总线框架,使用订阅/发布模式让组件间的通信变得简单。

官网:http://greenrobot.org/eventbus/

官方教程:http://greenrobot.org/eventbus/documentation/

Github地址:https://github.com/greenrobot/EventBus

原理很简单,就是订阅/发布模式的一个实现。好处是什么?解耦

这篇文章从示例的角度解释EventBus的使用方法和原理。

示例展示了两个fragment之间如何通过EventBus通信。在fragment1中点击按钮发送事件(说成消息也可以),fragment2接收事件,并展示事件内容。效果如下:

1. 示例一:fragment之间通信

fragment之间的通信可以通过直接调用对象方法、广播等形式实现。这里有做些讨论。不过如果使用EventBus实现,既解耦,又优雅。

创建项目,在build.gradle中添加:

compile 'org.greenrobot:eventbus:3.0.0'

添加代码,最终结构如下:

1.1 MainActivity和布局文件

这个是程序入口:

package com.example.letian.eventbusapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

对应的布局文件activity_main.xml内容如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.letian.eventbusapplication.MainActivity">

    <fragment android:name="com.example.letian.eventbusapplication.Example01Fragment"
              android:id="@+id/list"
              android:layout_weight="1"
              android:layout_width="match_parent"
              android:layout_height="match_parent" />
    <fragment android:name="com.example.letian.eventbusapplication.Example02Fragment"
              android:id="@+id/viewer"
              android:layout_weight="2"
              android:layout_width="match_parent"
              android:layout_height="match_parent" />

</LinearLayout>

这里指定了两个fragment。对应Example01Fragment类和Example02Fragment类。

1.2 MessageEvent事件类

事件类的本质就是封装消息,根据需要自定义即可。

package com.example.letian.eventbusapplication.event;

public class MessageEvent {
    private String data;

    public MessageEvent(String data) {
        this.data = data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    @Override
    public String toString() {
        return "MessageEvent{" +
                "data='" + data + '\'' +
                '}';
    }

}

1.3 Example01Fragment和布局文件

Example01Fragment类内容如下:

package com.example.letian.eventbusapplication;

import android.app.Fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

import com.example.letian.eventbusapplication.event.MessageEvent;

import org.greenrobot.eventbus.EventBus;


public class Example01Fragment extends Fragment {

    private View rootView;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        // return super.onCreateView(inflater, container, savedInstanceState);
        rootView = inflater.inflate(R.layout.example01_fragment, container, false);
        Button sendBtn = rootView.findViewById(R.id.send);
        sendBtn.setOnClickListener(new View.OnClickListener() {  // 点击按钮,发布事件
            @Override
            public void onClick(View view) {
                EventBus.getDefault().post(new MessageEvent("Hello everyone!"));  // 发布事件
            }
        });
        return rootView;
    }

}

EventBus.getDefault()得到EventBus的默认实例,post方法用于发布事件。

布局文件example01_fragment.xml内容如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <Button
        android:id="@+id/send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送消息"/>

</LinearLayout>

1.4 Example02Fragment和布局文件

Example02Fragment类内容如下:

package com.example.letian.eventbusapplication;

import android.app.Fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.example.letian.eventbusapplication.event.MessageEvent;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

public class Example02Fragment extends Fragment {

    private View rootView;
    private TextView textView;
    private static String TAG = "Example02Fragment";
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        // return super.onCreateView(inflater, container, savedInstanceState);
        rootView = inflater.inflate(R.layout.example02_fragment, container, false);
        textView = rootView.findViewById(R.id.msg);
        return rootView;
    }

    @Override
    public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this); // 将该对象注册到 EventBus
    }

    @Override
    public void onStop() {
        super.onStop();
        EventBus.getDefault().unregister(this); // 解除注册关系
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessage(MessageEvent event) { // 监听 MessageEvent 事件
        Log.v(TAG, "GET event: "+event);
        textView.setText(event.getData());
    }
}
  • EventBus.getDefault().register(this);是将Example02Fragment作为订阅者注册到EventBus中;
  • EventBus.getDefault().unregister(this);用于解除订阅关系。
  • onMessage方法监听MessageEvent类型的事件,在MAIN线程(也就是主线程)收到事件后,将数据显示在textView中。

布局文件example02_fragment.xml内容如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="接收消息"/>
    <TextView
        android:id="@+id/msg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

2. 示例二:使用索引类提升性能

示例1中使用EventBus基于反射收集订阅者的信息,但是反射机制在性能上表现不佳,一个优化是将订阅者的信息生成索引写入一个Java类里。也就是生成一个包含订阅者信息的Java类,运行时就不需要用反射了。这里涉及到注解处理器的概念。EventBus的注解处理器实现在这里

2.1 生成索引文件

修改build.gradle添加下面的内容:

android {
    defaultConfig {
    
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ eventBusIndex : 'com.example.letian.eventbusapplication.MyEventBusIndex' ]
            }
        }
    }
}

dependencies {
    compile 'org.greenrobot:eventbus:3.0.0'
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

首先通过编译参数指定注解处理器的参数是eventBusIndex : 'com.example.letian.eventbusapplication.MyEventBusIndex'

然后在dependencies中通过gradle内置的annotationProcessor指令指定注解处理器: 'org.greenrobot:eventbus-annotation-processor:3.0.1'

如此,在编译过程中会生成类com.example.letian.eventbusapplication.MyEventBusIndex。这个类生成后在不是放在代码目录,而是在build目录里。根据文件名可以搜索到这个文件,这里晒下它的内容:

package com.example.letian.eventbusapplication;

import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberMethodInfo;
import org.greenrobot.eventbus.meta.SubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberInfoIndex;

import org.greenrobot.eventbus.ThreadMode;

import java.util.HashMap;
import java.util.Map;

/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
		
        // 重点在这里
        putIndex(new SimpleSubscriberInfo(Example02Fragment.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onMessage", com.example.letian.eventbusapplication.event.MessageEvent.class,
                    ThreadMode.MAIN),
        }));

    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}

2.2 使用索引文件

要使用索引文件,就不需要用EventBus.getDefault()去做注册订阅者、取消注册订阅者、发送消息等事情了。而要使用自定义的EventBus对象。

首先编写类MyEventBus,如下:

package com.example.letian.eventbusapplication;

import org.greenrobot.eventbus.EventBus;

public class MyEventBus {

    public final static EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

}

将其他代码里的EventBus.getDefault()替换成MyEventBus.eventBus即可。

3. 更多

EventBus支持多种ThreadMode,支持事件优先级,支持sticky模式,支持事件中断,这些内容都可以在简洁易懂的官方教程中找到。

下面的文章值得一读:

当然,除了EventBus,事件总线有很多实现,例如otto、guava、AndroidEventBus等。有时间了,搞一把。


( 本文完 )