Android插件中使用EventBus出现java.lang.IllegalArgumentException: Expected receiver of type xxx, but got xx

本文分析了在Android插件化架构中使用EventBus2进行跨插件通信时出现的异常问题,主要原因是不同ClassLoader导致的类同名转换异常,并提供了解决方案。

使用场景

由于所开发的Android项目是个老项目,EventBus使用的还是EventBus2,整个项目是插件化架构,不同插件使用的ClassLoader不同。插件1中有个onEvent方法,用来更新插件1中的一些信息。在插件2中发送一个EventBus消息更新插件1。在插件升级的时候(新老插件ClassLoader不同)偶现下述异常。

java.lang.IllegalArgumentException: Expected receiver of type 
com.yuntao.plugin.order.DetailActivity, but got 
com.yuntao.plugin.order.DetailActivity at java.lang.reflect.Method.invoke(Native Method) at 
java.lang.reflect.Method.invoke(Method.java:372) at 
de.greenrobot.event.EventBus.invokeSubscriber(EventBus.java:498) at 
de.greenrobot.event.EventBus.postToSubscription(EventBus.java:429) at 
de.greenrobot.event.EventBus.postSingleEventForEventType(EventBus.java:410) at 
de.greenrobot.event.EventBus.postSingleEvent(EventBus.java:383) at 
de.greenrobot.event.EventBus.post(EventBus.java:263) at 
com.yuntao.plugin.PaymentActivity$2.onClick(PaymentActivity.java:141) at 
android.view.View.performClick(View.java:4783) at 
android.view.View$PerformClick.run(View.java:19887) at 
android.os.Handler.handleCallback(Handler.java:739) at 
android.os.Handler.dispatchMessage(Handler.java:95) at 
android.os.Looper.loop(Looper.java:135) at 
android.app.ActivityThread.main(ActivityThread.java:5290) at 
java.lang.refle.... 

异常原因

看异常结果是在调用Method.invoke中抛出的。追到invoke方法查看

public Object invoke(Object receiver, Object... args)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        return invoke(receiver, args, isAccessible());
    }

    private native Object invoke(Object receiver, Object[] args, boolean accessible)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;

最终只是调用了一个Native方法。那看下方法的注释啥时候会抛出IllegalArgumentException异常

/**
@throws IllegalArgumentException
  * if the number of arguments doesn't match the number of parameters, the receiver
  * is incompatible with the declaring class, or an argument could not be unboxed
  * or converted by a widening conversion to the corresponding parameter type
  */

翻译来看遇到下述情况:参数数目不同,接收的对象与声明的类不一致,参数不能自动拆箱,参数不能通过拓宽转换为相应的参数类型。
由于参数都是String类型,异常是Expected receiver of type ,基本可以断定是receiver与声明的类不一致的问题。
写一段测试代码看看:

public class TestReflect {
    public void show(Integer a) {
        Log.i("pyt", a + "");
    }
}

public class TestReflect1 {
    public void show(Integer a) {
        Log.i("pyt", a + "");
    }
}

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
            Class<?> c = Class.forName("com.yuntao.testeventbus.TestReflect");
            Method method = c.getDeclaredMethod("show", Integer.class);
            method.setAccessible(true);
            method.invoke(new TestReflect1()); //正常传递c.newInstance(),这里换成另一个类的对象
        } catch (Exception e) {
            e.printStackTrace();
            Log.e("pyt", e.toString());
        }
    }

果然会抛出异常

 void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

EventBus2源码分析

按照method的声明类与invoke参数的对象声明类不一致会出现异常的思路来分析。
直接查看异常的调用栈,最终方法

 void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

这里写图片描述
看下两个类的关系Subscription里包含了EventBus注册的那个对象与onEvent方法。subscriber就是注册的实例,subscriberMethod中包含的就是当前实例的onEvent方法。上边的方法是直接取出method.invoke(subscription),都是保存在Subscription实例中的,那就要看Subscription实例是如何创建的了。

追溯到EventBus的register方法,查看

List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());

