手写EventBus框架

本文介绍了如何从零开始封装一个类似EventBus的轻量级框架,详细讲解了订阅者订阅事件、根据标签存储订阅者、发布事件及注销的过程,旨在实现组件间解耦并避免引入大型库导致的项目臃肿。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.EventBus是什么

EventBus是一款Android下的发布/订阅事件总线机制。可以代替Intent、Handler、Broadcast等在Fragment、Activity之间传递消息。
优点:开销小,代码优雅。将发送者和接受者解耦。

二.为什么我们要写一套自己框架呢

既然官方的EventBus这么牛逼,为什么我们还要自己写一套呢,问得好,官方的EventBus虽然很牛逼,但是不一定每个功能我们都用得到啊,如果你在项目里引入想这些官方的框架,你的项目就会很臃肿,笨重。那么问题来了,如何实现EventBus的功能还不显得笨重呢,接下来就来看看吧。

三.如何封装

大家都知道eventBus有自己的订阅者,发布者,发布者发布消息,订阅者就都能接收到消息。

1.订阅者订阅事件

订阅者一般都是写一个方法,然后在标明自己的标签,发布者根据订阅者的标签发送到对应的订阅者身上。
那么如何给方法提供标注呢
其实较好的方法是注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
    String[] value();
}

此注解表明在方法体上并且保留到运行时,定义一个数组标签,因为有的订阅者需要多个标签。

然后我们在给订阅者订阅事件

@Subscribe({"1"})
public void test1(String msg,String msg1){
    Log.e("test==========","msg:"+msg+"  msg1:"+msg1);
}
@Subscribe({"1","2"})
public void test2(String msg,Integer msg1){
    Log.e("test==========","msg:"+msg+"  msg1:"+msg1);
}

2.根据标签存储订阅者

第一步我们已经表明了哪些方法时订阅者,那我们要如何让发布者知道呢,我们可以把所有的订阅者根据标签存储到一个对象池,到时候发布者要发布消息得时候就可以从里面去找啦。
①.拿到调用者的所有订阅对象

/**
 * 拿到订阅的方法
 * @param object 调用者
 * @return
 */
public List<SubscribeMethod> findSubscribeMethod(Object object) {
    Class<?> subscribeClass = object.getClass();
    List<SubscribeMethod> subscribeMethods = methodmap.get(subscribeClass);
    if (subscribeMethods == null) {
        subscribeMethods = new ArrayList<>();
        //拿到里面的所有方法
        Method[] declaredMethods = subscribeClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            //拿到注解
            Subscribe subscribe = method.getAnnotation(Subscribe.class);
            if (subscribe != null) {
                String[] values = subscribe.value();
                //拿到方法的参数类型集合
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (String lable : values) {
                    method.setAccessible(true);
                    //一个标签就存储一个subscribeMethod
                    subscribeMethods.add(new SubscribeMethod(lable, method, parameterTypes));
                }
            }
        }
        methodmap.put(subscribeClass, subscribeMethods);
    }
    return subscribeMethods;
}

因为反射会耗费性能,所有我们就把对象存储在对象池里,下次就不用在反射
1.先去缓存区区找,如果找不到就去传进来的调用者去获取
2.根据调用者去获取所有的方法
3.遍历所有的方法体,找到注解的方法
4.拿到注解方法的value[]
5.最后和method,parameterType,lable组成一个对象存储到methodmap里
实际上拿到的是这样的:
在这里插入图片描述
②.根据标签存储订阅者

到这里methodmap就存储完成,虽然说订阅者的标签有很多相同的,但是相同的标签对应的是不同的订阅者,那我们就可以根据标签存储订阅者集合,存到subscriptionmap对象池里,把SubscribeMethod和Object(调用者)存到Subscription,到时候发布消息,各个订阅者都能接受到消息,如果不明白的可以看下图:

在这里插入图片描述

其中object是调用者,即方法对应的类对象,后面执行的时候会用到

/**
 * 订阅
 * @param object
 */
public void regesiter(Object object) {
    if(resultmap.containsKey(object.getClass())){
        return;
    }
    //取消订阅时用
    List<String> lables = resultmap.get(object.getClass());
    if (lables == null) {
        lables = new ArrayList<>();
        resultmap.put(object.getClass(), lables);
    }
    //找到所有的订阅者,一个标签对应一个订阅者,包含重复订阅者
    List<SubscribeMethod> subscribeMethods = findSubscribeMethod(object);
    for (SubscribeMethod subscribeMethod : subscribeMethods) {
        String lable = subscribeMethod.getLable();
        if (!lables.contains(lable)) {
            lables.add(lable);
        }
        //一个标签对应所有Subscription对象,subscriptions里的都是相同的标签
        List<Subscription> subscriptions = subscriptionmap.get(lable);
        if (subscriptions == null) {
            subscriptions = new ArrayList<>();
            subscriptionmap.put(lable, subscriptions);
        }
        subscriptions.add(new Subscription(subscribeMethod, object));
    }
}

