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

使用场景

由于所开发的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方法清空缓存。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值