模仿EventBus手写实现低配版
EventBus基本用法
EventBus用于组件间通信,能大大降低组件之间的耦合度,并且使用起来也非常简单;另一个优点就是它非常小,只有50k,对项目来说是极其友好的。
GitHub地址:https://github.com/greenrobot/EventBus
implementation 'org.greenrobot:eventbus:3.1.1'
在需要接收事件通知的组件中,如Activity,使用register注册及unregister反注册。
官方推荐在onStart()中注册和onStop()中反注册,反注册一定程度上是防止用于存储注册信息的Map过大,不再使用时需要及时进行清理。
@Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
protected void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
定义一个事件实体类
public calss MyEvent {
public String msg;
MyEvent(String msg) {
this.msg = msg;
}
}
定义接收该事件的方法,方法名称无所谓,但参数类型即派发事件的类型,并且只能有一个参数,也是为了方便管理和映射,如果需要接收多个参数,只需要把多个参数封装到一个对象中即可;方法应声明为public。
ThreadMode表示该方法在什么线程中被回调,如果用于更新UI,直接设置成MAIN即可。
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MyEvent event){
if(evnet != null) {
Log.i("onMessageEvent", event.msg);
}
}
事件派发
EventBus.getDefault().post(new MyEvent("Message"));
手写实现低配版
EventBus 3.0之前使用的反射技术来完成主要功能,而在3.0之后进行了大的优化,减少了反射的使用,但对我们来说用法没有任何改变,但是性能确实提高了。
EventBus的核心思想就是解耦,具体的实现方式并不是最重要的,所以我们今天还是完全依靠反射的方式手写一个低配版,来具体感受一下EventBus解耦的思路。
我们实现的步骤分为四步:
- 创建线程模式;
- 创建注解;
- 封装方法类;
- “存储”方法,并通过反射进行调用;
创建线程模式
这个类非常简单,我们定义成枚举类型,一个主线程,一个子线程
public enum ThreadMode {
MAIN,
BACKGROUND
}
创建注解
@Target是指定该注解可以放在什么地方,METHOD,也就是方法之上;
@Retention指定该注解是什么类型,运行时?编译时?当然,我们需要实现动态注册,就是运行时了;
参数就是我们上面定义的枚举,线程模式,默认为主线程。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.MAIN;
}
封装方法类
我们需要为每一个注册者生成一个方法对象列表,而每一个方法又需要一个对象来保存其基本信息,比如方法是哪一个?线程模式是什么?方法的参数类型是什么?
public class SubscribeMethod {
// 保存方法对象
private Method method;
//线程模式
private ThreadMode threadMode;
// 方法的参数类型
private Class<?> type;
public SubscribeMethod(Method method, ThreadMode threadMode, Class<?> type) {
this.method = method;
this.threadMode = threadMode;
this.type = type;
}
public Method getMethod() {
return method;
}
public ThreadMode getThreadMode() {
return threadMode;
}
public Class<?> getType() {
return type;
}
}
“存储”方法,并通过反射进行调用
这里就是我们的最后一步了,也是我们整个demo的核心部分:EventBus类。在这里,我们需要实现官方版的register、post方法。(unregister就不实现了,其实就是移除我们CacheMap中的注册对象)
首先我们模仿官方的**EventBus.getDefault()**来构造我们的单例。
EventBus.java
private static volatile EventBus sInstance;
public static EventBus getDefault(){
if(sInstance == null) {
synchronized (EventBus.class){
if(sInstance == null) {
sInstance = new EventBus();
}
}
}
return sInstance;
}
由于我们需要在不同的线程中回调不同的方法,因此,我们的EventBus需要分别持有一个主线程和一个子线程。同时,我们需要一个Map来存储所有注册对象的方法信息。
//用于缓存所有注册者的方法信息
//这里的SubscribeMethod就是我们第三步构建的用于存储方法信息的对象
private Map<Object, List<SubscribeMethod>> mCacheMap;
//用于主线程回调
private Handler mHandler;
//用于子线程回调
private ExecutorService mExecutorService;
private EventBus(){
mCacheMap = new HashMap<>();
mHandler = new Handler(Looper.getMainLooper());//获取主线程
mExecutorService = Executors.newCachedThreadPool();//获取子线程
}
接下来我们实现注册方法。避免重复注册,这里做了空值判断。
public void register(Object obj){
List<SubscribeMethod> list = mCacheMap.get(obj);
if(list == null){
mCacheMap.put(obj,findSubscribeMethods(obj));
}
}
我们看一下findSubscribeMethods(Object obj)方法的实现,这里就是通过反射获取注册对象所有的标记了我们Subsribe注解的方法。
private List<SubscribeMethod> findSubscribeMethods(Object obj){
Class<?> clazz = obj.getClass();
List<SubscribeMethod> subMethods = new ArrayList<>();
Method[] methods = clazz.getMethods();
for (Method method : methods){
//获取我们自定义的注解
Subscribe subscribe = method.getAnnotation(Subscribe.class);
if(subscribe == null){
continue;
}
//判断参数数量是否只有一个,否则抛出异常
Class<?>[] paramType = method.getParameterTypes();
if(paramType.length != 1){
throw new RuntimeException("EventBus回调方法只能接收一个参数");
}
//保存方法信息
SubscribeMethod subscribeMethod = new SubscribeMethod(method, subscribe.threadMode(), paramType[0]);
subMethods.add(subscribeMethod);
}
return subMethods;
}
我们在这里采用的是Class.getMethods()获取方法列表,因此只能获取到当前类及其父类中所有的public方法。
如果使用Class.getDeclaredMethods()则可以获取当前类中所有的方法,但在调用时需要设置Method.setAccessible(true),否则我们将无法调用非public方法。
到这里我们已经完成了大部分的工作,只需要再实现post方法即可完成我们低配版的实现了。
在post方法中我们需要根据ThreadMode标记来在不同的线程中回调这些方法。
// param是待发送的消息的实体对象
public void post(final Object param) {
Set<Object> set = mCacheMap.keySet();
for (final Object obj : set) {
List<SubscribeMethod> list = mCacheMap.get(obj);
if(list != null) {
for (final SubscribeMethod subscribeMethod : list) {
//待接收事件通知的方法的参数类型必须是post出去的参数类型的相同类或者其父类
if (subscribeMethod.getType().isAssignableFrom(param.getClass())) {
//无论在post时是在主线程还是在子线程,都在threadMode对应的线程接收
switch (subscribeMethod.getThreadMode()){
case MAIN: // 主线程接收
// 主 => 主
if(Looper.myLooper() == Looper.getMainLooper()) {
invoke(subscribeMethod, obj, param);
} else {
// 子 => 主
mHandler.post(new Runnable() {
@Override
public void run() {
invoke(subscribeMethod, obj, param);
}
});
}
break;
case BACKGROUND: // 子线程接收
// 主 => 子
if(Looper.myLooper() == Looper.getMainLooper()) {
mExecutorService.execute(new Runnable() {
@Override
public void run() {
invoke(subscribeMethod, obj, param);
}
});
} else {
// 子 => 子
invoke(subscribeMethod, obj, param);
}
break;
}
}
}
}
}
}
isAssignableFrom方法是用来判断前者是否是后者的相同类或者其父类或其父接口。
例如Event继承自BaseEvent,那么onMessageEvent(BaseEvent event)方法也可以接收BaseEvent或Event对象作为参数。
我们这里根据ThreadMode的值在不同的线程中派发事件,回调方法。再来看一下invoke方法的具体实现,在这里调用了对应的回调方法。
private void invoke(SubscribeMethod subscribeMethod, Object obj, Object param){
Method method = subscribeMethod.getMethod();
try {
method.invoke(obj, param);
} catch (Exception e) {
e.printStackTrace();
}
}
至此,我们就完成了低配版的全部内容,可以像官方版EventBus使用简单的register和post了。
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
}
@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onEvent(EventBean bean){ // EventBean可以是任意对象
Log.i("MainActivity", "thread = " + Thread.currentThread().getName());
Log.i("MainActivity", "onEvent: " + bean.toString());
}
//点击界面上的按钮跳转到SecondActivty
public void jump(View view) {
startActivity(new Intent(this, SecondActivity.class));
}
SecondActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
//点击界面按钮发送Event
public void send(View view) {
EventBus.getDefault().post(new EventBean("name", "password"));
}
EventBus使用范围非常广泛,在绝大部分项目中都有使用到,但我们不单是要学会如何使用,更要学习它的思想,技术实现并不难,难就难在能想到可以这么做。
人生中第一篇博客,也终于是迈出了这一步了。
虽然现在的自己还很菜鸡,但我相信,只要努力,只要用心,我也可以成为大牛的!加油~