Android-EventBus(手写简易版)
Android-EventBus(手写简易版)
- 简介
- 基本使用
- 手写核心地方
- 注册 以及 取消注册
- 流程图(EventBus注册的大致流程)
- post发送消息
- 流程图(EventBus发送消息的大致流程)
- 使用我们自己简易的EventBus
简介
EventBus可以代替Android传统的Intent,Handler,Broadcast或接口函数,在Fragment,Activity,Service线程之间传递数据,执行方法
GitHub-EventBus
Subscribe :@Subscribe采用运行时注解,且注解只能用在函数上,默认的threadmode为posting
threadMode分为五种:
POSTING:默认的模式,开销最小的模式,因为声明为POSTING的订阅者会在发布的同一个线程调用,发布者在主线程那么订阅者也就在主线程,反之亦,避免了线程切换,如果不确定是否有耗时操作,谨慎使用,因为可能是在主线程发布;
MAIN:主线程调用,视发布线程不同处理不同,如果发布者在主线程那么直接调用(非阻塞式),如果发布者不在主线程那么阻塞式调用;
MAIN_ORDERED:和MAIN差不多,主线程调用,和MAIN不同的是他保证了post是非阻塞式的(默认走MAIN的非主线程的逻辑,所以可以做到非阻塞);
BACKGROUND:在子线程调用,如果发布在子线程那么直接在发布线程调用,如果发布在主线程那么将开启一个子线程来调用,这个子线程是阻塞式的,按顺序交付所有事件,所以也不适合做耗时任务,因为多个事件共用这一个后台线程;
ASYNC:在子线程调用,总是开启一个新的线程来调用,适用于做耗时任务,比如数据库操作,网络请求等,不适合做计算任务,会导致开启大量线程
基本使用
导入依赖
implementation 'org.greenrobot:eventbus:3.1.1'
接收消息的MainActivity界面
register(this) 注册绑定当前的Activity;
unregister(this) 解除绑定
@Subscribe(threadMode = ThreadMode.MAIN):指定在主线程中接收消息;
package com.lk.myeventbus;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.widget.TextView;import android.widget.Toast;import org.greenrobot.eventbus.EventBus;import org.greenrobot.eventbus.Subscribe;import org.greenrobot.eventbus.ThreadMode;public class MainActivity extends AppCompatActivity { TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);//注册绑定 EventBus.getDefault().register(this); tv = findViewById(R.id.tv); }//主线程中接收消息 @Subscribe(threadMode = ThreadMode.MAIN) public void receive(Friend friend){ tv.setText(friend.toString()); } @Override protected void onDestroy() { super.onDestroy(); //解除绑定 EventBus.getDefault().unregister(this); } public void change(View view) { //这里我们跳转到另外一个Activity当中去发消息 startActivity(new Intent(MainActivity.this,TestActivity.class)); }}
发送消息的TestActivity界面
通过post发送一条消息,消息内容是个Friend对象
package com.lk.myeventbus;import android.app.Activity;import android.os.Bundle;import android.view.View;import org.greenrobot.eventbus.EventBus;import androidx.annotation.Nullable;public class TestActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); } public void send(View view) { new Thread(new Runnable() { @Override public void run() { //我们在子线程中发个消息,看看接收消息界面在注解中指定主线程是否能成功 EventBus.getDefault().post(new Friend("lk",18)); } }).start(); }}
手写核心地方
我们这里写一个简易版的EventBus,写出核心的地方
注册 以及 取消注册
根据EventBus的源码,我们这类单例;
在构造方法中,我们创建出来缓存订阅者以及对应的实体类;
至于这个Handler和线程池,是会用来发送消息的时候,注册中可以忽略;
这类我们定义一个注册和取消注册的方法;
注册的方法中:
1、先从缓存中去获取这个订阅者里面的所有标有注解的方法;
2、如果在缓存中没有获取到的话,我们就去并且添加到缓存的Map当中去;
怎么去获取所有标有注解的方法?
来看getSubscrbleMethods这个方法;
1、创建出一个集合来存储SubscrbleMethod这个实体类,也就是所有标有注解的方法存入到这个集合当中;
2、我们得一层一层的去寻找吧?因为当前的Activity可能会是继承了自己写的基类的BaseActivity吧?
3、然后我直接过滤掉系统的类;
4、获取到订阅者这个类里面的所有方法进行遍历;
5、在每个方法上面去寻找是否标有LkSubsrcible这个注解;
6、如果标有这个注解,我们就获取这个方法的所有参数;因为EventBus中,接收方法只能是一个参数,所以我们这里过滤掉不是一个参数的方法;
7、获取注解中的线程标识;
8、然后创建SubscrbleMethod实体类,存放方法体、方法执行的线程标识、参数类型;
添加到集合当中,并返回出去;
到这里我们就做完了注册订阅者
public class LkEventBus { private volatile static LkEventBus instance; private Handler handler; //线程池 private ExecutorService executorService; //缓存 订阅者对应所有标有注解的方法 private Map<Object, List<SubscrbleMethod>> cacheMap; private LkEventBus(){ cacheMap = new HashMap<>(); //创建主线程的Handler handler = new Handler(Looper.getMainLooper()); //初始化线程池 executorService = Executors.newCachedThreadPool(); } /** * 注册 * 保存订阅者信息 * @param subscriber 订阅者 */ public void register(Object subscriber){ //拿到订阅者类信息 Class<?> aClass = subscriber.getClass(); //先从缓存中获取 方法集合 List<SubscrbleMethod> subscrbleMethods = cacheMap.get(subscriber); //如果缓存中没有获取到 也就是如果已经注册,就不需要注册添加到Map当中了 if(subscrbleMethods == null){ //去给我们创建 subscrbleMethods = getSubscrbleMethods(aClass); //将订阅者和里面标有注解的所有方法存入缓存map中 cacheMap.put(subscriber,subscrbleMethods); } } /** * 获取订阅者的所有标有注解的方法 * @param subscriber 订阅者 * @return */ private List<SubscrbleMethod> getSubscrbleMethods(Class<?> clazz) { //创建一个List集合来保存所有的方法 List<SubscrbleMethod> list = new ArrayList<>(); //因为 订阅者 上一层 BaseActivity 上一层是Activity while (clazz!=null){ //判断父类是在哪个包下(如果是系统的就不需要) String name = clazz.getName(); //这些包下面的就系统的,我们直接跳过 if(name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.") || name.startsWith("androidx.")){ break; } Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method method : declaredMethods) { //获取LkSubsrcible注解 LkSubsrcible annotation = method.getAnnotation(LkSubsrcible.class); //没有标有这个注解 if(annotation==null){ continue; } //如果标有这个注解 //获取这个方法的参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); //因为EventBus里面,接收消息的方法中参数只能是一个 if(parameterTypes.length!=1){ throw new RuntimeException(method.getName() +"方法只能接收一个参数"); } //如果标有LkSubsrcible注解,并且参数个数也只有一个 //获取到这个方法上LkSubsrcible注解中的threadMode参数值 LkThreadMode lkThreadMode = annotation.threadMode(); //通过实体类保存 方法体、方法执行的线程标识、参数类型 SubscrbleMethod subscrbleMethod = new SubscrbleMethod(method, lkThreadMode, parameterTypes[0]); //添加到集合当中 list.add(subscrbleMethod); } clazz = clazz.getSuperclass(); } return list; } /** * 取消注册 */ public void unregister(Object subscriber){ Class<?> aClass = subscriber.getClass(); List<SubscrbleMethod> list = cacheMap.get(subscriber); if(list!=null){ //从缓存中去移除掉这个订阅信息 cacheMap.remove(subscriber); } }}
SubscrbleMethod 类
这个类用来存放订阅者的方法体、线程的标识、方法的参数类型
package com.lk.myeventbus.core.entity;import java.lang.reflect.Method;public class SubscrbleMethod { //接收消息的方法体 private Method method; //方法需要执行的线程 private LkThreadMode threadMode; //方法的参数类型 private Class<?> parameteType; public SubscrbleMethod(Method method, LkThreadMode threadMode, Class<?> parameteType) { this.method = method; this.threadMode = threadMode; this.parameteType = parameteType; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } public LkThreadMode getThreadMode() { return threadMode; } public void setThreadMode(LkThreadMode threadMode) { this.threadMode = threadMode; } public Class<?> getParameteType() { return parameteType; } public void setParameteType(Class<?> parameteType) { this.parameteType = parameteType; }}
LkThreadMode 类
这个类我们和EventBus里面保持一致,用来做接收方法的线程标识
package com.lk.myeventbus.core.entity;/** * 标注方法需要执行的线程 * 我们把EventBus的线程标识类型拿过来 */public enum LkThreadMode { POSTING, MAIN, MAIN_ORDERED, BACKGROUND, ASYNC}
流程图(EventBus注册的大致流程)
post发送消息
需要注意的是EventBus中会通过ThreadLoad去处理线程同步的问题,我们这类做了这一步的省略…
发送消息这个方法:
大致思路:我们需要去遍历订阅者集合,找到对应的方法,然后去对应的线程中执行该方法;
1、遍历缓存所有订阅者以及他们的所有标有LkSubsrcible注解的方法;
2、通过方法的参数类型进行比对发送的消息的数据类型;
3、如果找到符合要求的方法,再去获取到该方法注解里面的线程标识,做线程切换
4、然后在对应线程中去执行接收消息的方法,把参数传进方法中;
package com.lk.myeventbus.core;import android.os.Handler;import android.os.Looper;import com.lk.myeventbus.core.entity.LkThreadMode;import com.lk.myeventbus.core.entity.SubscrbleMethod;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;import java.util.Set;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class LkEventBus { private volatile static LkEventBus instance; private Handler handler; //线程池 private ExecutorService executorService; //缓存 订阅者对应所有标有注解的方法 private Map<Object, List<SubscrbleMethod>> cacheMap; public static LkEventBus getDefault(){ if (instance == null) { synchronized (LkEventBus.class) { if (instance == null) { instance = new LkEventBus(); } } } return instance; } private LkEventBus(){ cacheMap = new HashMap<>(); //创建主线程的Handler handler = new Handler(Looper.getMainLooper()); //初始化线程池 executorService = Executors.newCachedThreadPool(); } /** * 发送消息 * @param obj */ public void post(final Object obj){ //第一步线程同步这一块我们省略掉了 //第二步 遍历 //获取到key集合 Set<Object> set = cacheMap.keySet(); //获取迭代器 Iterator<Object> iterator = set.iterator(); //遍历 while (iterator.hasNext()){ //获取到订阅者 key是订阅者 final Object next = iterator.next(); //获取类中所有添加注解的方法 List<SubscrbleMethod> subscrbleMethods = cacheMap.get(next); //遍历所有的方法 for (final SubscrbleMethod subscrbleMethod : subscrbleMethods) { //判断这个方法是否是需要 根据参数类型去做判断 //isAssignableFrom 调用者和参数都是Class对象,调用者为父类,参数为本身或者其子类 //如果符合这个参数类型,也就是是需要接收消息的方法 if(subscrbleMethod.getParameteType().isAssignableFrom(obj.getClass())){ //就去执行这个方法 //下面做一下线程切换 通过注解中的线程标识去做 switch (subscrbleMethod.getThreadMode()){ case MAIN: //接收方法是主线程标识 //如果接收方法需要在主线程执行的 以及发送消息也是在主线程中发送的 if(Looper.myLooper() == Looper.getMainLooper()){ invoke(subscrbleMethod,next,obj); }else{ //如果接收如果接收方法需要在主线程执行的 而发送消息是在子线程中发送的 //这里我们就通过handler去执行这个方法 发送消息 handler.post(new Runnable() { @Override public void run() { invoke(subscrbleMethod,next,obj); } }); } break; case ASYNC: //接收方法是子线程标识 //如果接收消息的方法是需要在子线程中 而当前发送消息的线程是在主线程中 if(Looper.myLooper() == Looper.getMainLooper()){ //这类就用线程池来执行 executorService.execute(new Runnable() { @Override public void run() { invoke(subscrbleMethod,next,obj); } }); }else{ //如果接收消息的方法是需要在子线程中 并且当前发送消息的线程也是在子线程 //直接执行 invoke(subscrbleMethod,next,obj); } break; case POSTING: //接收方法是默认线程标识 //默认的情况,我们就不管线程了 invoke(subscrbleMethod,next,obj); break; } } } } } /** * 执行对应的方法 * @param subscrbleMethod * @param subscriber 订阅者实例 * @param obj 方法执行的参数 */ private void invoke(SubscrbleMethod subscrbleMethod, Object subscriber, Object obj) { try { subscrbleMethod.getMethod().invoke(subscriber,obj); } catch (Exception e) { e.printStackTrace(); } }}
流程图(EventBus发送消息的大致流程)
使用我们自己简易的EventBus
注册、取消注册、接收消息
package com.lk.myeventbus;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.widget.TextView;import android.widget.Toast;import com.lk.myeventbus.core.LkEventBus;import com.lk.myeventbus.core.LkSubsrcible;import com.lk.myeventbus.core.entity.LkThreadMode;import org.greenrobot.eventbus.EventBus;import org.greenrobot.eventbus.Subscribe;import org.greenrobot.eventbus.ThreadMode;public class MainActivity extends AppCompatActivity { TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //这里是EventBus的注册// EventBus.getDefault().register(this); //我们来使用自己的EventBus进行注册 LkEventBus.getDefault().register(this); tv = findViewById(R.id.tv); } //EventBus的注解标识// @Subscribe(threadMode = ThreadMode.MAIN) //我们换上自己的 @LkSubsrcible(threadMode = LkThreadMode.MAIN) public void receive(Friend friend){ tv.setText(friend.toString()); } @Override protected void onDestroy() { super.onDestroy(); //EventBus的取消注册// EventBus.getDefault().unregister(this); //用我们自己的 LkEventBus.getDefault().unregister(this); } public void change(View view) { startActivity(new Intent(MainActivity.this,TestActivity.class)); }}
发送消息
package com.lk.myeventbus;import android.app.Activity;import android.os.Bundle;import android.view.View;import com.lk.myeventbus.core.LkEventBus;import org.greenrobot.eventbus.EventBus;import androidx.annotation.Nullable;public class TestActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); } public void send(View view) { new Thread(new Runnable() { @Override public void run() {// EventBus.getDefault().post(new Friend("lk",18)); LkEventBus.getDefault().post(new Friend("lk",18)); } }).start(); }}
相互学习和进步,如有错误请指出,大神勿喷,谢谢
更多相关文章
- AndroidStudio中的图片资源存放位置以及drawable文件夹的创建方
- Android四大组件生命周期,组件类的继承与实现,Context
- Java 四种线程池的用法分析
- android,ExpandableList,将被选中的group置于顶端
- Android(安卓)挂断电话流程
- 深入源码分析Handler的消息处理机制
- Android中的自定义注解
- 查看Linux & Android中内存占用方法
- android 起动APP时锁住(Lock apps)