findSubscriberMethods方法

 List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        String key = subscriberClass.getName();
        List<SubscriberMethod> subscriberMethods;
        synchronized (methodCache) {
            subscriberMethods = methodCache.get(key);
        }
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        subscriberMethods = new ArrayList<SubscriberMethod>();
        ////省略n行代码,主要是反射当前subscriberClass中的onEvent方法,创建SubscriberMethod对象,缓存到methodCache(一个静态HashMap变量)中,key是类的name。
}        

看上述方法拿到了当前类的name,然后就会去methodCache中查找,有的话会直接返回。看到这里一定会联想到我们的框架,当有静态变量缓存的时候会出现的同名类转换异常问题(ClassLoader不同)。
当插件升级的时候,如果methodCache缓存了旧的插件的method,新插件获取到了旧的插件的method,然后invoke方法传入的是新插件的实例,这时候会出现异常。

上述分析比较简单,不懂可以详细参考EventBus源码

解决方案

更改EventBus源码。更改methodCache的key为Class对象,或者为Class对象的hashCode,这样可以使用SparseArray缓存,提高性能
在创建新的插件的时候调用EventBus的clearCaches方法清空缓存。

<think>好的,我现在需要解决用户遇到的Java程序中的NoClassDefFoundError问题,具体是缺少com.google.common.eventbus.EventBus类。用户希望知道如何通过Maven依赖来解决这个问题。 首先,我得确认这个类属于哪个库。根据经验,Google的EventBus库通常有两个常见的实现:一个是Guava库中的EventBus,另一个是greenrobot的EventBus。用户提到的类名是com.google.common.eventbus.EventBus,这明显属于Guava库,因为包名是com.google.common。所以,用户需要添加Guava的依赖到项目中。 接下来,我需要检查Maven仓库中Guava的最新版本。访问Maven中央仓库网站,搜索Guava,找到最新的稳定版本。假设当前最新版本是31.1-jre,所以依赖配置应该是: ```xml <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version> </dependency> ``` 然后,用户添加依赖后可能还会遇到问题,比如依赖冲突或者作用域(scope)的问题。比如,如果项目中其他依赖引用了旧版本的Guava,可能会导致类加载失败。这时候需要检查依赖树,使用mvn dependency:tree命令来查看是否有版本冲突,并排除旧版本。 另外,用户可能错误地引用了其他EventBus库,比如greenrobot的EventBus,这时候需要确认依赖是否正确。比如,greenrobot的EventBus的包名是org.greenrobot.eventbus,而用户的问题中提到的包名是com.google.common,所以这里需要明确区分。 还有,用户可能没有正确配置Maven仓库,或者网络问题导致依赖下载失败,这时候需要检查本地仓库是否存在对应的jar包,或者尝试重新构建项目。 另外,关于作用域的问题,如果依赖被声明为provided或test,那么在运行时可能不会包含进去,导致类找不到。需要确保依赖的scope是compile(默认),这样在编译和运行时都会包含。 最后,用户可能需要重新导入Maven项目,清理并重新构建,确保IDE正确加载了新的依赖。如果使用IntelliJ,可能需要点击Maven面板的重新导入按钮,或者执行mvn clean install命令。 总结步骤:添加正确的Guava依赖,检查版本冲突,排除旧版本,确保作用域正确,重新构建项目。</think>要解决$java.lang.NoClassDefFoundError: com.google.common.eventbus.EventBus$错误,需通过Maven正确引入Guava库依赖,以下是具体步骤: 1. **添加Guava依赖** ```xml <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version> </dependency> ``` 该依赖会将包含$EventBus$类的Guava库引入项目[^1]。 2. **验证依赖冲突** 执行命令检查依赖树: ```bash mvn dependency:tree -Dincludes=com.google.guava ``` 若存在旧版本Guava,需在冲突依赖中添加排除: ```xml <exclusions> <exclusion> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </exclusion> </exclusions> ``` 3. **检查作用域配置** 确保依赖未设置非常用作用域(如$<scope>test</scope>$或$<scope>provided</scope>$),默认作用域为$compile$时会自动包含在运行时环境[^3]。 4. **IDE同步操作** 在IntelliJ/Eclipse中执行: - Maven面板点击"Reload Project" - 执行$mvn clean install$ - 检查target目录下是否包含guava-31.1-jre.jar
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值