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

xiaoxiao2021-02-28  15

使用场景

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

转载请注明原文地址: https://www.6miu.com/read-1100281.html

最新回复(0)