上篇,我们学习了EventBus3的使用方法,本篇一起来分析一下EventBus3的主要源码。
我们主要分析以下重点即可,其他的都是围绕这几个重点做补充:
EventBus实例创建
register注册流程
post事件发送流程
unregister解注册流程
EventBus实例创建
EventBus实例的创建方式有2种
EventBus.getDefault(),获取默认配置的实例。使用的是很常见的Double Check和volatile保证单例。
static volatile EventBus defaultInstance;
/**
* Double Check 方式单例
*/
public static EventBus getDefault() {
EventBus instance = defaultInstance;
if (instance == null) {
synchronized (EventBus.class) {
instance = EventBus.defaultInstance;
if (instance == null) {
instance = EventBus.defaultInstance = new EventBus();
}
}
}
return instance;
}
自定义配置,使用的是Builder建造者模式,可对EventBus的一些配置进行自定义,例如配置子线程回调订阅者方法的线程池执行器等。最后build()方法可按配置成EventBus实例,后续使用需要自己保存好实例,如果调用getDefault()获取的还是默认实例。如果希望让EventBus保存默认实例则可调用installDefaultEventBus()来保存实例为EventBus的默认实例。
/**
* 自定义配置EventBus实例
*/
public static EventBusBuilder builder() {
return new EventBusBuilder();
}
/**
* EventBus实例自定义Builder配置
*/
public class EventBusBuilder {
private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
boolean logSubscriberExceptions = true;
boolean logNoSubscriberMessages = true;
boolean sendSubscriberExceptionEvent = true;
boolean sendNoSubscriberEvent = true;
boolean throwSubscriberException;
boolean eventInheritance = true;
boolean ignoreGeneratedIndex;
boolean strictMethodVerification;
ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
List> skipMethodVerificationForClasses;
List subscriberInfoIndexes;
Logger logger;
MainThreadSupport mainThreadSupport;
EventBusBuilder() {
}
//省略其他配置的set方法...
/**
* 配置子线程执行的线程池执行器
*/
public EventBusBuilder executorService(ExecutorService executorService) {
this.executorService = executorService;
return this;
}
//省略其他配置的set方法...
/**
* 将生成的实例安装到默认实例,后续则可以使用getDefault()获取回这个实例,注意只能安装一次
* 因此installDefaultEventBus()之前也不能调用getDefault()获取过实例,否则抛出异常
*/
public EventBus installDefaultEventBus() {
synchronized (EventBus.class) {
if (EventBus.defaultInstance != null) {
throw new EventBusException("Default instance already exists." +
" It may be only set once before it's used the first time to ensure consistent behavior.");
}
EventBus.defaultInstance = build();
return EventBus.defaultInstance;
}
}
/**
* 按Builder配置生成EventBus实例
*/
public EventBus build() {
return new EventBus(this);
}
}
订阅者注册
订阅者的注册,通过调用register()方法,传入订阅者实例即可,一般我们为Activity、Fragment中使用。接下来分析注册流程。
获取订阅者的Class,通过subscriberMethodFinder.findSubscriberMethods()查找订阅者的所有订阅方法。
遍历每个订阅方法,为每个订阅方法进行注册。
/**
* 订阅事件
*/
public void register(Object subscriber) {
Class subscriberClass = subscriber.getClass();
//查找订阅者所有的订阅方法
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
//遍历订阅的方法列表,对每个订阅方法都进行注册
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
来分析一下subscriberMethodFinder.findSubscriberMethods()。SubscriberMethodFinder类是一个专门查找订阅者方法的一个类。
反射查找订阅方法是一个很耗时的操作,所以EventBus也做了缓存METHOD_CACHE,搜索前先从缓存中找,找得到则马上使用。
接下来是2个分支,一个是apt编译器生成订阅信息索引的方法,一种是反射获取订阅者的订阅方法,ignoreGeneratedIndex标志位标识是否忽略apt生成的订阅信息索引,默认为false,所以会走else逻辑。
findUsingInfo()方法,则是反射获取订阅者的订阅信息。
如果找不到订阅信息,证明订阅者没有声明订阅方法,抛出异常。找得到则将订阅信息缓存到METHOD_CACHE,然后再返回。
//订阅方法缓存
private static final Map, List> METHOD_CACHE = new ConcurrentHashMap<>();
/**
* 查找订阅者的订阅方法,将订阅信息封装在SubscriberMethod类中,多个方法,所以返回值为一个List集合
*
* @param subscriberClass 订阅者Class
*/
List findSubscriberMethods(Class subscriberClass) {
//先从缓存中查找,有则直接使用
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//是否忽略apt生成的索引,ignoreGeneratedIndex默认为false
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
//反射获取所有订阅的方法
subscriberMethods = 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;
}
}
findSubscriberMethods()的主要逻辑都在findUsingInfo()上,继续追踪
第一步,调用prepareFindState()方法,获取当前线程的查找状态并初始化
第二步,判断findState.subscriberInfo字段是否为空,为空调用findUsingReflectionInSingleClass(),这个方法才是反射获取订阅者的订阅信息
第三步,配置父类Class信息,会自动忽略系统类来提高性能
最后,getMethodsAndRelease(),转移findState上保存的List,并对FindState中间对象回收
/**
* 反射查找订阅者所有的订阅方法
*
* @param subscriberClass 订阅者Class
*/
private List findUsingInfo(Class subscriberClass) {
//获取当前线程的查找状态
FindState findState = prepareFindState();
//初始化一些值
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
//获取订阅信息
findState.subscriberInfo = getSubscriberInfo(findState);
//初始化时,findState.subscriberInfo为null
if (findState.subscriberInfo != null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
//反射获取订阅者的所有订阅方法
findUsingReflectionInSingleClass(findState);
}
//配置父类Class信息,会自动忽略系统类来提高性能
findState.moveToSuperclass();
}
//转移findState上保存的List,并对FindState中间对象回收
return getMethodsAndRelease(findState);
}
分析prepareFindState()方法
很明显,FindState是复用对象的,作用就是避免频繁创建FindState类。
先从缓存池中查找FindState可用的对象,可用则返回。
不可用,则新建一个FindState类实例。
//对象池大小
private static final int POOL_SIZE = 4;
//FindState对象池
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
/**
* 获取一个查找状态
*/
private FindState prepareFindState() {
synchronized (FIND_STATE_POOL) {
//享元模式,从对象池中找,避免频繁创建FindState类
for (int i = 0; i < POOL_SIZE; i++) {
FindState state = FIND_STATE_POOL[i];
if (state != null) {
FIND_STATE_POOL[i] = null;
return state;
}
}
}
//没有缓存对象可用,则创建一个
return new FindState();
}
findState.initForSubscriber(subscriberClass),则是初始化findState的初始化一些值
/**
* 查找状态实体类
*/
static class FindState {
Class subscriberClass;
boolean skipSuperClasses;
SubscriberInfo subscriberInfo;
//初始化findState的初始化一些值
void initForSubscriber(Class subscriberClass) {
this.subscriberClass = clazz = subscriberClass;
skipSuperClasses = false;
subscriberInfo = null;
}
}
findState经过initForSubscriber()后,subscriberInfo字段被置为null,所以只能走else逻辑,继续分析findUsingReflectionInSingleClass()
findUsingReflectionInSingleClass()方法,主要是使用反射来获取订阅者的Subscribe注解的方法,然后将方法的Method对象(有这个对象就能调用invoked调用订阅者方法),事件Class,配置的threadMode,priorty优先级,是否粘性sticky,封装到SubscriberMethod对象中,最后所有的订阅方法都在SubscriberMethod对象都收集到了findState.subscriberMethods集合中。这样订阅者的订阅信息都收集到了。
/**
* 反射获取订阅者的所有订阅方法
*
* @param findState 查找状态
*/
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
//使用getDeclaredMethods,反射获取所有方法,不会获取到父类中的方法,避免查找耗时,尤其是Activity,一般我们都是在子类上使用
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
//遍历订阅者的方法
for (Method method : methods) {
//获取修饰符
int modifiers = method.getModifiers();
//判断方法的修饰符是否为Public公开,并且不是static静态方法,
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
//获取方法参数
Class[] parameterTypes = method.getParameterTypes();
//限定订阅方法的方法参数为1个,就是event事件类
if (parameterTypes.length == 1) {
//判断方法是否加了@Subscribe注解,必须加了才处理
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
//获取第一个参数,就是事件event
Class eventType = parameterTypes[0];
//检查是否已经添加过了,没有添加过才继续
if (findState.checkAdd(method, eventType)) {
//获取@Subscribe注解上的threadMode参数,就是事件回调的线程策略
ThreadMode threadMode = subscribeAnnotation.threadMode();
//创建SubscriberMethod对象,代表每个订阅方法的信息
//将@Subscribe注解上定义的事件类型、线程回调策略、回调优先级、是否粘性等字段保存到SubscriberMethod类中
//再将SubscriberMethod对象保存到findState.subscriberMethods订阅的方法列表中
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");
}
}
}
回到SubscriberMethodFinder的findUsingInfo()方法,调用完findUsingReflectionInSingleClass()收集订阅者订阅信息后,再调用了moveToSuperclass()方法,其实就是配置父类Class信息,会自动忽略系统类来提高性能。
/**
* 配置父类Class信息,会自动忽略系统类来提高性能
*/
void moveToSuperclass() {
if (skipSuperClasses) {
clazz = null;
} else {
clazz = clazz.getSuperclass();
String clazzName = clazz.getName();
//跳过系统的类(肯定不会有EventBus的东西),来提高性能
if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {
clazz = null;
}
}
}
调用getMethodsAndRelease(),将findState中的subscriberMethods订阅信息集合转到一个ArrayList中,调用findState的recycle()重置对象字段,再将实例放到FIND_STATE_POOL对象池中复用。
/**
* 将FindState上保存的订阅方法保存到一个List集合,并将FindState回收,将FindState类放到对象池中复用
*
* @param findState 保存了订阅信息的中间类
*/
private List getMethodsAndRelease(FindState findState) {
//将订阅的方法信息保存到一个List集合
List subscriberMethods = new ArrayList<>(findState.subscriberMethods);
//回收,就是重置字段
findState.recycle();
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
//保存到对象池中复用
if (FIND_STATE_POOL[i] == null) {
FIND_STATE_POOL[i] = findState;
break;
}
}
}
//返回这个List集合
return subscriberMethods;
}
/**
* 查找状态实体类
*/
static class FindState {
//...忽略其他字段和方法
/**
* 回收操作,重置字段
*/
void recycle() {
subscriberMethods.clear();
anyMethodByEventType.clear();
subscriberClassByMethodKey.clear();
methodKeyBuilder.setLength(0);
subscriberClass = null;
clazz = null;
skipSuperClasses = false;
subscriberInfo = null;
}
}
调用findSubscriberMethods()方法获取到订阅者的订阅信息列表后,我们回到register()方法,将订阅信息列表遍历,调用subscribe()方法,传入订阅对象和订阅信息。
subscribe()方法的作用是:将订阅信息和订阅关系分拆到3个Map中,然后订阅者优先级排序,最后如果有粘性方法订阅,查找粘性事件的Map,回调订阅者方法。
先来了解下这3个Map是什么(很重要,后续事件回调都需要用到它们):
subscriptionsByEventType,事件类型和订阅它的订阅者的订阅信息(包含订阅者对象和订阅方法),一对多关系,一个事件类型,对应多个订阅者信息。
typesBySubscriber,订阅者和它所订阅的事件类型映射,一对多关系,一个订阅者对应多个事件
stickyEvents,粘性事件映射表,一对一关系,一个事件对应一个最近发送的事件对象
/**
* 事件类型和订阅它的订阅者的订阅信息(包含订阅者对象和订阅方法),一对多关系,一个事件类型,对应多个订阅者信息
*/
private final Map, CopyOnWriteArrayList> subscriptionsByEventType;
/**
* 订阅者和它所订阅的事件类型映射,一对多关系,一个订阅者对应多个事件
*/
private final Map>> typesBySubscriber;
/**
* 粘性事件映射表,一对一关系,一个事件对应一个最近发送的事件对象
*/
private final Map, Object> stickyEvents;
/**
* 将订阅者和订阅方法执行绑定
*
* @param subscriber 订阅者
* @param subscriberMethod 订阅方法信息对象
*/
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//获取事件类型
Class eventType = subscriberMethod.eventType;
//新建Subscription订阅信息类
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//从subscriptionsByEventType中查找订阅信息列表
CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
//没有,则创建一个,并放到subscriptionsByEventType中
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//已经调用了register方法订阅过了,不能重复订阅
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//优先级排序,优先级越高,越在List列表中靠前
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
//从typesBySubscriber中用订阅者获取它订阅的事件类型列表
List> subscribedEvents = typesBySubscriber.get(subscriber);
//还没有注册过,所以时间类型列表subscribedEvents为空,为空则创建一个
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
//创建完毕,再保存到Map
typesBySubscriber.put(subscriber, subscribedEvents);
}
//将事件类型添加到事件类型列表中
subscribedEvents.add(eventType);
//判断订阅方法是否是粘性的
if (subscriberMethod.sticky) {
//判断是否需要发送子类事件时也发送父类事件,默认为true,如果事件POJO不会继承,建议设置为false来提高性能
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List).
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 {
//粘性事件意思是订阅时,如果之前有发送过粘性事件则马上回调订阅方法
//从粘性事件列表以事件类型中获取粘性事件POJO实例(所有粘性事件都会保存最近一份到内存)
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
判断到存在粘性订阅方法时,调用checkPostStickyEventToSubscription()方法,检查粘性事件是否存在,存在则调用postToSubscription发送事件,回调订阅者的订阅方法。postToSubscription()方法我们在下面post()事件时分析。
/**
* 检查粘性事件对象,以及将粘性事件发送到订阅者
*
* @param newSubscription 订阅信息
* @param stickyEvent 粘性事件
*/
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
//没有发送过这个类型的粘性事件,那么就不做任何操作
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, isMainThread());
}
}
register流程我们分析完了,工作原理主要是使用反射获取订阅者使用Subscribe注册标记的订阅方法,里面的耗时操作都加上了享元模式-对象池缓存或者是Map缓存,避免重复查找。
获取完订阅者的订阅信息列表后,将订阅信息和订阅关系分拆到很关键的3个Map中,最后判断是否有粘性事件订阅方法,再判断粘性事件是否存在,存在则发送事件,回调订阅者的订阅方法。
发送事件
发送事件使用post()方法,发送粘性事件使用,下面来一起分析一下吧。
/**
* 发送事件
*/
public void post(Object event) {
//获取当前线程的发送状态
PostingThreadState postingState = currentPostingThreadState.get();
//获取发送状态的队列
List eventQueue = postingState.eventQueue;
//事件入队
eventQueue.add(event);
//如果没有在发送,则马上发送
if (!postingState.isPosting) {
//对postingState做一些配置
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
//队列不为空,则发送事件
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
第一步,通过currentPostingThreadState.get(),获取当前线程的发送状态。currentPostingThreadState是一个ThreadLocal对象,所以每个线程都有一个PostingThreadState对象。
将事件放入postingState的eventQueue字段,eventQueue是事件队列。
判断postingState.isPosting字段,如果没有在发送,则马上发送。
通过一个while循环,调用postSingleEvent(),发送队列中的事件。
发送接触后,finally块再将必要字段重置。
/**
* ThreadLocal类型,保存当前线程的发送状态类,每个线程都有一份PostingThreadState
*/
private final ThreadLocal currentPostingThreadState = new ThreadLocal() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
/**
* 发送线程的状态
*/
final static class PostingThreadState {
/**
* 事件队列
*/
final List eventQueue = new ArrayList<>();
/**
* 是否正在发送中
*/
boolean isPosting;
/**
* 是否主线程
*/
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}
postSingleEvent()方法,发送单个事件。
判断eventInheritance标志位,代表是否发送子类事件时也发送父类事件,然后调用postSingleEventForEventType()方法发送事件。
postSingleEventForEventType()方法返回一个Boolean值,代表这个事件是否有订阅者订阅,如果没有,则发送一个NoSubscriberEvent事件。
/**
* 发送一个事件
*
* @param event 事件对象
* @param postingState 发送状态
*/
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class eventClass = event.getClass();
boolean subscriptionFound = false;
//判断是否发送子类事件时也发送父类事件
if (eventInheritance) {
List> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class clazz = eventTypes.get(h);
//发送事件
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
//发送事件
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
//处理没有订阅者的情况
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
//没有订阅者,发送一个NoSubscriberEvent事件
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
分析postSingleEventForEventType()
subscriptionsByEventType:事件类型和订阅它的订阅者的订阅信息(包含订阅者对象和订阅方法),一对多关系,一个事件类型,对应多个订阅者信息
从subscriptionsByEventType映射Map中,使用事件类型获取所有的订阅者信息,并赋值到subscriptions集合
判断subscriptions集合是否为空,为空则代表没有订阅者,否则则有订阅者
有订阅者,遍历subscriptions集合,调用postToSubscription()方法,为每个订阅者发送事件
/**
* 按事件类型,发送单个事件
*
* @param event 事件对象
* @param postingState 发送状态
* @param eventClass 事件类型Class
* @return 是否有订阅者订阅者这个事件
*/
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class eventClass) {
CopyOnWriteArrayList subscriptions;
synchronized (this) {
//回查subscriptionsByEventType,获取要发送的这个事件的所有订阅者信息
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;
}
看来postToSubscription是最终的发送方法,继续分析
模式简介:
POSTING模式,直接在当前线程反射调用订阅者的订阅方法
MAIN模式,保证在主线程回调订阅者的订阅方法
MAIN_ORDERED模式,保证主线程中回调订阅者的订阅方法,但是每次都是用Handler去post一个消息
BACKGROUND模式,保证在子线程回调,如果当前已经在子线程,直接调用订阅者的订阅方法
ASYNC模式,无论在哪个线程都让asyncPoster执行任务,所以就算已经在线程中了,也新开一个线程执行
/**
* 发送事件到订阅者
*
* @param subscription 订阅信息
* @param event 事件类型
* @param isMainThread 当时是否在主线程
*/
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
//分类事件回调线程模式
switch (subscription.subscriberMethod.threadMode) {
//POSTING模式,直接在当前线程反射调用订阅者的订阅方法
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
//MAIN模式,保证在主线程回调订阅者的订阅方法
//判断当前是否为主线程,直接调用即可
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
//当前不在主线程,将任务交给mainThreadPoster主线程发送器使用Handler发送
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
//MAIN_ORDERED模式,保证主线程中回调订阅者的订阅方法,但是每次都是用Handler去post一个消息
//所以即使当前已经是主线程了,也依然post一个消息给Handler排队执行
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
//不在安卓上使用EventBus,直接调用订阅者订阅方法
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
//BACKGROUND模式,保证在子线程回调,如果当前已经在子线程,直接调用订阅者的订阅方法
if (isMainThread) {
//不在子线程,将任务交给backgroundPoster
backgroundPoster.enqueue(subscription, event);
} else {
//已经在子线程了,直接调用
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
//ASYNC模式,无论在哪个线程都让asyncPoster执行任务,所以就算已经在线程中了,也新开一个线程执行
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
/**
* 发射调用订阅者的订阅方法
*
* @param subscription 订阅信息
* @param event 事件对象
*/
void invokeSubscriber(Subscription subscription, Object event) {
try {
//拿到订阅信息的method对象,invoke()反射调用
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
接下来,我们分析每种Poster:
Poster接口,多种Poster,其实是采用了策略模式,抽取了接口Poster,不同策略的实现自己的enqueue()方法即可。
/**
* 事件发送器
*/
interface Poster {
/**
* 入队一个事件发送任务
*
* @param subscription 订阅信息
* @param event 事件对象
*/
void enqueue(Subscription subscription, Object event);
}
PendingPost消息,相当于Handler的Messsage对象,PendingPost类则是将每次要发送的事件和下一个事件用next字段保存,内部有一个static的pendingPostPool,其实是PendingPost的对象池,obtainPendingPost()从对象池中获取一个消息,releasePendingPost()则是将消息放回对象池中。
/**
* 封装等待发送事件Api的类,相当于一个消息,类似Handler的Message对象,内部有对象池和事件
*/
final class PendingPost {
/**
* 队列
*/
private final static List pendingPostPool = new ArrayList();
/**
* 事件
*/
Object event;
/**
* 订阅者信息
*/
Subscription subscription;
/**
* 下一个要发送的信息
*/
PendingPost next;
private PendingPost(Object event, Subscription subscription) {
this.event = event;
this.subscription = subscription;
}
/**
* 从池子中获取一个消息
*
* @param subscription 订阅信息
* @param event 事件
*/
static PendingPost obtainPendingPost(Subscription subscription, Object event) {
synchronized (pendingPostPool) {
int size = pendingPostPool.size();
if (size > 0) {
PendingPost pendingPost = pendingPostPool.remove(size - 1);
//对字段赋值
pendingPost.event = event;
pendingPost.subscription = subscription;
pendingPost.next = null;
return pendingPost;
}
}
return new PendingPost(event, subscription);
}
/**
* 回收
*
* @param pendingPost 包裹了订阅者信息的消息
*/
static void releasePendingPost(PendingPost pendingPost) {
//重置字段
pendingPost.event = null;
pendingPost.subscription = null;
pendingPost.next = null;
//将对象放回对象池
synchronized (pendingPostPool) {
//这里对池子大小做限制,不然会不断让池容量增长
if (pendingPostPool.size() < 10000) {
pendingPostPool.add(pendingPost);
}
}
}
}
PendingPostQueue,Poster发送事件使用的队列,维护了2个消息,队头消息和队尾消息,提供enqueue()方法消息入队,poll()方法获取下一个消息,这2个方法中,维护消息之间的链表关系。
/**
* 发送事件队列,维护一个消息对象链表
*/
final class PendingPostQueue {
/**
* 队头消息
*/
private PendingPost head;
/**
* 队尾
*/
private PendingPost tail;
/**
* 入队
*
* @param pendingPost 下一个消息
*/
synchronized void enqueue(PendingPost pendingPost) {
if (pendingPost == null) {
throw new NullPointerException("null cannot be enqueued");
}
//将事件绑定在,当前最后一个事件对象的next
if (tail != null) {
tail.next = pendingPost;
tail = pendingPost;
} else if (head == null) {
//第一次,队头为空,赋值
head = tail = pendingPost;
} else {
throw new IllegalStateException("Head present, but no tail");
}
notifyAll();
}
/**
* 获取下一个消息
*/
synchronized PendingPost poll() {
PendingPost pendingPost = head;
if (head != null) {
head = head.next;
if (head == null) {
tail = null;
}
}
return pendingPost;
}
synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {
if (head == null) {
wait(maxMillisToWait);
}
return poll();
}
}
Android主线程Poster:HandlerPoster
子线程串行Poster:BackgroundPoster
子线程并发Poster:AsyncPoster
Android主线程Poster:HandlerPoster,吱声继承Handler,实现Poster接口,enqueue()方法首先调用obtainPendingPost()获取一个消息,然后入队队列,判断如果没有执行则开始,然后就是用Handler的sendMessage()发消息,Handler发送消息,会主线程回调handleMessage()方法,用while循环,不断从队列中拿取消息,然后使用消息内的订阅信息,invokeSubscriber()调用订阅者的订阅方法,这样就实现了主线程回调。
/**
* Android主线程事件发送器,继承Handler,实现Poster接口
*/
public class HandlerPoster extends Handler implements Poster {
/**
* 发送队列
*/
private final PendingPostQueue queue;
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
/**
* 是否正在运行,没有限制
*/
private boolean handlerActive;
protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue();
}
/**
* 入队一个事件
*
* @param subscription 订阅信息
* @param event 事件对象
*/
@Override
public void enqueue(Subscription subscription, Object event) {
//获取一个等待发送的消息
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
//消息入队
queue.enqueue(pendingPost);
//没有正在执行,那么执行
if (!handlerActive) {
handlerActive = true;
//使用Handler发消息
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 = queue.poll();
if (pendingPost == null) {
synchronized (this) {
//为空再检查一遍,因为在并发情况,使用synchronized同步
pendingPost = queue.poll();
//真的是获取不到了,那么就是没有
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
//反射调用订阅者的订阅方法,这里在Handler中回调,所以在主线程
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;
}
}
}
子线程串行Poster:BackgroundPoster,实现了Runnable和Poster接口,enqueue()方法中,也是先obtainPendingPost()获取一个消息,然后消息入队,判断没有执行,则马上执行。通过eventBus.getExecutorService()获取配置的线程池执行器,调用执行器的execute(),将自己传入,能传入是因为BackgroundPoster实现了Runnable接口,那么自然run()肯定被复写了。run()方法,也是启动一个while循环,从队列中获取下一个消息,由于run()方法回调已经在子线程中执行了,所以invokeSubscriber()调用回订阅者的订阅方法,自然在子线程执行。BackgroundPoster是单线程执行的,它是怎么做到的呢?其实是因为executorRunning这个标志位,任务开始前置为true,任务结束才置为false,在enqueue()入队方法时就返回executorRunning是否为false,为false则只添加到队列中,这样自然成为了单线程执行了。其实还使用了synchronized来保证不同线程去enqueue()入队时,在子线程中线程不安全的问题。
/**
* 子线程串行回调事件订阅的发送器,实现了Runnable和Poster接口
*/
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();
}
@Override
public void enqueue(Subscription subscription, Object event) {
//获取一个消息,并将任务重新初始化
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
//加synchronized保证单线程执行
synchronized (this) {
//任务入队
queue.enqueue(pendingPost);
//如果没有执行,马上执行
if (!executorRunning) {
executorRunning = true;
//获取配置的线程池执行器进行执行,将任务包裹到自身去执行
eventBus.getExecutorService().execute(this);
}
}
}
@Override
public void run() {
try {
try {
//一直死循环执行
while (true) {
//获取下一个消息,并设定1秒阻塞
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
//加synchronized保证单线程执行,这里也加是因为要保证executorRunning的值不出错
synchronized (this) {
//同样要双重检查
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 {
//所有发送任务执行完毕,标志位置为false,下次再入队再继续执行
executorRunning = false;
}
}
}
子线程并发Poster:AsyncPoster,实现了Runnable,、Poster接口,和BackgroundPoster类似,但是不是串行而是并行,enqueue()方法获取一个消息,将消息入队,然后eventBus.getExecutorService()获取到线程池执行器后,马上execute()传入自身,那么也一样,run()方法肯定被重写,run()方法中,获取下一个消息,马上调用invokeSubscriber()回调订阅者的订阅方法,因为run()回调是在子线程,所以回调订阅者时为子线程调用。
/**
* 子线程并发执行器,实现了Runnable,、Poster接口
*/
class AsyncPoster implements Runnable, Poster {
/**
* 消息队列
*/
private final PendingPostQueue queue;
private final EventBus eventBus;
AsyncPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
@Override
public void enqueue(Subscription subscription, Object event) {
//入队,获取一个缓存的PendingPost消息对象,重新初始化
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
//将消息入队
queue.enqueue(pendingPost);
//获取执行器执行
eventBus.getExecutorService().execute(this);
}
@Override
public void run() {
//获取一个PendingPost消息
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
//反射调用订阅者的订阅方法
eventBus.invokeSubscriber(pendingPost);
}
}
订阅者解注册
typesBySubscriber:订阅者和它所订阅的事件类型映射,一对多关系,一个订阅者对应多个事件
解注册,使用typesBySubscriber获取订阅者订阅的所有事件类型,判断订阅列表是否为空,不为空,则for循环订阅的事件列表,调用unsubscribeByEventType(),再将订阅者从typesBySubscriber中移除。
/**
* 注销事件注册
*
* @param subscriber 订阅者
*/
public synchronized void unregister(Object subscriber) {
//获取订阅者订阅的所有事件类型
List> subscribedTypes = typesBySubscriber.get(subscriber);
//没有订阅过,忽略
if (subscribedTypes != null) {
//遍历订阅的事件类型,取消注册
for (Class eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
//从订阅者列表中移除
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
获取事件类型的所有订阅者列表
有订阅者,则遍历订阅者列表,找出需要解注册的订阅者,将订阅信息active标志设置为false,表示取消,再将订阅者从订阅列表中移除。
/**
* 使用事件类型,注销订阅
*
* @param subscriber 订阅者
* @param eventType 事件类型
*/
private void unsubscribeByEventType(Object subscriber, Class eventType) {
//获取事件类型的所有订阅者的订阅定系
List subscriptions = subscriptionsByEventType.get(eventType);
//没有订阅者忽略
if (subscriptions != null) {
//遍历订阅者的订阅信息列表
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
//找到本次要取消订阅的订阅者信息
Subscription subscription = subscriptions.get(i);
if (subscription.subscriber == subscriber) {
//设置为取消订阅
subscription.active = false;
//从列表中移除
subscriptions.remove(i);
i--;
size--;
}
}
}
}
发送粘性事件
发送粘性事件,使用的是postSticky()方法,先将事件放到stickyEvents这个Map中,保存事件类型和最近发送的事件对象。然后调用post()发送事件,流程和上面的Post发送事件流程一样。
stickyEvents:粘性事件映射表,一对一关系,一个事件对应一个最近发送的事件对象
/**
* 发送粘性事件
*
* @param event 事件
*/
public void postSticky(Object event) {
//保存事件到粘性事件映射表
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
//马上发送事件,和普通事件一样,粘性事件的特点是register()订阅时,马上检查是否有存在的粘性事件,有则马上回调
post(event);
}
总结
EventBus中可见到运用了好几种设计模式:
单例模式:Double Check保存EventBus实例
建造者模式:EventBusBuilder自定义EventBus配置
享元模式:对象池复用FindState对象,减少频繁创建
策略模式:不同的线程有不同的Poster对象发送事件
熟悉设计模式,更容易理解开源框架的逻辑,可以说设计模式是框架中常用的套路了~
EventBus中的subscriptionsByEventType、typesBySubscriber,这2个Map,设计得很巧妙,在注册、反注册、发送事件等3个重要流程中都起到了很重要的作用,subscriptionsByEventType记录了订阅者和订阅方法信息保存,在Post发送时,使用subscriptionsByEventType反查出订阅对应的订阅方法,利用不同的Poster在不同线程中调用。typesBySubscriber则是保存订阅者和订阅的事件类型的关系,在注册和反注册中起到注册表的作用。
你可能感兴趣的:(EventBus3源码分析)
华为云专家出品《深入理解边缘计算》电子书上线
华为云PaaS服务小智
华为云 边缘计算 人工智能
华为开发者大会PaaS生态电子书推荐,助你成为了不起的开发者!什么是边缘计算?边缘计算的应用场景有哪些?华为云出品《深入理解边缘计算》电子书上线带你系统理解云、边、端协同的相关原理了解开源项目的源码分析流程学成能够对云、边、端主流开源实现进行定制开发!【适用人群】1.对云原生感兴趣的开发者2.对边缘计算有学习需求或想拓展业务之外开发技能的开发者【精彩导读】首先,介绍边缘计算概念、边缘计算系统具体组
Spring Bean 生命周期
金州小铁匠
spring python java
SpringBean生命周期一、Bean的创建方式与底层实现Spring中Bean的创建方式及其底层源码实现是理解生命周期的关键。以下是常见的Bean注册方式及其源码层面的核心逻辑:1.XML配置方式源码分析:解析阶段:XmlBeanDefinitionReader解析XML文件,生成BeanDefinition并注册到BeanFactory的beanDefinitionMap。注册逻辑:Defa
【Hive】学习路线:架构、运维、Hsql实战、源码分析
roman_日积跬步-终至千里
# hive hive 学习 架构
文章目录一.Hive基础学习1.基础知识2.安装与配置3.数据存储与表结构二.hive运维三.Hive实战1.HiveSQL基础2.高级查询与数据分析3.数据存储优化4.性能调优四.Hive源码分析一.Hive基础学习1.基础知识hive简介架构说明【hive-design】hive架构详解:描述了hive架构,hive主要组件的作用、hsql在hive执行过程中的底层细节、hive各组件作用2.
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_process_options
若云止水
nginx 运维
ngx_process_options声明在src\core\nginx.cstaticngx_int_tngx_process_options(ngx_cycle_t*cycle);定义在src\core\nginx.cstaticngx_int_tngx_process_options(ngx_cycle_t*cycle){u_char*p;size_tlen;if(ngx_prefix){l
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_atoi 函数
若云止水
nginx 运维
ngx_atoi声明在src/core/ngx_string.hngx_int_tngx_atoi(u_char*line,size_tn);定义在src/core/ngx_string.cngx_int_tngx_atoi(u_char*line,size_tn){ngx_int_tvalue,cutoff,cutlim;if(n==0){returnNGX_ERROR;}cutoff=NGX_
深入源码分析 kotlin的CoroutineExceptionHandler机制
古苏
kotlin android
启动一个协程,然后内部启动子协程,那么最内层如果发生异常,是怎么传递异常的?valrootExceptionHandler=CoroutineExceptionHandler{_,throwable->println("调用【根】协程异常处理器:${throwable.message}")}valparentExceptionHandler=CoroutineExceptionHandler{_,
QT OpenGL高级编程
QT性能优化QT原理源码QT界面美化
qt qt6.3 qt5 QT教程 c++
QTOpenGL高级编程使用AI技术辅助生成QT界面美化视频课程QT性能优化视频课程QT原理与源码分析视频课程QTQMLC++扩展开发视频课程免费QT视频课程您可以看免费1000+个QT技术视频免费QT视频课程QT统计图和QT数据可视化视频免费看免费QT视频课程QT性能优化视频免费看免费QT视频课程QT界面美化视频免费看1QT_OpenGL基础1.1OpenGL简介1.1.1OpenGL简介Ope
QT 3D光照与阴影
QT性能优化QT原理源码QT界面美化
qt qt6.3 qt5 QT教程 c++ 3d
QT3D光照与阴影使用AI技术辅助生成QT界面美化视频课程QT性能优化视频课程QT原理与源码分析视频课程QTQMLC++扩展开发视频课程免费QT视频课程您可以看免费1000+个QT技术视频免费QT视频课程QT统计图和QT数据可视化视频免费看免费QT视频课程QT性能优化视频免费看免费QT视频课程QT界面美化视频免费看1QT_3D光照基础1.1光照概念与重要性1.1.1光照概念与重要性光照概念与重要性
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_crc32_table_init 函数
若云止水
nginx 运维
ngx_crc32_table_init声明在src/core/ngx_crc32.hngx_int_tngx_crc32_table_init(void);实现在src/core/ngx_crc32.cngx_int_tngx_crc32_table_init(void){void*p;if(((uintptr_t)ngx_crc32_table_short&~((uintptr_t)ngx_c
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_os_init 函数
若云止水
nginx 运维
ngx_os_init声明在src/os/unix/ngx_os.hngx_int_tngx_os_init(ngx_log_t*log);定义在src\os\unix\ngx_posix_init.cngx_int_tngx_os_init(ngx_log_t*log){ngx_time_t*tp;ngx_uint_tn;#if(NGX_HAVE_LEVEL1_DCACHE_LINESIZE)l
Spark源码分析
陈同学�
spark big data scala
Spark源码分析SparkonYarnclientCluster本质区别,driver位置不同1)有哪些不同得进程?2)分别有什么作用?3)Spark作业执行流程是什么样的跑yarn有--masteryarnCoarseGrainedExecutorBackend默认executor有两个CoarseGrainedExecutorBackendSparkSubmitApplicationMast
Spark源码分析 – Shuffle
weixin_34292924
大数据
参考详细探究Spark的shuffle实现,写的很清楚,当前设计的来龙去脉HadoopHadoop的思路是,在mapper端每次当memorybuffer中的数据快满的时候,先将memory中的数据,按partition进行划分,然后各自存成小文件,这样当buffer不断的spill的时候,就会产生大量的小文件所以Hadoop后面直到reduce之前做的所有的事情其实就是不断的merge,基于文件
【QT教程】QT6图形渲染与OpenGL编程 QT
QT性能优化QT原理源码QT界面美化
qt qt6.3 qt5 c++ QT教程
QT6图形渲染与OpenGL编程使用AI技术辅助生成QT界面美化视频课程QT性能优化视频课程QT原理与源码分析视频课程QTQMLC++扩展开发视频课程免费QT视频课程您可以看免费1000+个QT技术视频免费QT视频课程QT统计图和QT数据可视化视频免费看免费QT视频课程QT性能优化视频免费看免费QT视频课程QT界面美化视频免费看1QT6图形渲染机制1.1QT6图形渲染概述1.1.1QT6图形渲染概
2024年网络安全最全【玄机】常见攻击事件分析--钓鱼邮件,网络相关+网络安全三方库的源码分析+数据结构与算法
2401_84302583
程序员 网络安全 学习 面试
还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!【完整版领取方式在文末!!】93道网络安全面试题内容实在太多,不一一截图了黑客学习资源推荐最后给大家分享一份全套的网络安全
责任链模式原理详解和源码实例以及Spring AOP拦截器链的执行源码如何使用责任链模式?
一个儒雅随和的男子
spring 设计模式 责任链模式 spring java
前言 本文首先介绍了责任链的基本原理,并附带一个例子说明责任链模式,确保能够理解责任链的前提下,在进行SpringAOP执行责任链的源码分析。责任链模式允许将多个处理对象连接成链,请求沿着链传递,直到被处理或结束。每个处理者可以选择处理请求或传递给下一个。 SpringAOP的拦截器链,拦截器或者过滤器链,都是典型的责任链应用。比如,当一个方法被调用时,多个拦截器按顺序执行,每个拦截器可以决定
OpenMetadata MySQL 数据库使用率提取管道实现解析
10年JAVA大数据技术研究者
数据治理 数据库 mysql openmetadata 源码分析
目录架构概述核心组件源码分析使用率指标定义数据提取流程图源码类图配置与扩展指南架构概述OpenMetadata通过可插拔的元数据摄取框架实现对MySQL使用率数据的采集,核心流程包含三个阶段:数据采集层:从MySQLperformance_schema和sysschema获取原始指标指标处理层:将原始数据转换为统一的使用率指标模型数据存储层:将处理后的指标持久化到OpenMetadata服务核心组
Python3.5源码分析-sys模块及site模块导入
小屋子大侠
python Python分析 python源码
Python3源码分析本文环境python3.5.2。参考书籍>python官网Python3的sys模块初始化根据分析完成builtins初始化后,继续分析sys模块的初始化,继续分析_Py_InitializeEx_Private函数的执行,void_Py_InitializeEx_Private(intinstall_sigs,intinstall_importlib){...sysmod=
Android从源码分析handler.post(runnable),view.post(runnable),runOnUiThread(runnable)执行时机
听者110
Android高级开发系列笔记 Android 线程
大家好,我是听者,耳听心受的听,孙行者的者,感谢大家阅读我的文章。废话不说直接进入主题,不管是Android还是其他语言,线程之间通信都是一个比较“头疼”问题,开发Android的码农应该都知道回到主线程的方式有handler.post(runnable),view.post(runnable),runOnUiThread(runnable)。但是这三种方式的区别以及其执行的时机如何呢?今天就给大
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_localtime 函数
若云止水
nginx 运维
ngx_localtime函数声明在src\os\unix\ngx_time.h中:voidngx_localtime(time_ts,ngx_tm_t*tm);定义在src/os/unix/ngx_time.c中voidngx_localtime(time_ts,ngx_tm_t*tm){#if(NGX_HAVE_LOCALTIME_R)(void)localtime_r(&s,tm);#els
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_pnalloc函数
若云止水
nginx 运维
ngx_pnalloc声明在src\core\ngx_palloc.hvoid*ngx_pnalloc(ngx_pool_t*pool,size_tsize);定义在src\core\ngx_palloc.cvoid*ngx_pnalloc(ngx_pool_t*pool,size_tsize){#if!(NGX_DEBUG_PALLOC)if(sizemax){returnngx_palloc_
十四、Flink源码阅读--JobGraph生成过程
灰二和杉菜
Apache Flink Flink JobGraph生成源码分析
上篇分析了client整个提交任务过程,最终提交的是一个JobGraph对象,那么是如何从jar或sql任务转为JobGraph的呢,这篇我们仔细研究一下,版本为1.6.3源码分析上篇我们介绍client端提交任务最终会到到ClusterClient.run()方法,就在这个方法中封装了JobGraph的步骤。publicJobSubmissionResultrun(FlinkPlancompil
RocketMQ源码分析-Rpc通信模块(remoting)二
吃水果的猪儿虫
java-rocketmq rocketmq rpc
前言今天继续RocketMQ-Rpc通信模块(remoting)的源码分析。上一章提到了主要的start()方法执行流程,如果有不清楚的地方可以一起讨论哈,这篇文章会继续解读主要方法,按照惯例先看看NettyRemotingAbstract的类图,看类图知方法。和NettyEventExecutor以及MQ的交互流程。按照惯例先看看NettyRemotingAbstract的类图,看类图知方法,文
QT 3D渲染技术详解
QT性能优化QT原理源码QT界面美化
qt 3d qt6.3 qt5 c++ QT教程
QT3D渲染技术详解使用AI技术辅助生成QT界面美化视频课程QT性能优化视频课程QT原理与源码分析视频课程QTQMLC++扩展开发视频课程免费QT视频课程您可以看免费1000+个QT技术视频免费QT视频课程QT统计图和QT数据可视化视频免费看免费QT视频课程QT性能优化视频免费看免费QT视频课程QT界面美化视频免费看1QT_3D渲染技术概述1.13D渲染技术简介1.1.13D渲染技术简介3D渲染技
ffmpeg源码分析:avformat_open_input()
风雨兼程8023
ffmpeg ffmpeg
目录一、avformat_alloc_context()二、init_input()2.1av_probe_input_format2()2.2av_probe_input_buffer2()2.3io_open三、read_header()本文简单分析FFmpeg中一个常用的函数:avformat_open_input()。该函数用于打开多媒体数据并且获得一些相关的信息。它的声明位于libavf
FFMpeg 源码分析 (3)avformat_open_input()
雷霆小屁熊
FFmpeg ffmpeg
这个函数主要用来打开媒体资源。完成媒体格式的探测和获取相关的媒体信息的工作。函数完成定义如下:intavformat_open_input(AVFormatContext**ps,constchar*filename,AVInputFormat*fmt,AVDictionary**options){AVFormatContext*s=*ps;inti,ret=0;AVDictionary*tmp=
FFMPEG源码分析:avformat_open_input()(媒体打开函数)
sunshineywz
ffmpeg
本文分析了FFMPEG中的媒体打开函数avformat_open_input()//参数ps包含一切媒体相关的上下文结构,有它就有了一切,本函数如果打开媒体成功,//会返回一个AVFormatContext的实例.//参数filename是媒体文件名或URL.//参数fmt是要打开的媒体格式的操作结构,因为是读,所以是inputFormat.此处可以//传入一个调用者定义的inputFormat,
Java CopyOnWriteArrayList 源码分析及使用案例
铁甲小宝摸鱼
Java面试八股文 java windows 开发语言
CopyOnWriteArrayList是Java中一种线程安全的List实现,它通过在每次修改时复制底层数组来实现线程安全。本文将详细分析CopyOnWriteArrayList的源码,并结合具体使用案例来展示其使用场景和优缺点。一、CopyOnWriteArrayList简介CopyOnWriteArrayList是java.util.concurrent包中的一个类,适用于读多写少的场景。它
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_time_update函数
若云止水
ubuntu nginx linux
定义在src\core\ngx_times.c中ngx_time_init函数后面voidngx_time_update(void){u_char*p0,*p1,*p2,*p3,*p4;ngx_tm_ttm,gmt;time_tsec;ngx_uint_tmsec;ngx_time_t*tp;structtimevaltv;if(!ngx_trylock(&ngx_time_lock)){retu
Android N(全志平台A40i)添加adb登录密码
阿姨打太极
android adb
需求:在adbshell登录终端时加入鉴权密码,鉴权开关可配置且密码可修改问题分析:见下文AndroidN:adb及adbd源码分析解决方案:思路:pc上终端输入adbshell命令后,实际上是adbd守护进程fork出的子进程来执行/bin/sh,adbd监听usb/tcp输入执行命令,并通过socket将结果回显到pc。那么我们解决该问题的方法就是在执行/bin/sh之前加入我们的校验脚本。1
Kubernetes源码分析之kubelet
「已注销」
runtime 操作系统 运维
本节所有的代码基于1.13.4版本。启动分析Kubelet的启动参数有两种,kubeletFlags和kubeletConfig。其中,kubeletFlags与我们使用的kubelet的--参数命令保持一致;kubeletConfig通过解析特定的配置文件完成参数的配置,它们共同构成kubelet启动参数的配置。如图基本参数配置完成之后,接下来就是配置启动的Run方法。Kubelet启动的Run
Js函数返回值
_wy_
js return
一、返回控制与函数结果,语法为:return 表达式;作用: 结束函数执行,返回调用函数,而且把表达式的值作为函数的结果 二、返回控制语法为:return;作用: 结束函数执行,返回调用函数,而且把undefined作为函数的结果 在大多数情况下,为事件处理函数返回false,可以防止默认的事件行为.例如,默认情况下点击一个<a>元素,页面会跳转到该元素href属性
MySQL 的 char 与 varchar
bylijinnan
mysql
今天发现,create table 时,MySQL 4.1有时会把 char 自动转换成 varchar
测试举例:
CREATE TABLE `varcharLessThan4` (
`lastName` varchar(3)
) ;
mysql> desc varcharLessThan4;
+----------+---------+------+-
Quartz——TriggerListener和JobListener
eksliang
TriggerListener JobListener quartz
转载请出自出处:http://eksliang.iteye.com/blog/2208624 一.概述
listener是一个监听器对象,用于监听scheduler中发生的事件,然后执行相应的操作;你可能已经猜到了,TriggerListeners接受与trigger相关的事件,JobListeners接受与jobs相关的事件。
二.JobListener监听器
j
oracle层次查询
18289753290
oracle;层次查询;树查询
.oracle层次查询(connect by)
oracle的emp表中包含了一列mgr指出谁是雇员的经理,由于经理也是雇员,所以经理的信息也存储在emp表中。这样emp表就是一个自引用表,表中的mgr列是一个自引用列,它指向emp表中的empno列,mgr表示一个员工的管理者,
select empno,mgr,ename,sal from e
通过反射把map中的属性赋值到实体类bean对象中
酷的飞上天空
javaee 泛型 类型转换
使用过struts2后感觉最方便的就是这个框架能自动把表单的参数赋值到action里面的对象中
但现在主要使用Spring框架的MVC,虽然也有@ModelAttribute可以使用但是明显感觉不方便。
好吧,那就自己再造一个轮子吧。
原理都知道,就是利用反射进行字段的赋值,下面贴代码
主要类如下:
import java.lang.reflect.Field;
imp
SAP HANA数据存储:传统硬盘的瓶颈问题
蓝儿唯美
HANA
SAPHANA平台有各种各样的应用场景,这也意味着客户的实施方法有许多种选择,关键是如何挑选最适合他们需求的实施方案。
在 《Implementing SAP HANA》这本书中,介绍了SAP平台在现实场景中的运作原理,并给出了实施建议和成功案例供参考。本系列文章节选自《Implementing SAP HANA》,介绍了行存储和列存储的各自特点,以及SAP HANA的数据存储方式如何提升空间压
Java Socket 多线程实现文件传输
随便小屋
java socket
高级操作系统作业,让用Socket实现文件传输,有些代码也是在网上找的,写的不好,如果大家能用就用上。
客户端类:
package edu.logic.client;
import java.io.BufferedInputStream;
import java.io.Buffered
java初学者路径
aijuans
java
学习Java有没有什么捷径?要想学好Java,首先要知道Java的大致分类。自从Sun推出Java以来,就力图使之无所不包,所以Java发展到现在,按应用来分主要分为三大块:J2SE,J2ME和J2EE,这也就是Sun ONE(Open Net Environment)体系。J2SE就是Java2的标准版,主要用于桌面应用软件的编程;J2ME主要应用于嵌入是系统开发,如手机和PDA的编程;J2EE
APP推广
aoyouzi
APP 推广
一,免费篇
1,APP推荐类网站自主推荐
最美应用、酷安网、DEMO8、木蚂蚁发现频道等,如果产品独特新颖,还能获取最美应用的评测推荐。PS:推荐简单。只要产品有趣好玩,用户会自主分享传播。例如足迹APP在最美应用推荐一次,几天用户暴增将服务器击垮。
2,各大应用商店首发合作
老实盯着排期,多给应用市场官方负责人献殷勤。
3,论坛贴吧推广
百度知道,百度贴吧,猫扑论坛,天涯社区,豆瓣(
JSP转发与重定向
百合不是茶
jsp servlet Java Web jsp转发
在servlet和jsp中我们经常需要请求,这时就需要用到转发和重定向;
转发包括;forward和include
例子;forwrad转发; 将请求装法给reg.html页面
关键代码;
req.getRequestDispatcher("reg.html
web.xml之jsp-config
bijian1013
java web.xml servlet jsp-config
1.作用:主要用于设定JSP页面的相关配置。
2.常见定义:
<jsp-config>
<taglib>
<taglib-uri>URI(定义TLD文件的URI,JSP页面的tablib命令可以经由此URI获取到TLD文件)</tablib-uri>
<taglib-location>
TLD文件所在的位置
JSF2.2 ViewScoped Using CDI
sunjing
CDI JSF 2.2 ViewScoped
JSF 2.0 introduced annotation @ViewScoped; A bean annotated with this scope maintained its state as long as the user stays on the same view(reloads or navigation - no intervening views). One problem w
【分布式数据一致性二】Zookeeper数据读写一致性
bit1129
zookeeper
很多文档说Zookeeper是强一致性保证,事实不然。关于一致性模型请参考http://bit1129.iteye.com/blog/2155336
Zookeeper的数据同步协议
Zookeeper采用称为Quorum Based Protocol的数据同步协议。假如Zookeeper集群有N台Zookeeper服务器(N通常取奇数,3台能够满足数据可靠性同时
Java开发笔记
白糖_
java开发
1、Map<key,value>的remove方法只能识别相同类型的key值
Map<Integer,String> map = new HashMap<Integer,String>();
map.put(1,"a");
map.put(2,"b");
map.put(3,"c"
图片黑色阴影
bozch
图片
.event{ padding:0; width:460px; min-width: 460px; border:0px solid #e4e4e4; height: 350px; min-heig
编程之美-饮料供货-动态规划
bylijinnan
动态规划
import java.util.Arrays;
import java.util.Random;
public class BeverageSupply {
/**
* 编程之美 饮料供货
* 设Opt(V’,i)表示从i到n-1种饮料中,总容量为V’的方案中,满意度之和的最大值。
* 那么递归式就应该是:Opt(V’,i)=max{ k * Hi+Op
ajax大参数(大数据)提交性能分析
chenbowen00
Web Ajax 框架 浏览器 prototype
近期在项目中发现如下一个问题
项目中有个提交现场事件的功能,该功能主要是在web客户端保存现场数据(主要有截屏,终端日志等信息)然后提交到服务器上方便我们分析定位问题。客户在使用该功能的过程中反应点击提交后反应很慢,大概要等10到20秒的时间浏览器才能操作,期间页面不响应事件。
根据客户描述分析了下的代码流程,很简单,主要通过OCX控件截屏,在将前端的日志等文件使用OCX控件打包,在将之转换为
[宇宙与天文]在太空采矿,在太空建造
comsci
我们在太空进行工业活动...但是不太可能把太空工业产品又运回到地面上进行加工,而一般是在哪里开采,就在哪里加工,太空的微重力环境,可能会使我们的工业产品的制造尺度非常巨大....
地球上制造的最大工业机器是超级油轮和航空母舰,再大些就会遇到困难了,但是在空间船坞中,制造的最大工业机器,可能就没
ORACLE中CONSTRAINT的四对属性
daizj
oracle CONSTRAINT
ORACLE中CONSTRAINT的四对属性
summary:在data migrate时,某些表的约束总是困扰着我们,让我们的migratet举步维艰,如何利用约束本身的属性来处理这些问题呢?本文详细介绍了约束的四对属性: Deferrable/not deferrable, Deferred/immediate, enalbe/disable, validate/novalidate,以及如
Gradle入门教程
dengkane
gradle
一、寻找gradle的历程
一开始的时候,我们只有一个工程,所有要用到的jar包都放到工程目录下面,时间长了,工程越来越大,使用到的jar包也越来越多,难以理解jar之间的依赖关系。再后来我们把旧的工程拆分到不同的工程里,靠ide来管理工程之间的依赖关系,各工程下的jar包依赖是杂乱的。一段时间后,我们发现用ide来管理项程很不方便,比如不方便脱离ide自动构建,于是我们写自己的ant脚本。再后
C语言简单循环示例
dcj3sjt126com
c
# include <stdio.h>
int main(void)
{
int i;
int count = 0;
int sum = 0;
float avg;
for (i=1; i<=100; i++)
{
if (i%2==0)
{
count++;
sum += i;
}
}
avg
presentModalViewController 的动画效果
dcj3sjt126com
controller
系统自带(四种效果):
presentModalViewController模态的动画效果设置:
[cpp]
view plain
copy
UIViewController *detailViewController = [[UIViewController al
java 二分查找
shuizhaosi888
二分查找 java二分查找
需求:在排好顺序的一串数字中,找到数字T
一般解法:从左到右扫描数据,其运行花费线性时间O(N)。然而这个算法并没有用到该表已经排序的事实。
/**
*
* @param array
* 顺序数组
* @param t
* 要查找对象
* @return
*/
public stati
Spring Security(07)——缓存UserDetails
234390216
ehcache 缓存 Spring Security
Spring Security提供了一个实现了可以缓存UserDetails的UserDetailsService实现类,CachingUserDetailsService。该类的构造接收一个用于真正加载UserDetails的UserDetailsService实现类。当需要加载UserDetails时,其首先会从缓存中获取,如果缓存中没
Dozer 深层次复制
jayluns
VO maven po
最近在做项目上遇到了一些小问题,因为架构在做设计的时候web前段展示用到了vo层,而在后台进行与数据库层操作的时候用到的是Po层。这样在业务层返回vo到控制层,每一次都需要从po-->转化到vo层,用到BeanUtils.copyProperties(source, target)只能复制简单的属性,因为实体类都配置了hibernate那些关联关系,所以它满足不了现在的需求,但后发现还有个很
CSS规范整理(摘自懒人图库)
a409435341
html UI css 浏览器
刚没事闲着在网上瞎逛,找了一篇CSS规范整理,粗略看了一下后还蛮有一定的道理,并自问是否有这样的规范,这也是初入前端开发的人一个很好的规范吧。
一、文件规范
1、文件均归档至约定的目录中。
具体要求通过豆瓣的CSS规范进行讲解:
所有的CSS分为两大类:通用类和业务类。通用的CSS文件,放在如下目录中:
基本样式库 /css/core
C++动态链接库创建与使用
你不认识的休道人
C++ dll
一、创建动态链接库
1.新建工程test中选择”MFC [dll]”dll类型选择第二项"Regular DLL With MFC shared linked",完成
2.在test.h中添加
extern “C” 返回类型 _declspec(dllexport)函数名(参数列表);
3.在test.cpp中最后写
extern “C” 返回类型 _decls
Android代码混淆之ProGuard
rensanning
ProGuard
Android应用的Java代码,通过反编译apk文件(dex2jar、apktool)很容易得到源代码,所以在release版本的apk中一定要混淆一下一些关键的Java源码。
ProGuard是一个开源的Java代码混淆器(obfuscation)。ADT r8开始它被默认集成到了Android SDK中。
官网:
http://proguard.sourceforge.net/
程序员在编程中遇到的奇葩弱智问题
tomcat_oracle
jquery 编程 ide
现在收集一下:
排名不分先后,按照发言顺序来的。
1、Jquery插件一个通用函数一直报错,尤其是很明显是存在的函数,很有可能就是你没有引入jquery。。。或者版本不对
2、调试半天没变化:不在同一个文件中调试。这个很可怕,我们很多时候会备份好几个项目,改完发现改错了。有个群友说的好: 在汤匙
解决maven-dependency-plugin (goals "copy-dependencies","unpack") is not supported
xp9802
dependency
解决办法:在plugins之前添加如下pluginManagement,二者前后顺序如下:
[html]
view plain
copy
<build>
<pluginManagement