模仿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解耦的思路。

我们实现的步骤分为四步:

  1. 创建线程模式
  2. 创建注解
  3. 封装方法类;
  4. 存储”方法,并通过反射进行调用;

创建线程模式

这个类非常简单,我们定义成枚举类型,一个主线程,一个子线程

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使用范围非常广泛,在绝大部分项目中都有使用到,但我们不单是要学会如何使用,更要学习它的思想,技术实现并不难,难就难在能想到可以这么做。

人生中第一篇博客,也终于是迈出了这一步了。

虽然现在的自己还很菜鸡,但我相信,只要努力,只要用心,我也可以成为大牛的!加油~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值