轻量级的EventBus简单实现

EventBus 简介 EventBus是一种用于Android的事件发布-订阅总线,由GreenRobot开发,Gihub地址是:EventBus。它简化了应用程序内各个组件之间进行通信的复杂度,尤其是碎片之间进行通信的问题,可以避免由于使用广播通信而带来的诸多不便。
1.1 三个角色

  1. Event:事件,它可以是任意类型,EventBus会根据事件类型进行全局的通知。
  2. Subscriber:事件订阅者,在EventBus 3.0之前我们必须定义以onEvent开头的那几个方法,分别是onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe,并且指定线程模型,默认是POSTING。
  3. Publisher:事件的发布者,可以在任意线程里发布事件。一般情况下,使用EventBus.getDefault()就可以得到一个EventBus对象,然后再调用post(Object)方法即可。
1.2 四种线程模型
EventBus3.0有四种线程模型,分别是:
  1. POSTING:默认,表示事件处理函数的线程跟发布事件的线程在同一个线程。
  2. MAIN:表示事件处理函数的线程在主线程(UI)线程,因此在这里不能进行耗时操作。
  3. BACKGROUND:表示事件处理函数的线程在后台线程,因此不能进行UI操作。如果发布事件的线程是主线程(UI线程),那么事件处理函数将会开启一个后台线程,如果果发布事件的线程是在后台线程,那么事件处理函数就使用该线程。
  4. ASYNC:表示无论事件发布的线程是哪一个,事件处理函数始终会新建一个子线程运行,同样不能进行UI操作。

先看下以下的一种场景:
现在有在同一个进程的ActivityA和ActivityB,现在的简单需求是点击ActivityB中的按钮,修改ActivityA中TextView中的内容.
public class ActivityA extends Activity implements View.OnClickListener {
private TextView textView;
public static ActivityA activityA;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
textView = findViewById(R.id.tv);
Button button = findViewById(R.id.btnStartB);
button.setOnClickListener(this);
activityA = this;
}

public void setTextView(){
textView.setText("ActivityB set ActivityA");
}

@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(this,ActivityB.class);
startActivity(intent);
}

public void onDestroy(){
super.onDestroy();
activityA = null; //防止内存漏洞
}
}
public class ActivityB extends Activity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
Button button = findViewById(R.id.btn);
button.setOnClickListener(this);
}

@Override
public void onClick(View v) {
ActivityA.activityA.setTextView();
}
}
以上的代码是以一种,这种方式的问题是直接引用在ActivityB引用了ActivityA类,耦合度高。
现在做以下的优化:
新增ActivityGloble
public class ActivityGloble {
privateActivityA activityA;
public static ActivityGloble activityGloble = new ActivityGloble();
public void setActivityA(ActivityA activityA){
this.activityA = activityA;
}

public void setTextView(){
activityA.setTextView();
}
}
public class ActivityA extends Activity implements View.OnClickListener {
private TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
textView = findViewById(R.id.tv);
Button button = findViewById(R.id.btnStartB);
button.setOnClickListener(this);
ActivityGloble.activityGloble.setActivityA(this);
//activityA = this;
}

public void setTextView(){
textView.setText("ActivityB set ActivityA");
}

@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(this,ActivityB.class);
startActivity(intent);
}

public void onDestroy(){
super.onDestroy();
ActivityGloble.activityGloble.setActivityA(null); //防止内存漏洞
}
}

public class ActivityB extends Activity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
Button button = findViewById(R.id.btn);
button.setOnClickListener(this);
}

@Override
public void onClick(View v) {
ActivityGloble.activityGloble.setTextView();
}
}
现在看起来已经优雅了很多了,但是还有以下一些问题要:
  1. 如果不是ActivityA变成了其他的ActivityC或者其他的类,要怎么处理?
  2. 现在是只有一个方法,如果方法多了,ActivityGloble是要手动增加方法的,如何才能做到自动获取方法?
