EventBus的onEvent被多次执行
最近项目开发一个评论功能A页面,另有B页面,C页面。A点击按钮后startActivityForResult()进入B页面,B点击按钮进入C页面同时finish B和A页面。C点击按钮后进入A同时finish C。
在A页面发表评论时发现在某些情况下会多有条相同内容的评论,查看后台数据库发现确实创建了多条相同内容的评论。
初步估计可能为消息系统重复发送评论请求。
查看服务器日志,确实在同一时间执行了多次添加评论的方法。
查看nginx日志,发现在同一时间有多条同一内容的添加评论请求,确定bug为客户端多次发送添加评论请求。
可发送评论按钮已经添加防重复点击判断,怎么会在同一时间(多次请求间隔仅几毫秒)发送多个添加评论请求呢?
在发送按钮点击事件添加log,请求发送前添加log,观察log,点击事件确实只触发一次,但却发送了多次添加评论请求。
发送评论请求使用的是EventBus的异步事件,到此可以确定问题在EventBus的post订阅上,网上查找资料,基本确定是eventBus没有正确unregister。
查看代码,在onCreateView方法中register eventBus,在onDestroyView中unregister eventBus,确定没有问题。又一次陷入头疼中。 再次查看log发现A页面的onDestroyView方法没有被调用。查看B页面代码,B点击进入C时的代码:
startActivity(new Intent(this, C.class));
Intent intent = new Intent();
setResult(RESULT_OK, intent);
finish();
而A页面onActivityResult函数没有执行。仔细一看B的代码startActivity(new Intent(this, C.class))页面跳转到了C页面,A的onActivityResult没有执行。 查看onActivityResult源码为一个空函数。查看api
protected void onActivityResult (int requestCode, int resultCode, Intent data)
Since:API Level 1 Called when an activity you launched exits, giving you the requestCode you started it with, the resultCode it returned, and any additional data from it. The resultCode will be RESULT_CANCELED if the activity explicitly returned that, didn’t return any result, or crashed during its operation.
You will receive this call immediately before onResume() when your activity is re-starting.
This method is never invoked if your activity sets noHistory to true.
翻译:
protected void onActivityResult (int requestCode, int resultCode, Intent data)
当你调用完一个存在的activity之后,onActivityResult将会返回以下数据:你调用时发出的requestCode、被调用activity的结果标志resultCode(如RESULT_OK)和其他的额外数据。我们期望的都是得到RESULT_OK,表示调用成功,但是当被调用activity什么也没返回,或者调用过程中发生崩溃时,resultCode的值会为RESULT_CANCELED,重新回到调用activity时会马上执行onActivityResult方法,然后才是onResume()方法。
如果你的activity设置noHistory为true,则这个方法永远不会被调用。
而项目中页面跳转到了C页面,没有返回A页面,所以A的onActivityResult没有执行。
将B中startActivity(new Intent(this, C.class));移到A的onActivityResult中,日志显示A的onDestroyView顺利执行,问题顺利解决。
但是为什么EventBus,没有unRegister后eventBus的事件会多次执行呢? 查看EventBus的register源码:
private synchronized void register(Object subscriber, boolean sticky, int priority) {
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod, sticky, priority);
}
}
首先看register函数的三个参数
subscriber: 就是要注册的订阅者
sticky: 表示是否是粘性的,一般默认都是false,除非你调用registerSticky方法了
priority: 表示事件的优先级,默认就行
看第一句代码:
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
这是从订阅者中查询所有订阅方法,即所有onEvent开头的方法。
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>();
Class<?> clazz = subscriberClass;
HashSet<String> eventTypesFound = new HashSet<String>();
StringBuilder methodKeyBuilder = new StringBuilder();
while (clazz != null) {
String name = clazz.getName();
if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
// Skip system classes, this just degrades performance
break;
}
// Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
String methodName = method.getName();
if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
ThreadMode threadMode;
if (modifierString.length() == 0) {
threadMode = ThreadMode.PostThread;
} else if (modifierString.equals("MainThread")) {
threadMode = ThreadMode.MainThread;
} else if (modifierString.equals("BackgroundThread")) {
threadMode = ThreadMode.BackgroundThread;
} else if (modifierString.equals("Async")) {
threadMode = ThreadMode.Async;
} else {
if (skipMethodVerificationForClasses.containsKey(clazz)) {
continue;
} else {
throw new EventBusException("Illegal onEvent method, check for typos: " + method);
}
}
Class<?> eventType = parameterTypes[0];
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(methodName);
methodKeyBuilder.append('>').append(eventType.getName());
String methodKey = methodKeyBuilder.toString();
if (eventTypesFound.add(methodKey)) {
// Only add if not already found in a sub class
subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
}
}
} else if (!skipMethodVerificationForClasses.containsKey(clazz)) {
Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "."
+ methodName);
}
}
}
clazz = clazz.getSuperclass();
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "
+ ON_EVENT_METHOD_NAME);
} else {
synchronized (methodCache) {
methodCache.put(key, subscriberMethods);
}
return subscriberMethods;
}
}
register的下句:
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod, sticky, priority);
}
迭代所有查询到的订阅方法,并调用subscribe方法:
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
Class<?> eventType = subscriberMethod.eventType;
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<Subscription>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
// Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
// subscriberMethod.method.setAccessible(true);
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
subscriptions.add(i, newSubscription);
break;
}
}
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<Class<?>>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
if (sticky) {
Object stickyEvent;
synchronized (stickyEvents) {
stickyEvent = stickyEvents.get(eventType);
}
if (stickyEvent != null) {
// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
// --> Strange corner case, which we don't take care of here.
postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
}
}
}
分析EventBus源码,重复register而没有报错。
此次记录EventBus register后一定要及时unregister,否则就可能会出现重复执行onEvent,甚至应用崩溃。
EventBus源码分析可以参看以下博客:
http://blog.csdn.net/lmj623565791/article/details/40920453
http://www.cnblogs.com/punkisnotdead/p/4794151.html