一样的,也是保存到对象池里,if(resultmap.containsKey (object.getClass())){ return; }这行代码是为了避免重复跳到这个页面的时候重复添加subscriptionmap对象池,methodmap就不用了,它是根据类进行区分,在findSubscribeMethod()就做好了判断。

3.发布事件

发布事件,实际上是拿到方法的调用者,在传入实际的参数,对应的订阅者才会响应,因为传入的参数各有不同,所有还需在加一层判断,具体实现见如下代码:

/**
 * 发送消息给订阅者
 *
 * @param lable  标签
 * @param params 实际参数数组
 */
public void post(String lable, Object... params) {
    List<Subscription> subscriptions = subscriptionmap.get(lable);
    if (null == subscriptions) {
        return;
    }
    for (Subscription subscription :
            subscriptions) {
        SubscribeMethod subscribeMethod = subscription.getSubscribeMethod();
        //调用者
        Object object = subscription.getObject();
        //方法
        Method method = subscribeMethod.getMethod();
        //参数类型数组
        Class[] argumentType = subscribeMethod.getArgumentType();
        Object[] realparams = new Object[argumentType.length];
        if (params != null) {
            for (int i = 0; i < argumentType.length; i++) {
            //如果长度在params范围之内并且类型会对应则存储,否则存null
                if (i < params.length && argumentType[i].isInstance(params[i])) {
                    realparams[i] = params[i];
                } else {
                    realparams[i] = null;
                }
            }
        }
        try {
           //调用方法
            method.invoke(object, realparams);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

  1. 根据标签拿到SubScription集合
  2. 遍历Subscription集合,拿到SubscribeMethod,Object调用者,在拿到method
  3. 传进来的参数如果不是对应的类型,或者数组的长度不是正确的长度,则给他赋空
Object[] realparams = new Object[argumentType.length];
        if (params != null) {
            for (int i = 0; i < argumentType.length; i++) {
                if (i < params.length && argumentType[i].isInstance(params[i])) {
                    realparams[i] = params[i];
                } else {
                    realparams[i] = null;
                }
            }
        }

4.最后传入参数调用方法

method.invoke(object, realparams);

4.注销

有订阅当然要有取消订阅,我们可以在订阅的时候根据类保存所有的标签,然后在取消订阅的时候根据类拿到对应的标签集合,遍历标签集合,把所有含有该标签的订阅者给移除。如下图所示:
在这里插入图片描述

/**
 * 取消订阅
 */
public void unregister(Object object) {
    List<String> lables = resultmap.get(object.getClass());
    if (lables != null) {
        for (String lable : lables) {
            List<Subscription> subscriptions = subscriptionmap.get(lable);
            Iterator<Subscription> iterator = subscriptions.iterator();
            while (iterator.hasNext()){
                Subscription subscription = iterator.next();
                Object object1 = subscription.getObject();
                if(object1==object){
                    iterator.remove();
                }
            }
        }
       if(resultmap.containsKey(object.getClass())){
           resultmap.remove(object.getClass());
       }
    }
}}

四.框架的使用

package com.example.myeventbus;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import com.example.myeventbus.bus.MyBus;
import com.example.myeventbus.bus.Subscribe;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //订阅
        MyBus.getInstance().regesiter(this);
    }

    @Subscribe({"1"})
    public void test1(String msg,String msg1){
        Log.e("main==test===","msg:"+msg+"  msg1:"+msg1);
    }
    @Subscribe({"1","2"})
    public void test2(String msg,Integer msg1){
        Log.e("main==test===","msg:"+msg+"  msg1:"+msg1);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //取消订阅
        MyBus.getInstance().unregister(this);
    }

    public void skip(View view) {
        startActivity(new Intent(this,Main2Activity.class));
    }

    public void traverse(View view) {
        MyBus.getInstance().traverse();
    }
}

package com.example.myeventbus;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import com.example.myeventbus.bus.MyBus;
import com.example.myeventbus.bus.Subscribe;

public class Main2Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        MyBus.getInstance().regesiter(this);
        //发布事件
        MyBus.getInstance().post("1","1",2);
    }

    @Subscribe("1")
    public void test(String msg,String msg1,String msg2){
        Log.e("sencod==test===","msg:"+msg+" msg1:"+msg1+" msg2:"+msg2);
    }

    public void skip(View view) {
        startActivity(new Intent(this,MainActivity.class));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        MyBus.getInstance().unregister(this);
    }

    public void traverse(View view) {
        MyBus.getInstance().traverse();
    }
}

到这里就结束了,大家看看是不是很简单。
最后附上源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值