前言
在之前的文章 Android 注解系列之APT工具(三) 中,我们介绍了 APT 技术的及其使用方式,也提到了一些知名的开源框架如 Dagger2、ButterKnife、EventBus 都使用了该技术。为了让大家更好的了解 APT 技术的使用,在接下来的文章中我将会着重带领大家来了解 EventBus 中 APT 技术的使用,在了解该知识之前,需要我们对 EventBus 内部原理较为熟悉,如果你已经熟悉其内部机制了,可以跳过该篇文章,直接阅读 Android 注解系列之EventBus3 “加速引擎“(五)。
阅读该篇文章,我们能够学到如下知识点:
EventBus3 内部原理
EventBus3 订阅与发送消息原理
EventBus3 线程切换的原理
EventBus3 粘性事件的处理
整篇文章结合 EventBus 3.1.1 版本进行讲解。
EventBus 简介
EventBus 对于 Android 程序员来说应该不是很陌生,它是基于观察者模式的事件发布/订阅框架,我们常常用它来实现不同组件的通讯,后台线程通信等。
EventBus-Publish-Subscribe.png
虽然 EventBus 非常简单好用,但是还是会因为 EventBus 满天飞,使程序代码结构非常混乱,难以测试和追踪。即使 EventBus 有很多诟病,但仍然不影响我们去学习其中的原理与编程思想~
大概流程
在了解 EventBus 内部原理之前,我们先了解一下 EventBus 框架的一个大概流程。如下图所示:
上图中绿色
为订阅流程,红色
为发送事件流程,大家可以结合上图,来理解源码。
在上图中我们在 A.java
中订阅了事件 AEvent
,在 B.java
中订阅了事件 AEvent
与 BEvent
,下面我们来分析 EventBus 中注册与事件发送的两个流程,在介绍两个流程之前,先介绍一下 Subscription
与 SubscriberMethod
中所包含的内容。
Subscription
类中包含以下内容:
当前注册对象
对应订阅方法的封装对象 SubscriberMethod
SubscriberMethod
类中包含以下内容:
包含 @Subscribe
注解的方法的 Method (java.lang.reflect
包下的对象)。
@Subscribe
注解中设置的线程模式 ThreadMode
方法的注册的事件类型的 Class 对象
@Subscribe
中设置的优先级 priority
@Subscribe
中设置事件是否是粘性事件 sticky
注册流程
当我们通过调用 EventBus.register() 注册 A、B 两个对象时,EventBus 会做以下几件事件:
通过内部的 SubscriberMethodFinder
来获取 A、B类中含有 @Subscribe
注解的方法,并将该注解中的内容与对应方法封装为 SubscriberMethod
对象。然后再将当前订阅对象与对应的 SubscriberMethod
再封装为 Subscription
对象。
将所有的 Subscription
放在名为 subscriptionsByEventType
类型为 Map, CopyOnWriteArrayList>
数据结构(key 为事件类型的 Class 对象) 中,因为 Subscription
对象内部包含 SubscriberMethod
, 那么就能知道订阅的事件类型,所以我们可以根据事件类型来区分 Subscription
,又因为相同事件可以被不同订阅者中的方法来订阅,所以相同类型的事件也就以对应不同的 Subscription
。
将订阅者中的所有订阅的事件都封装在名为 typesBySubscriber
类型为 Map>>
数据结构(key 为订阅对象,value 为该对象订阅的事件类型 Class 对象)。该集合主要用于取消订阅,在下文中我们会进行介绍。
在整个注册流程中,最主要的流程就是 EventBus 通过 SubscriberMethodFinder
去获取类中包含 @Subscribe
注解的订阅方法。在 EventBus 3.0 之前该流程一直都是通过反射
的方式去获取。在 3.0 及以后版本,EventBus 采用了 APT 技术,对 SubscriberMethodFinder
查找订阅方法流程进行了优化,使其能在 EventBus.register()
方法调用之前就能知道相关订阅事件的方法,这样就减少了程序在运行期间使用反射遍历获取方法所带来的时间消耗。在下文中我们也会指出具体的优化点。
事件发送流程
知道了 EventBus 的注册过程,再来了解事件的发生流程就非常简单了。因为我们已经通过 subscriptionsByEventType
存储事件对应的 Subscription
,只要找到了 Subscription
,那么我们就能从 Subscription
拿到订阅事件的对象 subscriber
,以及对应的订阅方法 Method (java.lang.reflect
包下的对象)。然后通过反射调用:
Subscription 内部包含订阅者及 SubscriberMethod(内部包含订阅方法 Method )
method.invoke(subscription.subscriber, event)
通过上述方法,就能将对应事件发送到相关订阅者了。当然这里只是简单的介绍了事件是如何发送到相关订阅者的。关于 EventBus 中粘性事件的处理,线程如何切换。会在下文中进行详细介绍。
源码分析
在了解了 EventBus 的内部大概流程后,现在我们通过源码来更深层次的了解其内部实现。还是从订阅过程与事件的发送两个过程进行讲解。
订阅过程源码分析
EventBus 的订阅入口为 register() 方法,如下所示:
public void register(Object subscriber) {
Class subscriberClass = subscriber.getClass();
//流程1:获取对应类中所有的订阅方法
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
//流程2:实际订阅
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
在该方法中,主要涉及到 SubscriberMethodFinder 查找方法与实际订阅两个流程,下面我们会对这两个流程进行介绍。
SubscriberMethodFinder 查找方法流程
在该流程中,主要通过 SubscriberMethodFinder
去获取订阅者中所有的 SubscriberMethod ,我们先看 findSubscriberMethods()
方法:
List findSubscriberMethods(Class subscriberClass) {
//从缓存中获取订阅者中的订阅方法,如果有则读缓存,如果没有进行查找
List subscriberMethods = (List)METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
} else {
if (this.ignoreGeneratedIndex) {//如果忽略索引类,则使用反射。
subscriberMethods = this.findUsingReflection(subscriberClass);
} else {//否则使用索引类
subscriberMethods = this.findUsingInfo(subscriberClass);
}
//如果订阅者没有订阅方法,则抛出异常
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation”);
} else {
//将对应类中的订阅方法,添加到缓存中,提高效率,方便下次查找
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
}
该方法的逻辑也非常简单,为如下几个步骤:
步骤1:先从缓存( METHOD_CACHE
)中获取订阅者对应的 SubscriberMethod(订阅方法)
,如果有则从缓存中取。
步骤2:如果缓存中没有,则通过布尔变量 ignoreGeneratedIndex
,来判断是直接使用反射获取订阅方法,还是通过索引类
(EventBus 3.0 使用APT 增加的类)来获取。因为 ignoreGeneratedIndex
默认值为 false ,则默认会走 findUsingInfo()
方法
步骤3:将步骤2中获得的订阅方法集合,存储到缓存中,方便下一次获取,提高效率。
因为默认会走 findUsingInfo()
方法,我们继续查看该方法:
private List findUsingInfo(Class subscriberClass) {
//步骤1:构建了查询状态缓存池,最多缓存4个类的查询状态
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
//步骤2,获取查找状态对应的订阅信息,?这里EventBus 3.0 使用了索引类,
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
//将订阅者的所有的订阅方法添加到FindState的集合中
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {//步骤3:如果订阅信息为null,则通过反射来获取类中所有的方法
findUsingReflectionInSingleClass(findState);
}// 继续查找父类的方法
findState.moveToSuperclass();
}
//步骤4,获取findState中的所有方法,并清空对象池
return getMethodsAndRelease(findState);
}
步骤1:创建与订阅者相关的 FindState 对象。会从 FinState 对象缓存池(最大为4个)中获取,一个订阅者对象对应一个FindState,一个订阅者对象对应一个或多个订阅方法。
步骤2:通过 FindState 对象 调用 getSubscriberInfo()
方法去获取订阅者相关的订阅方法信息。该方法使用了 APT 技术,构建了EventBus的索引类。关于具体的优化,会在下篇文章中 Android 注解系列之EventBus3 “加速引擎“(五)进行描述,大家这里有个印象就好了。
步骤3:如果通过步骤2获取不到订阅方法信息,则通过反射
来获取类中的所有的订阅方法。并将获取的方法,封装到 FindState 中的 subscriberMethods 集合中去。
步骤4:将 FindState 对象中的 subscriberMethods 集合返回。
在上述方法中,我们需要注意的是,如果当前订阅着没有相关的订阅方法,那么会依次遍历其父类的订阅方法。还有一个知识点,就是该方法中 FindState 使用了 对象缓存池
,不会每次注册一个订阅者就创建 一个FindState 对象。这样就节约了内存的使用。
关于索引类的知识点,会在下篇文章中进行介绍,这里我们直接查看 findUsingReflectionInSingleClass()
方法:
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
//获取当前订阅者中的所有的方法
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
//获取该类的所有public 方法 包括继承的公有方法
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
//循环遍历所有的方法,通过相关注解找到相应的订阅方法。
for (Method method : methods) {
int modifiers = method.getModifiers();
//满足修饰符为 public 并且非抽象、非静态
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class[] parameterTypes = method.getParameterTypes();
//找到参数为1,且该方法包含Subscrile注解的方法
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
// 创建订阅方法对象,并将对应方法对象,事件类型,线程模式,优先级,粘性事件封装到SubscriberMethod对象中。
ThreadMode threadMode = subscribeAnnotation.threadMode();
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract”);
}
}
}
该方法的逻辑也非常简单,通过获取 FindState 中的订阅者的 Class 对象,然后通过反射获取所有包含 @Subscribe
注解且参数为 1
的 Method 对象,并读取到该参数的类型EventType
,接着读取注解中的 thredMode
、priority
、sticy
,最后将这些数据都统一分装到新建的SubscriberMethod
对象中,最后将该对象添加到 FindState 中的 subscriberMethods 集合中去。
实际订阅方法 subscribe
当找到订阅者所有的方法集合后,最终会遍历调用 subscribe()
方法,查看该方法:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class eventType = subscriberMethod.eventType;
//步骤1,将每个订阅方法和订阅者封装成Subscription
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//步骤2,获取对应事件中所有的 Subscription,判断是否重复添加
CopyOnWriteArrayList subscriptions = (CopyOnWriteArrayList)this.subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList();
this.subscriptionsByEventType.put(eventType, subscriptions);
} else if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType);
}
//步骤3,根据优先级,将当前新封装的Subscription对象添加到subscriptionsByEventType中去
int size = subscriptions.size();
for(int i = 0; i <= size; ++i) {
if (i == size || subscriberMethod.priority > ((Subscription)subscriptions.get(i)).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
//步骤4,将当前订阅者中与当前订阅者所订阅的事件类型,添加到typesBySubscriber中去
List> subscribedEvents = (List)this.typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList();
this.typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//步骤5,如果该方法有订阅了粘性事件,则从stickyEvents中获取相应粘性事件,并发送
if (subscriberMethod.sticky) {
if (eventInheritance) {
Set, Object>> entries = stickyEvents.entrySet();
for (Map.Entry, Object> entry : entries) {
Class candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = this.stickyEvents.get(eventType);
this.checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
在上述方法中主要流程如下:
步骤1,将每个订阅方法和订阅者封装成 Subscription。
步骤2,获取对应事件中所有的 Subscription ,判断是否重复添加。
步骤3,根据 优先级
,将当前新封装的 Subscription 对象添加到 subscriptionsByEventType 中去。(设置了优先级后,EvenBus 就可以按照优先级顺序,将事件发送给订阅者)
步骤4,将当前订阅者中与当前订阅者所订阅的事件类型,添加到 typesBySubscriber 中去。
步骤5,如果该方法有订阅了粘性事件,则从 stickyEvents 中获取相应粘性事件,并发送。
再结合我们最开始所画的 EventBus 大致流程,该方法其实就做了下图红色虚线框
中的事:
关于粘性事件的知识点,需要我们了解事件的发送流程,我们会在下文进行详细介绍。
事件发送流程源码分析
事件的发送,主要分为简单事件
与粘性事件
,分别对应方法为 post()
与 postSticky()
两个方法。这里我们先看简单事件的发送,代码如下:
简单事件的发送
public void post(Object event) {
//步骤1,获取当前线程中独立拥有的PostingThreadState,并从中获取事件队列(eventQueue),将发送的事件添加到该队列中
EventBus.PostingThreadState postingState = (EventBus.PostingThreadState)this.currentPostingThreadState.get();
List eventQueue = postingState.eventQueue;
eventQueue.add(event);
//步骤2:判断当前线程是否正在分发事件,如果不是,则循环遍历事件队列中的事件,并将事件分发出去,直到当前事件队列空为止
if (!postingState.isPosting) {
postingState.isMainThread = this.isMainThread();
postingState.isPosting = true;
//如果当前分发事件状态为取消,则抛出异常
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset”);
}
//循环遍历事件队列,并将消息发送出去
try {
while(!eventQueue.isEmpty()) {
this.postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
在 EventBus 中会为个每调用 post() 方法的线程都会创建一个唯一的 PostingThreadState
对象,用于记录当前线程存储发送消息与发送的状态,其内部结构如下所示:
PositingThreadState与线程的关系.jpg
PostingThreadState 使用了 ThreadLocal 不熟悉 ThreadLocal 的小伙伴,可以查看该篇文章:Android Handler机制之ThreadLocal
也就是说当我们调用 EventBus.post()
方法,其实是从 EventQueue 队列中取出消息,然后通过调用 postSingleEvent()方法 来实际发送消息,该方法代码如下所示:
private void postSingleEvent(Object event, EventBus.PostingThreadState postingState) throws Error {
Class eventClass = event.getClass();
boolean subscriptionFound = false;
//步骤1:?判断否事件传递发送
if (this.eventInheritance) {
List> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for(int h = 0; h < countTypes; ++h) {
Class clazz = (Class)eventTypes.get(h);
//?循环遍历遍历事件并发送
subscriptionFound |= this.postSingleEventForEventType(event, postingState, clazz);
}
} else {
//步骤2:?如果不支持事件的传递,那么这里开始发送事件。
subscriptionFound = this.postSingleEventForEventType(event, postingState, eventClass);
}
//步骤3:如果没有找到订阅的方式,提示用户
if (!subscriptionFound) {
if (this.logNoSubscriberMessages) {
this.logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (this.sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {
this.post(new NoSubscriberEvent(this, event));
}
}
}
该方法主要为如下三个步骤:
步骤1:通过布尔变量 eventInheritance
判断是否支持事件是否传递发送,如果支持,那么通过lookupAllEventTypes()
方法获得发送事件祖先类及其接口。然后通过 postSingleEventForEventType()
方法,将它们都发送出去,
步骤2:步骤1返回 false 那么就直接使用 postSingleEventForEventType()
方法发送事件。
步骤3:如果没有找到相关的订阅方法,那么就提示用户没有相关的订阅方法。
布尔变量 eventInheritance
默认为 false
,我们可以通过 EventBusBuilder 来配置该变量的值。
那什么是事件的传递发送呢?我们来查看 lookupAllEventTypes()
方法:
private static List> lookupAllEventTypes(Class eventClass) {
synchronized (eventTypesCache) {
List> eventTypes = eventTypesCache.get(eventClass);
if (eventTypes == null) {
eventTypes = new ArrayList<>();
Class clazz = eventClass;
//?获取该类所有祖先类及其接口
while (clazz != null) {
eventTypes.add(clazz);
addInterfaces(eventTypes, clazz.getInterfaces());
clazz = clazz.getSuperclass();
}
eventTypesCache.put(eventClass, eventTypes);
}
return eventTypes;
}
}
//将接口添加到集合中
static void addInterfaces(List> eventTypes, Class[] interfaces) {
for (Class interfaceClass : interfaces) {
if (!eventTypes.contains(interfaceClass)) {
eventTypes.add(interfaceClass);
addInterfaces(eventTypes, interfaceClass.getInterfaces());
}
}
}
在该方法中,会获取发送事件的所有的祖先类及其接口,最后将他们以集合的方式返回,在 postSingleEvent
方法中拿到这个集合之后,那么就会将集合中所有的数据都发送出去。这样做会造成什么效果呢?如果当前我们的继承体系为 Aevent -> Bevent -> Cevent ( ->
表示继承),那么通过发送 Aevent,那么其他所有订阅过 Bevent 及 Cevent 的订阅者都会收到消息。
我们继续查看 postSingleEventForEventType()
方法,代码如下所示:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class eventClass) {
CopyOnWriteArrayList subscriptions;
//?从缓存中拿取之前存取的 Subscription
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
//?这里找到相应的方法后,开始切换线程了。
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
该方法的逻辑非常简单,就是从我们之前的 subscriptionsByEventType
集合中拿到存储的 Subscription
,并根据当前线程状态设置关联的 PostingState
中 canceled
、subscription
、isMainThread
等属性值,然后通过 postToSubscription()
方法来真正的执行事件的传递。
到目前为止整个流程如下所示:
postToSubscription()
postToSubscription() 方法是真正实际将事件传递到订阅者的代码。查看该方法:
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
从上述方法中,我们拿到 Subscription
中成员变量 SubscriberMethod
中的线程模式 threadMode
来判断订阅方法需要执行的线程。如果当前线程模式是 POSTING
,那么默认就直接调用 invokeSubscriber()
方法。具体代码如下所示:
void invokeSubscriber(Subscription subscription, Object event) {
try {
//?直接通过反射调用订阅方法。
subscription.subscriberMethod.method.
invoke(subscription.subscriber, event);
}
//省略部分代码
}
如果为其他模式,那么会根据相应的 poster
调用 enqueue()
方法来控制执行订阅方法所在的线程。在 EventBus 中提供了如下三个 Poster 来控制订阅方法的所运行的线程。
HandlerPoster (切换到主线程)
BackgroundPoster (切换到后台线程)
AsyncPoster (切换到后台线程)
以上三个 Poster 都实现了 Poster 接口,且内部都维护了一个名为 PendingPostQueue
的队列,该队列以 PendingPost
为存储单元,其中 PendingPost
中存储内容为我们根据当前事件所找到的 Subscription
与当前所发生的事件。
那么结合整个流程,我们能得到下图:
针对上图,再进行一下简单的说明。
当我们调用 EventBus.post()
发送简单事件时,会将该事件放入与线程相关的 PostingThreadState
的 EventQueue
中。
接着会从之前在 subscriptionsByEventType
集合中找到与该事件相关的 Subscription
。
接着将找到的 Subscription
与当前所发送的事件都封装为 PendingPost
并添加到对应 Poster
中的 PendingPostQueue
队列中。
最后对应的 Poster
从队列中取出相应的 PendingPost
,通过反射调用订阅者的订阅方法。
其中订阅方法执行线程的规则,如下所示:
线程的切换
在上节中,订阅者的订阅方法执行的所在线程,是由 EventBus 中内部的三个 Poster
来实现的。那下面我们就来看看这三个 Poster
的实现。
HandlerPoster
public class HandlerPoster extends Handler implements Poster {
private final PendingPostQueue queue;
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
private boolean handlerActive;
//默认会传递主线程的Looper
protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
//?这里将PedingPost放入PendingPostQueue中,然后发送消息
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message”);
}
}
}
}
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
//?从队列中取出最近的PendingPost
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
//?直接通过反射,调用订阅者的订阅方法。
eventBus.invokeSubscriber(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message”);
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
}
HanderPoster 中的逻辑非常容易理解,继承 Handler,并在初始化的时候默认会关联 主线程
的 Looper,这样该 Handler 所发送的消息将会在主线程中被处理。
分析一下 HanderPoster 中主要的步骤:
在调用 enqueue()
方法时,会将之前我们封装好的 PendingPost
放入 PendingPostQueue
队列中,同时发送消息。
在 handleMessage()
方法中,从 PendingPostQueue
队列中取出最近的 PendingPost
,然后直接通过 eventBus.invokeSubscriber()
反射执行订阅者的订阅方法。
BackgroundPoster
final class BackgroundPoster implements Runnable, Poster {
private final PendingPostQueue queue;
private final EventBus eventBus;
private volatile boolean executorRunning;
BackgroundPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
//使用线程池来提交任务,该方法是线程安全的。
synchronized (this) {
queue.enqueue(pendingPost);
if (!executorRunning) {
executorRunning = true;
eventBus.getExecutorService().execute(this);
}
}
}
@Override
public void run() {
try {
try {
while (true) {
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}
}
BackgroundPoster 与 HandlerPoster 最大的不同是其内部使用了线程池,并且该类也实现了 Runnable 接口。
在 BackgroundPoster 中的 enqueue()
方法中,默认会使用 EventBus 中默认的线程池 DEFAULT_EXECUTOR_SERVICE
来提交任务 ,该线程池的声明如下:
private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
CachedThreadPool 适用于大量的且耗时较少的任务
同样的,BackgroundPoster 也就是通过反射调用订阅者的订阅方法,只不过不同的是它是放入线程池中的非主线程中进行执行。
需要注意的是不管是在任何线程中发送消息,EventBus 总是线程安全的。从 BackgroundPoster 的代码中我们就可以看出。
AsyncPoster
class AsyncPoster implements Runnable, Poster {
private final PendingPostQueue queue;
private final EventBus eventBus;
AsyncPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
queue.enqueue(pendingPost);
eventBus.getExecutorService().execute(this);
}
@Override
public void run() {
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available”);
}
eventBus.invokeSubscriber(pendingPost);
}
}
这里就不对 AsyncPoster 进行讲解了,相信大家根据之前的内容也能理解。
粘性事件的发送
现在我们还剩最后一个知识点了,就是粘性事件的发送。在 EventBus 中发送粘性事件,我们需要调用方法 postSticky()
方法,代码如下所示:
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
post(event);
}
从代码中,我们不难看出,粘性的事件发送与简单事件的发送唯一的区别就是将发送的事件添加到 stickyEvents
集合中去了。那为什么要这么做呢?在了解具体的原因之前,我们需要了解粘性事件的概念。
粘性事件的概念:当订阅者还没有订阅相关事件 A
时,程序已经发送了一些事件 A
,按照正常的逻辑,当订阅者开始订阅事件 A
时,是接受不到程序已经发送过的事件 A
,但是我们希望接受到那些已经发送过的消息。这种已经过时,但又被重新接受的事件,我们称之为粘性事件。
那么根据粘性事件的思想,我们需要将已经发送的事件存储下来,并在粘性事件的订阅的过程中进行特别的处理,也就是在 EventBus.register()
方法中进行处理。还记得之前注册过程中的 subscribe()
方法吗?该方法内部对粘性事件进行了特殊的处理,代码如下所示:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//省略部分代码
//判断是否是粘性事件
if (subscriberMethod.sticky) {
//?支持事件传递的粘性事件
if (eventInheritance) {
Set, Object>> entries = stickyEvents.entrySet();
for (Map.Entry, Object> entry : entries) {
Class candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
//?开始执行订阅方法。
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
在上述逻辑中,会从 stickyEvents
中获取之前发送的事件,然后调用 checkPostStickyEventToSubscription()
。该方法代码如下所示:
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
postToSubscription(newSubscription, stickyEvent, isMainThread());
}
}
又因为checkPostStickyEventToSubscription()
方法内部会调用 postToSubscription()
方法。那么最终订阅者就能接受到之前发送的事件,并执行相应的订阅方法啦。
最后
EventBus 主要的流程到现在已经讲完了。从实际的代码中,我们不仅能看到其良好的代码规范以及封装思想。还能看到该框架对性能的优化,尤其是添加了一些必要的缓存。我相信以上的这些点,都是值得我们借鉴与参考的。在接下来的文章中我们会讲解 EventBus 中的 “加速引擎"
索引类。有兴趣的小伙伴可以继续关注。
你可能感兴趣的:(Android 注解系列之 EventBus3 原理(四))
解锁机器学习核心算法 | 线性回归:机器学习的基石
紫雾凌寒
AI 炼金厂 # 机器学习算法 算法 机器学习 线性回归 人工智能 深度学习 ai python
在机器学习的众多算法中,线性回归宛如一块基石,看似质朴无华,却稳稳支撑起诸多复杂模型的架构。它是我们初涉机器学习领域时便会邂逅的算法之一,其原理与应用广泛渗透于各个领域。无论是预测房价走势、剖析股票市场波动,还是钻研生物医学数据、优化工业生产流程,线性回归皆能大显身手。本质上,线性回归是一种用于构建变量间线性关系的统计模型。它试图寻觅一条最佳拟合直线(或超平面),以使预测值与实际观测值之间的误差降
vue2中组件通信的12种方式
Hopebearer_
Vue2 前端 javascript 开发语言 vue.js 前端框架 js
文章目录vue2组件通信1.props通信(父传子)1.1声明方式(1)简单数组形式(2)对象形式1.2向子组件传递props(1)静态传递(2)动态传递1.3props的类型验证1.4props的单向数据流2.v-model通信(双向绑定)2.1基本原理2.2在自定义组件中使用v-model(1)默认实现(2)自定义prop和事件(3)多个v-model绑定3..sync通信(双向绑定)3.1基
Qt创建模态和非模态对话框
Goallegoal
Qt qt
Qt创建模态和非模态对话框在使用软件时,会遇到各式各样的对话框,弹出的对话框大致可以分为两大类,模态对话框和非模态对话框,怎么对这两种对话框进行区分呢?简单来说,对于模态对话框,在未关闭之前,无法再对同一软件的其他窗口进行操作,即强制性用户交互;而非模态对话框与之相反,未关闭之前,依然可以操作同软件下的其他窗口,即非强制性用户交互。Qt中根据功能需求,可以分别对这两类对话框进行设置与构建。首先来看
HTML超链接 表格 列表 表单
s_kzn
html html5
目录一、HTML超链接1.外部链接2.内部链接3.空链接#4.下载链接5.网页元素的链接6.锚点链接二、表格1.表格标签的属性2.合并单元格三、列表1.无序列表2.有序列表3.自定义列表四、表单1.表单标签2.用户注册表单实例一、HTML超链接超链接可以是一个字,一个词,或者一组词,也可以是一幅图像,可以点击这些内容来跳转到新的文档或者当前文档中的某个部分。超链接标签:一个链接到另一个链接【Anc
华为云专家出品《深入理解边缘计算》电子书上线
华为云PaaS服务小智
华为云 边缘计算 人工智能
华为开发者大会PaaS生态电子书推荐,助你成为了不起的开发者!什么是边缘计算?边缘计算的应用场景有哪些?华为云出品《深入理解边缘计算》电子书上线带你系统理解云、边、端协同的相关原理了解开源项目的源码分析流程学成能够对云、边、端主流开源实现进行定制开发!【适用人群】1.对云原生感兴趣的开发者2.对边缘计算有学习需求或想拓展业务之外开发技能的开发者【精彩导读】首先,介绍边缘计算概念、边缘计算系统具体组
发现声音处理的新大陆:Fish Audio Preprocessor
幸竹任
发现声音处理的新大陆:FishAudioPreprocessoraudio-preprocess项目地址:https://gitcode.com/gh_mirrors/au/audio-preprocess项目介绍在这个数字时代,音频处理成为了多媒体领域不可或缺的一环。引入《FishAudioPreprocessor》,一款专为简化音频预处理任务而设计的开源神器。它集结了一系列核心功能,覆盖从基本
DeepSeek模型微调的原理和方法
alankuo
人工智能
DeepSeek模型微调的原理迁移学习基础DeepSeek模型微调基于迁移学习的思想。预训练模型在大规模通用数据上进行了无监督或有监督的训练,学习到了丰富的语言知识、语义表示和通用模式。这些知识和模式具有一定的通用性,可以迁移到其他相关的任务中。在微调时,我们利用预训练模型已经学到的这些通用知识,针对特定的目标任务进行进一步的调整和优化,使得模型能够更好地适应新任务的需求。微调的参数更新机制在微调
php过滤文字中的表情字符和mysql服务端对emoji的支持
angzhan5306
php 移动开发 数据库
1.过滤emoji表情的原因在我们的项目开发中,emoji表情是个麻烦的东西,即使我们可以能存储,也不一定能完美显示,因为它的更新速度很快:在iOS以外的平台上,例如PC或者android。如果你需要显示emoji,就得准备一大堆emoji图片并使用第三方前端类库才行。即便如此,还是可能因为emoji图片不够全而出现无法显示的情况在大多数业务场景下,emoji也不是非要不可的。我们可以适当地考虑干
史上最全的Unity面试题(含答案)
Sindywangkeai
unity unity 面试题
一.什么是渲染管道?是指在显示器上为了显示出图像而经过的一系列必要操作。渲染管道中的很多步骤,都要将几何物体从一个坐标系中变换到另一个坐标系中去。主要步骤有:本地坐标->视图坐标->背面裁剪->光照->裁剪->投影->视图变换->光栅化。二.如何优化内存?有很多种方式,例如1.压缩自带类库;2.将暂时不用的以后还需要使用的物体隐藏起来而不是直接Destroy掉;3.释放AssetBundle占用的
Unity打包APK报错 using a newer Android Gradle plugin to use compileSdk = 35
[奋斗不止]
unity-android unity Unity apk 报错
Unity打包APK报错usinganewerAndroidGradleplugintousecompileSdk=35三个报错信息如下第一个WARNING:WerecommendusinganewerAndroidGradleplugintousecompileSdk=35ThisAndroidGradleplugin(7.1.2)wastesteduptocompileSdk=32Thiswa
DeepSeek不好用?那是你还没有清华大学指导手册
悉数之淀
pdf
「清华Deepseek最新使用手册教程资源」链接:https://pan.quark.cn/s/c9c795c32bed终于等到清华出了deepseek手册,前几天也看了一些花里胡哨的教程,大多言之无物,就是把之前gpt的手册换了个皮直接套给deepseek。果然清华出手就是不一样,从科普原理,到手把手教你科学使用。而且不光是告诉你怎么问,还会告诉你为什么要这么问,教你提示词的底层逻辑。✅这才是授
Texas Instruments (TI) 系列:TIVA C 系列 (基于 ARM Cortex-M4)_(7).TIVA C系列UART通信
kkchenkx
单片机开发 c语言 arm开发 开发语言 嵌入式硬件 单片机
TIVAC系列UART通信1.UART通信原理UART(UniversalAsynchronousReceiver-Transmitter)是一种常见的串行通信接口,用于在两个设备之间传输数据。TIVAC系列单片机基于ARMCortex-M4内核,提供了多个UART模块,支持全双工通信。UART通信的基本原理如下:1.1异步通信UART通信是一种异步通信方式,这意味着发送方和接收方之间没有共享的时
链表数据结构:从零开始的C++实现完全指南(教学版)
WHCIS
数据结构 数据结构 链表 c++
一、链表的核心原理(理论篇)1.1链表的数学本质链表可以看作是一个递归定义的序列结构:List=Empty|Node(data,List)Empty:空链表(基础情形)Node:包含数据元素和子链表的节点(递归情形)示例推导:List1=Node(5,Empty)List2=Node(3,List1)→Node(3,Node(5,Empty))List3=Node(1,List2)→Node(1,
PHP的数据结构一共有哪些?使用场景是什么?底层原理是什么?
快点好好学习吧
PHP php 数据结构 android
PHP的数据结构是编程中用来存储和组织数据的方式。它们就像不同的“容器”,可以用来装不同类型的东西。1.PHP的常见数据结构(1)数组(Array)定义:数组是一种可以存储多个值的容器。它可以是索引数组(用数字作为键)或关联数组(用字符串作为键)。示例:$fruits=["apple","banana","cherry"];//索引数组$person=["name"=>"Alice","age"=
GIT工作原理-入门必看
lcl_bigdata
git
--初入开发门槛或对git一知半解的你可能感兴趣这次纯纯是知识的搬运工,希望更多想了解git的人能看到这个。尹会生老师讲解的,把git的工作原理讲解的非常形象,如果脑子里有这个图,就会避免好多覆盖他人代码,遗漏拉去他人代码等事件了。不多说,上图,上讲解~有希望了解更多的,请到极客时间中找《零基础学Python(2023版)》尹会生第一章第5节。远程仓库:Remote本地仓库:Repository工
Java单例模式详解
Qzer_407
java # 设计模式 后端技术栈 java 单例模式 开发语言 设计模式
Java单例模式详解一、Java单例模式的概念和原理单例模式(SingletonPattern)是一种常用的软件设计模式,其核心思想是确保一个类仅有一个实例,并提供一个全局访问点来获取该实例。在Java中,单例模式通过私有化构造函数,并提供一个静态方法来返回类的唯一实例来实现。这样做的好处是控制资源访问,减少内存消耗,并确保数据的一致性。二、Java单例模式的实际应用场景和优势实际应用场景:配置管
【Java】单例模式
非 白
java 单例模式 笔记
单例模式所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。单例模式包含懒汉式和饿汉式,运行有且仅有一个实例化对象,只会new一次,两者区别在于何时new一个对象原理:如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造方法的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对
离子阱量子计算机的原理与应用:开辟量子计算的新天地
Echo_Wish
人工智能 前沿技术 量子计算 空间计算
离子阱量子计算机的原理与应用:开辟量子计算的新天地大家好,我是Echo_Wish,今天我们来聊聊一个正在颠覆传统计算机架构的前沿话题——离子阱量子计算机。随着量子计算的研究不断取得进展,它的潜力正在吸引越来越多的关注。而在众多量子计算技术中,离子阱量子计算因其优异的性能和较长的量子比特(qubit)寿命,逐渐成为学界和业界的研究热点。那么,什么是离子阱量子计算机?它的原理是什么?有哪些实际应用?本
Java基础:策略模式与Spring 源码中策略模式的应用
生活诙谐号
Java基础 设计模式 Spring源码 java 策略模式 spring
策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。这种模式提供了用一系列可互换的算法或策略来封装算法族,并使它们可以相互替换。策略模式鼓励使用对象组合而不是继承来实现具有多种行为的系统。以下是策略模式的组成部分以及一个详细的Java示例:组成部分:策略接口(Strategy):定义所有支持的算法的公共接口。通常是一个接口或抽象类,它规定了所有具体策略类需要实现的方法。publicinte
cocos creator 项目总结二(战斗帧同步解析)
xzben
cocos creator 帧同步
一、帧同步原理:帧同步,指的是将游戏过程中关键的操作帧数据同步给各个客户端实现游戏同步的方案。这个原理看上去一句话很简单,但是其实内部涉及的细节却很多,影响到游戏的卡顿,同步是否完全同步等问题,接下来我一一列举我制作过程中遇到的问题和解决方案。二、客户端同步一致性问题1、逻辑驱动归一管理,这个主要是要将战斗过程的所有逻辑运算update驱动要统一管理,而不是简单的通过UI层的update分散驱动,
在线教程丨 3 款声音克隆模型真实测评,GPT-SoVITS 精准拿捏「石矶娘娘」特点
hyperai
春节档电影「哪吒2」的票房一路高歌猛进,现已突破120亿,成为中国首部票房达到百亿大关的影片,已成功跻身全球影史票房榜前10。影片中,配音演员们用灵动的声音赋予了角色鲜活的生命力,从哪吒的「烟嗓」到太乙真人的四川方言,再到石矶娘娘的灵动,引发了大众的广泛讨论,让幕后配音艺术走到台前。提及配音艺术的魅力,「王者荣耀」芈月的白晶晶皮肤堪称绝佳例证。官方特邀「大话西游」电影中的白晶晶原配音演员王蕙君再度
忘掉 DeepSeek:Qwen 2.5 VL 与 Qwen Max 来了
ReactHook深入浅出CSS技巧与案例详解vue2与vue3技巧合集VueUse源码解读Qwen系列又推出了两款令人瞩目的新模型:Qwen2.5VL和Qwen2.5Max。如果还停留在“DeepSeek是最强”那一档,就可能要重新考虑一下,因为这两款模型的实力相当惊艳。什么是Qwen2.5VL?先从Qwen2.5VL说起。它不仅能看图识物,还能理解视频、文本,具备执行电脑操作(agentic)
网络通信(待补充)
四无青年203
golang
网络通信互联网中主机和主机连接必须遵守特定的要求,这个要求成为协议osi开放式系统互联,定义了计算机互联时网络通信的7层目前大规模使用的是tcp/ip协议:应用层:合并osi中567层(绘画,表示,应用)常用协议:http,ftp,smtp,pop3,ssl,rpc传输层:osi中第四层常用协议:tcp,udp网络层:osi中第三层常用协议:ip,ipv4,ipv6网络接口层:osi中第1,2层i
std::thread的同步机制
li星野
C++ c++ 学习 开发语言
在C++中,std::thread用于创建和管理线程。为了确保多个线程能正确、安全地访问共享资源,避免数据竞争和不一致问题,需要使用同步机制。互斥锁(std::mutex)原理:互斥锁是一种最基本的同步原语,用于保护共享资源。同一时间只允许一个线程访问被互斥锁保护的代码段,其他线程必须等待该线程释放锁后才能继续访问。#include#include#includestd::mutexmtx;int
mysql之事务深度解析与实战应用:保障数据一致性的基石
我爱松子鱼
mysql运行机制 mysql 数据库
文章目录MySQL事务深度解析与实战应用:保障数据一致性的基石一、事务核心概念与原理1.1事务的本质与意义1.2事务的ACID特性1.2.1原子性(Atomicity)1.2.2一致性(Consistency)1.2.3隔离性(Isolation)1.2.4持久性(Durability)1.3事务隔离级别与并发问题1.4MVCC(多版本并发控制)详解1.4.1核心组件1.4.2ReadView可见
使用toFixed四舍五入?
aqiu~
cocos creator cocos
有一个显示金额的地方需要对大数字进行kbmt转换即1249.63→1.24K使用了已有方法,但发现结果和预想不同(1249.63/1000).toFixed(2)=1.25查了下toFixed并不是严格的仅保留小数,也不是严格的四舍五入(这篇)解决方法:(Math.floor((1249.63/1000)*Math.pow(10,2))/Math.pow(10,2)).toString()=1.2
为什么使用虚拟DOM?
祈澈菇凉
前端面试题合集 1024程序员节
使用虚拟DOM(VirtualDOM)是为了提高前端应用的性能和开发效率,它的主要原理是在内存中构建一个轻量级的DOM树,通过对比虚拟DOM的变化,最小化真实DOM的操作。以下是使用虚拟DOM的几个主要优势:1:性能优化:虚拟DOM通过批量更新和最小化DOM操作,减少了对真实DOM的直接操作次数,从而提高了性能。2:跨平台能力:虚拟DOM是与平台无关的抽象层,可以在不同的平台上运行,包括浏览器、移
Java本地应用 使用spring 注解初始化
LiTianao88
Java Spring spring 本地应用 class
service类及接口ISample.Java[java]viewplaincopyprint?/****/packagetest;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;/***@Cla
分布式之Raft算法
点滴~
分布式
参考:分布式算法-Raft算法|Java全栈知识体系Raft算法详解|JavaGuide分布式|CS-Notes面试笔记
分布式之Gossip协议
点滴~
分布式
目录Gossip协议Redis如何通过Gossip协议进行通信的?Gossip协议参考:Gossip协议详解|JavaGuideRedis进阶-高可拓展:分片技术(RedisCluster)详解|Java全栈知识体系Redis如何通过Gossip协议进行通信的?在RedisCluster中使用Gossip协议来实现节点之间的通信
Enum用法
不懂事的小屁孩
enum
以前的时候知道enum,但是真心不怎么用,在实际开发中,经常会用到以下代码:
protected final static String XJ = "XJ";
protected final static String YHK = "YHK";
protected final static String PQ = "PQ";
【Spark九十七】RDD API之aggregateByKey
bit1129
spark
1. aggregateByKey的运行机制
/**
* Aggregate the values of each key, using given combine functions and a neutral "zero value".
* This function can return a different result type
hive创建表是报错: Specified key was too long; max key length is 767 bytes
daizj
hive
今天在hive客户端创建表时报错,具体操作如下
hive> create table test2(id string);
FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask. MetaException(message:javax.jdo.JDODataSto
Map 与 JavaBean之间的转换
周凡杨
java 自省 转换 反射
最近项目里需要一个工具类,它的功能是传入一个Map后可以返回一个JavaBean对象。很喜欢写这样的Java服务,首先我想到的是要通过Java 的反射去实现匿名类的方法调用,这样才可以把Map里的值set 到JavaBean里。其实这里用Java的自省会更方便,下面两个方法就是一个通过反射,一个通过自省来实现本功能。
1:JavaBean类
1 &nb
java连接ftp下载
g21121
java
有的时候需要用到java连接ftp服务器下载,上传一些操作,下面写了一个小例子。
/** ftp服务器地址 */
private String ftpHost;
/** ftp服务器用户名 */
private String ftpName;
/** ftp服务器密码 */
private String ftpPass;
/** ftp根目录 */
private String f
web报表工具FineReport使用中遇到的常见报错及解决办法(二)
老A不折腾
finereport web报表 java报表 总结
抛砖引玉,希望大家能把自己整理的问题及解决方法晾出来,Mark一下,利人利己。
出现问题先搜一下文档上有没有,再看看度娘有没有,再看看论坛有没有。有报错要看日志。下面简单罗列下常见的问题,大多文档上都有提到的。
1、没有返回数据集:
在存储过程中的操作语句之前加上set nocount on 或者在数据集exec调用存储过程的前面加上这句。当S
linux 系统cpu 内存等信息查看
墙头上一根草
cpu 内存 liunx
1 查看CPU
1.1 查看CPU个数
# cat /proc/cpuinfo | grep "physical id" | uniq | wc -l
2
**uniq命令:删除重复行;wc –l命令:统计行数**
1.2 查看CPU核数
# cat /proc/cpuinfo | grep "cpu cores" | u
Spring中的AOP
aijuans
spring AOP
Spring中的AOP
Written by Tony Jiang @ 2012-1-18 (转)何为AOP
AOP,面向切面编程。
在不改动代码的前提下,灵活的在现有代码的执行顺序前后,添加进新规机能。
来一个简单的Sample:
目标类:
[java]
view plain
copy
print
?
package&nb
placeholder(HTML 5) IE 兼容插件
alxw4616
JavaScript jquery jQuery插件
placeholder 这个属性被越来越频繁的使用.
但为做HTML 5 特性IE没能实现这东西.
以下的jQuery插件就是用来在IE上实现该属性的.
/**
* [placeholder(HTML 5) IE 实现.IE9以下通过测试.]
* v 1.0 by oTwo 2014年7月31日 11:45:29
*/
$.fn.placeholder = function
Object类,值域,泛型等总结(适合有基础的人看)
百合不是茶
泛型的继承和通配符 变量的值域 Object类转换
java的作用域在编程的时候经常会遇到,而我经常会搞不清楚这个
问题,所以在家的这几天回忆一下过去不知道的每个小知识点
变量的值域;
package 基础;
/**
* 作用域的范围
*
* @author Administrator
*
*/
public class zuoyongyu {
public static vo
JDK1.5 Condition接口
bijian1013
java thread Condition java多线程
Condition 将 Object 监视器方法(wait、notify和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
条件(也称为条件队列或条件变量)为线程提供了一
开源中国OSC源创会记录
bijian1013
hadoop spark MemSQL
一.Strata+Hadoop World(SHW)大会
是全世界最大的大数据大会之一。SHW大会为各种技术提供了深度交流的机会,还会看到最领先的大数据技术、最广泛的应用场景、最有趣的用例教学以及最全面的大数据行业和趋势探讨。
二.Hadoop
&nbs
【Java范型七】范型消除
bit1129
java
范型是Java1.5引入的语言特性,它是编译时的一个语法现象,也就是说,对于一个类,不管是范型类还是非范型类,编译得到的字节码是一样的,差别仅在于通过范型这种语法来进行编译时的类型检查,在运行时是没有范型或者类型参数这个说法的。
范型跟反射刚好相反,反射是一种运行时行为,所以编译时不能访问的变量或者方法(比如private),在运行时通过反射是可以访问的,也就是说,可见性也是一种编译时的行为,在
【Spark九十四】spark-sql工具的使用
bit1129
spark
spark-sql是Spark bin目录下的一个可执行脚本,它的目的是通过这个脚本执行Hive的命令,即原来通过
hive>输入的指令可以通过spark-sql>输入的指令来完成。
spark-sql可以使用内置的Hive metadata-store,也可以使用已经独立安装的Hive的metadata store
关于Hive build into Spark
js做的各种倒计时
ronin47
js 倒计时
第一种:精确到秒的javascript倒计时代码
HTML代码:
<form name="form1">
<div align="center" align="middle"
java-37.有n 个长为m+1 的字符串,如果某个字符串的最后m 个字符与某个字符串的前m 个字符匹配,则两个字符串可以联接
bylijinnan
java
public class MaxCatenate {
/*
* Q.37 有n 个长为m+1 的字符串,如果某个字符串的最后m 个字符与某个字符串的前m 个字符匹配,则两个字符串可以联接,
* 问这n 个字符串最多可以连成一个多长的字符串,如果出现循环,则返回错误。
*/
public static void main(String[] args){
mongoDB安装
开窍的石头
mongodb安装 基本操作
mongoDB的安装
1:mongoDB下载 https://www.mongodb.org/downloads
2:下载mongoDB下载后解压
[开源项目]引擎的关键意义
comsci
开源项目
一个系统,最核心的东西就是引擎。。。。。
而要设计和制造出引擎,最关键的是要坚持。。。。。。
现在最先进的引擎技术,也是从莱特兄弟那里出现的,但是中间一直没有断过研发的
软件度量的一些方法
cuiyadll
方法
软件度量的一些方法http://cuiyingfeng.blog.51cto.com/43841/6775/在前面我们已介绍了组成软件度量的几个方面。在这里我们将先给出关于这几个方面的一个纲要介绍。在后面我们还会作进一步具体的阐述。当我们不从高层次的概念级来看软件度量及其目标的时候,我们很容易把这些活动看成是不同而且毫不相干的。我们现在希望表明他们是怎样恰如其分地嵌入我们的框架的。也就是我们度量的
XSD中的targetNameSpace解释
darrenzhu
xml namespace xsd targetnamespace
参考链接:
http://blog.csdn.net/colin1014/article/details/357694
xsd文件中定义了一个targetNameSpace后,其内部定义的元素,属性,类型等都属于该targetNameSpace,其自身或外部xsd文件使用这些元素,属性等都必须从定义的targetNameSpace中找:
例如:以下xsd文件,就出现了该错误,即便是在一
什么是RAID0、RAID1、RAID0+1、RAID5,等磁盘阵列模式?
dcj3sjt126com
raid
RAID 1又称为Mirror或Mirroring,它的宗旨是最大限度的保证用户数据的可用性和可修复性。 RAID 1的操作方式是把用户写入硬盘的数据百分之百地自动复制到另外一个硬盘上。由于对存储的数据进行百分之百的备份,在所有RAID级别中,RAID 1提供最高的数据安全保障。同样,由于数据的百分之百备份,备份数据占了总存储空间的一半,因而,Mirror的磁盘空间利用率低,存储成本高。
Mir
yii2 restful web服务快速入门
dcj3sjt126com
PHP yii2
快速入门
Yii 提供了一整套用来简化实现 RESTful 风格的 Web Service 服务的 API。 特别是,Yii 支持以下关于 RESTful 风格的 API:
支持 Active Record 类的通用API的快速原型
涉及的响应格式(在默认情况下支持 JSON 和 XML)
支持可选输出字段的定制对象序列化
适当的格式的数据采集和验证错误
MongoDB查询(3)——内嵌文档查询(七)
eksliang
MongoDB查询内嵌文档 MongoDB查询内嵌数组
MongoDB查询内嵌文档
转载请出自出处:http://eksliang.iteye.com/blog/2177301 一、概述
有两种方法可以查询内嵌文档:查询整个文档;针对键值对进行查询。这两种方式是不同的,下面我通过例子进行分别说明。
二、查询整个文档
例如:有如下文档
db.emp.insert({
&qu
android4.4从系统图库无法加载图片的问题
gundumw100
android
典型的使用场景就是要设置一个头像,头像需要从系统图库或者拍照获得,在android4.4之前,我用的代码没问题,但是今天使用android4.4的时候突然发现不灵了。baidu了一圈,终于解决了。
下面是解决方案:
private String[] items = new String[] { "图库","拍照" };
/* 头像名称 */
网页特效大全 jQuery等
ini
JavaScript jquery css html5 ini
HTML5和CSS3知识和特效
asp.net ajax jquery实例
分享一个下雪的特效
jQuery倾斜的动画导航菜单
选美大赛示例 你会选谁
jQuery实现HTML5时钟
功能强大的滚动播放插件JQ-Slide
万圣节快乐!!!
向上弹出菜单jQuery插件
htm5视差动画
jquery将列表倒转顺序
推荐一个jQuery分页插件
jquery animate
swift objc_setAssociatedObject block(version1.2 xcode6.4)
啸笑天
version
import UIKit
class LSObjectWrapper: NSObject {
let value: ((barButton: UIButton?) -> Void)?
init(value: (barButton: UIButton?) -> Void) {
self.value = value
Aegis 默认的 Xfire 绑定方式,将 XML 映射为 POJO
MagicMa_007
java POJO xml Aegis xfire
Aegis 是一个默认的 Xfire 绑定方式,它将 XML 映射为 POJO, 支持代码先行的开发.你开发服 务类与 POJO,它为你生成 XML schema/wsdl
XML 和 注解映射概览
默认情况下,你的 POJO 类被是基于他们的名字与命名空间被序列化。如果
js get max value in (json) Array
qiaolevip
每天进步一点点 学习永无止境 max 纵观千象
// Max value in Array
var arr = [1,2,3,5,3,2];Math.max.apply(null, arr); // 5
// Max value in Jaon Array
var arr = [{"x":"8/11/2009","y":0.026572007},{"x"
XMLhttpRequest 请求 XML,JSON ,POJO 数据
Luob.
POJO json Ajax xml XMLhttpREquest
在使用XMlhttpRequest对象发送请求和响应之前,必须首先使用javaScript对象创建一个XMLHttpRquest对象。
var xmlhttp;
function getXMLHttpRequest(){
if(window.ActiveXObject){
xmlhttp:new ActiveXObject("Microsoft.XMLHTTP
jquery
wuai
jquery
以下防止文档在完全加载之前运行Jquery代码,否则会出现试图隐藏一个不存在的元素、获得未完全加载的图像的大小 等等
$(document).ready(function(){
jquery代码;
});
<script type="text/javascript" src="c:/scripts/jquery-1.4.2.min.js&quo