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