针对以上两点,要做如下的改动:将要引用的类改成Object类型,要被执行的方法通过反射获取。
public class ActivityGloble {
privateObject subscriber; //要订阅的类,
public static ActivityGloble activityGloble = new ActivityGloble();
private HashMap methodMap = new HashMap<>(); //需要被执行方法map
/*
* @param subscriber//要订阅的类
* @param postMethodNames,订阅类要执行的方法列表
*/
public void register(Object subscriber,String[] postMethodNames){
this.subscriber = subscriber;
if(subscriber == null){
return;
}
if(postMethodNames != null){
Class subscriberClass = subscriber.getClass(); //获取类
Method[] methods = subscriberClass.getDeclaredMethods(); //获取所有方法
for(int i = 0; i < methods.length; i++){
for(int j = 0; j < postMethodNames.length; j++){
if(methods[i].getName().equals(postMethodNames[j])){
StringBuilder paramTypeBuilde = null; //用于处理函数的重载
Class[] paramTypes = methods[i].getParameterTypes();
if(paramTypes != null && paramTypes.length > 0){
paramTypeBuilde = new StringBuilder();
for(int k = 0; k < paramTypes.length; k++){
paramTypeBuilde.append(paramTypes[k].getName()); //取出函数对应的参数
}
}
if(paramTypeBuilde == null){
methodMap.put(postMethodNames[j],methods[i]); //如果这个函数没有参数
}else{
//如果有参数,参数也和方法一起,作为一个key
//比如setTextView(String arg1,Integer arg3)会转化为 setTextViewjava.lang.Stringjava.lang.IntegermethodMap.put(postMethodNames[j]+paramTypeBuilde.toString(),methods[i]);
}
}
}
}
}
}
/**
* @param methodName
* @param args,要传入的参数
*/
public void post(String methodName, Object... args) {
if(args != null && args.length > 0){
//如果有参数,对方法进行拼接
StringBuilder builder = new StringBuilder();
builder.append(methodName);
for(int i = 0; i < args.length; i++){
builder.append(args[i].getClass().getName());
}
methodName = builder.toString();
}
Method method = methodMap.get(methodName);
if(method != null){
try {
if(args == null || args.length == 0){
method.invoke(subscriber);
}else{
method.invoke(subscriber,args);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
对于post方法,这里要进行说明下:
因为是Object... args这样的方式传参,所以会导致int,long,float,double,boolean,byte转为Integer,Long,Float,Double,Boolean,Byte
所以为了处理这个问题,所以要声名的函数int,long,float,double,boolean,byte这些类型都要转为Integer,Long,Float,Double,Boolean,Byte。

具体的调用:
public class ActivityA extends Activity implements View.OnClickListener {
private TextView textView;
private String[] methods = {"setTextView","setTextView1"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
textView = findViewById(R.id.tv);
Button button = findViewById(R.id.btnStartB);
button.setOnClickListener(this);
ActivityGloble.activityGloble.register(this,methods);
//activityA = this;
}

public void setTextView(){
textView.setText("ActivityB set ActivityA");
}
public void setTextView1(String arg1,String arg2){
TextView textView = findViewById(R.id.tv1);
textView.setText("ActivityB set ActivityA "+arg1+" "+arg2);
}
public void setTextView(String arg1,Integer arg3){
TextView textView = findViewById(R.id.tv2);
textView.setText("ActivityB set ActivityA "+arg1+" "+" arg3 is "+3);
}
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(this,ActivityB.class);
startActivity(intent);
}

public void onDestroy(){
super.onDestroy();
ActivityGloble.activityGloble.register(null,null); //防止内存漏洞
}
}
public class ActivityB extends Activity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
Button button = findViewById(R.id.btn);
button.setOnClickListener(this);
}

@Override
public void onClick(View v) {
ActivityGloble.activityGloble.post("setTextView");
ActivityGloble.activityGloble.post("setTextView1","qaa","bbb");
ActivityGloble.activityGloble.post("setTextView","wwww",1);
}
}
这样看来解决之前提出的两个问题。

现在又有新的问题出现了:
1.是否可以注册多个类?
2.这里是手动把要注册的方法定好了,有没有更方式,让我们不需要提前写好方法,就能知道哪个方法要被执行?
3.执行的时候,可不可以只传个参数就能知道调用哪个方法?

分析问题1:
参照了EventBus里的源码,可以用缓存做处理,如下:
HashMap cacheMap = new HashMap<>();
Object来作Key,就是订阅的类,HashMap这个是类对应订阅的方法。
代码如下:
HashMap methodMap = cacheMap.get(object); //先从缓存中查找。
if(methodMap == null){//缓存里没有找到
methodMap = getSubscribleMethodMap(object); //获取方法map,
cacheMap.put(object,methodMap); //将方法map放入到缓存中
}

分析下问题2:
在反射Method中有一个方法:
public T getAnnotation(Class annotationClass) {
return super.getAnnotation(annotationClass);
}
这个方法可以获取写在方法上的注解,以此来判断这个方法是否有用到这个注解。所以可以通过注解的方法来查找哪个方法需要被执行。

对于问题3,如果每个函数传入的参数都不一样,这样是可以做到的。但是如果是两个不同的函数,有相同的参数,这时是不参区分的。还有就是如果注册了多个类,每个类都有相同的函数名和参数,这个也是不好区分的。
所以要做到以下三点:
1.定义函数的时候,尽量不要有相同的参数
2.如果同一个类里多个函数有相同的参数,调用的时候还是要把函数名给传递。
3.如果注册了多个类,且类里还要相同的函数,这里还是要传入类名。

现在来看下具体的实现:
先声名一个注解类:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBusSubscribe {
ThreadMode threadMode() default ThreadMode.POSTING; //判断是运行在哪个线程
}
ThreadMode 是判断函数的运行的类型,具体是参照EventBus源码里的:
public enum ThreadMode {
/**
* Subscriber will be called directly in the same thread, which is posting the event. This is the default. Event delivery
* implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for
* simple tasks that are known to complete in a very short time without requiring the main thread. Event handlers
* using this mode must return quickly to avoid blocking the posting thread, which may be the main thread.
*/
POSTING,

/**
* On Android, subscriber will be called in Android's main thread (UI thread). If the posting thread is
* the main thread, subscriber methods will be called directly, blocking the posting thread. Otherwise the event
* is queued for delivery (non-blocking). Subscribers using this mode must return quickly to avoid blocking the main thread.
* If not on Android, behaves the same as {@link #POSTING}.
*/
MAIN,
/**
* On Android, subscriber will be called in Android's main thread (UI thread). Different from {@link #MAIN},
* the event will always be queued for delivery. This ensures that the post call is non-blocking.
*/
MAIN_ORDERED,
/**
* On Android, subscriber will be called in a background thread. If posting thread is not the main thread, subscriber methods
* will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single
* background thread, that will deliver all its events sequentially. Subscribers using this mode should try to
* return quickly to avoid blocking the background thread. If not on Android, always uses a background thread.
*/
BACKGROUND,
/**
* Subscriber will be called in a separate thread. This is always independent from the posting thread and the
* main thread. Posting events never wait for subscriber methods using this mode. Subscriber methods should
* use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number
* of long running asynchronous subscriber methods at the same time to limit the number of concurrent threads. EventBus
* uses a thread pool to efficiently reuse threads from completed asynchronous subscriber notifications.
*/
ASYNC}

//对所要订阅的方法进行一些封装。
public class SubscribleMethod {
//注册方法
private Method method;
//线程类型
private ThreadMode threadMode;

//参数类型
private Class[] paramTypes;


public SubscribleMethod(Method method, ThreadMode threadMode, Class[] paramTypes) {
this.method = method;
this.threadMode = threadMode;
this.paramTypes = paramTypes;
}

/**
*
* 获取方法名字
*/
public String getMethodNameKey(){
String methodName = method.getName();
if(paramTypes == null || paramTypes.length ==0){
// return method.getName();
}else{
StringBuilder builder = new StringBuilder();
builder.append(methodName);
for(int k = 0; k < paramTypes.length; k++){
builder.append(paramTypes[k].getName());
}
methodName = builder.toString();
}
returnmethodName;
}
/**
*
* 参数拼接的字段
*/
public String getParamTypesNameKey(){
if(paramTypes == null || paramTypes.length ==0){
// return method.getName();
return "";
}else{
StringBuilder builder = new StringBuilder();
for(int k = 0; k < paramTypes.length; k++){
builder.append(paramTypes[k].getName());
}
return builder.toString();
}
}
public Method getMethod() {
return method;
}

public void setMethod(Method method) {
this.method = method;
}

public ThreadMode getThreadMode() {
return threadMode;
}

public void setThreadMode(ThreadMode threadMode) {
this.threadMode = threadMode;
}

public Class[] getParamTypes() {
return paramTypes;
}

public void setParamTypes(Class[] paramTypes) {
this.paramTypes = paramTypes;
}
}

public class EventBus {
private static EventBus instance = new EventBus();
privateObject subscriber; //要订阅的类,
private HashMap cacheMap ;
private Handler handler; //用于处理需要在主线程中执行的事件
private ExecutorService executorService; //线程池用于处理异步事件
public static EventBus getDefault() {
if(instance == null){
synchronized (EventBus.class){
if(instance == null){
instance = new EventBus();
}
}
}
return instance;
}

public EventBus(){
this.cacheMap = new HashMap<>();
handler = new Handler(Looper.getMainLooper());
executorService = Executors.newCachedThreadPool();
}
public void regisg(Object object){
if(object == null){
return;
}
HashMap methodMap = cacheMap.get(object); //先从缓存中查找。
if(methodMap == null){
methodMap = getSubscribleMethodMap(object); //获取方法map,
cacheMap.put(object,methodMap); //将方法map放入到缓存中

}

}

private HashMap getSubscribleMethodMap(Object subscriber){
Class subscriberClass = subscriber.getClass(); //获取类
HashMap methodMap = new HashMap<>();
while (subscriberClass != null) {
//判断分类是在那个包下,(如果是系统的就不需要)
String name = subscriberClass.getName();
if (name.startsWith("java.") ||
name.startsWith("javax.") ||
name.startsWith("android.") ||
name.startsWith("androidx.")) {
break;
}
Method[] declaredMethods = subscriberClass.getDeclaredMethods();
for (Method method : declaredMethods) {
//找相应的注解
EventBusSubscribe annotation = method.getAnnotation(EventBusSubscribe.class);
if(annotation == null){//注解没有找到
continue;
}
ThreadMode threadMode = annotation.threadMode();
Class[] paramTypes = method.getParameterTypes();
SubscribleMethod subscribleMethod = new SubscribleMethod(method,threadMode,paramTypes);
methodMap.put(subscribleMethod.getMethodNameKey(),subscribleMethod);
}
subscriberClass = subscriberClass.getSuperclass(); //遍历父类
}
return methodMap;
}


public void post(final Object... event){
postClasssMethod(null,null,event);
}

public void postMethod(final String medhodName,final Object... event){
postClasssMethod(null,medhodName,event);
}

/**
*
* @param className 执行对应的类
* @param methodName 执行对应的方法
* @param event 参数
*/
public void postClasssMethod(final String className,final String methodName,final Object... event){
Set set = cacheMap.keySet();
Iterator iterator = set.iterator();
Object invokeObj = null;
SubscribleMethod subscribleMethod= null;
String myClassName = "";
if(className == null){
}else{
myClassName = className;
}
boolean isFindClass = false;
String myMethodName = methodName;
if(methodName == null ){
myMethodName = "";
}
while (iterator.hasNext()) {
//拿到注册类
final Object nextClassObj = iterator.next();
if(nextClassObj.getClass().getName().equals(myClassName)) {//通过类名找到对应的类
isFindClass = true;
}
HashMap methodHashMap = cacheMap.get(nextClassObj);
String tempMethodName = getMethodName(myMethodName,event);
for (Map.Entry entry : methodHashMap.entrySet()) {
subscribleMethod = entry.getValue();
String paramTypesName =subscribleMethod.getParamTypesNameKey(); //获取参数的拼接字符
String methodNameKey = subscribleMethod.getMethodNameKey();
if(methodNameKey.equals(tempMethodName)){//通过类名以及参数找到对应的方法了,执行完方法的,就退出循环了
invokeObj = nextClassObj;
invoke(subscribleMethod,invokeObj,event);
break;
}else if(paramTypesName.equals(tempMethodName)){//通过参数找到对应的方法
if(subscribleMethod != null){
invokeObj = nextClassObj;
invoke(subscribleMethod,invokeObj,event);
}
}
}
if(isFindClass){//如果找到对应的类,执行完对应的类中的方法就退出不再执行了
break;
}
}
}

private String getMethodName(String methodName,Object... args){
StringBuilder builder = new StringBuilder();
builder.append(methodName);
if(args != null && args.length > 0){
//如果有参数,对方法进行拼接
for(int i = 0; i < args.length; i++){
builder.append(args[i].getClass().getName());
}
// methodName = builder.toString();
}
return builder.toString();
}

private void invoke(SubscribleMethod subscribleMethod, final Object classObj, final Object ... params) {
final Method method = subscribleMethod.getMethod();
ThreadMode threadMode = subscribleMethod.getThreadMode();
switch (threadMode){
case MAIN:
if(Looper.myLooper() == Looper.getMainLooper()){
invokeMethod(method,classObj,params);
}else{
handler.post(new Runnable() {
@Override
public void run() {
invokeMethod(method,classObj,params);
}
});
}
break;
case POSTING:
invokeMethod(method,classObj,params);
break;
case ASYNC:
//post方法执行在主线程中
if(Looper.myLooper() == Looper.getMainLooper()){
executorService.execute(new Runnable() {
@Override
public void run() {
invokeMethod(method,classObj,params);
}
});
} else {
//post方法执行在子线程中
invokeMethod(method,classObj,params);
}
break;
}

}

private void invokeMethod(Method method,Object classObj, Object ... params){
try {
method.invoke(classObj, params);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}

//取消注册
public void unregister(Object subscriber) {
Object hashMap = cacheMap.get(subscriber);
//如果获取到
if (hashMap != null) {
cacheMap.remove(subscriber);
}
}
}

这里说明下post,postMethod,postClasssMethod,这三个方法。
post方法会执行注册过类中相同参数的所有方法。
postMethod,如果有传递方法的名称,则会根据方法名称和参数名字查找到对应的方法进行执行。
postClasssMethod,如果有同时传递注册过的类名以及其中的方法,则只会执行类中对应的方法,
如果只传入类名而没有传入方法,则执行类中有相同参数的方法。

最后
这个只是参考了eventBus源码,实现了一些基本的功能很多EvenbBus源码中的功能没有实现,如事件的顺序执行、切换到后台的线程等。EventBus源码中,传递事件的参数只有一个,这里代码的实现可以传递多个参数,也算是一个不同点。
【轻量级的EventBus简单实现】对应代码的地址:https://github.com/fzhFidt/eventBus

    推荐阅读