EventBus

@Subscribe@AllowConcurrentEvents解析这两个注解 Subscribe:标记方法为事件订阅者,是事件处理的“入口”。:允许订阅方法并发执行,是对事件处理“执行方式”的优化(需保证线程安全)。两者共同作用于 EventBus 的事件订阅机制,帮助开发者实现低耦合的组件通信,尤其适合复杂系统中分散组件的协作场景。 阅读详情

自用,简单记录,推荐文章JAVA | Guava EventBus 使用 发布/订阅模式 - 云+社区 - 腾讯云

依赖

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1-jre</version>
        </dependency>

粗略介绍,下面这些特性都可以从源码分析中得到印证

1. 使用Subscribe注解方法来表示事件订阅,该方法必须是单参数方法,该参数即为订阅的事件类型。

2. 一个订阅者可以订阅多个事件,对应多个使用Subscribe注解的方法,

3. 不同的方法也可以订阅同一个事件。

情形1:一个订阅者中的不同方法订阅同一个事件,多个方法的执行顺序取决于方法的上下位置

情形2:多个订阅者订阅同一个事件。按注册先后顺序,决定优先执行哪个订阅者的监听方法。

两种情形都不建议,EventBus似乎不支持手动指定顺序,所以无法准确预知代码的执行顺序,理论上,一个事件触发,其处理逻辑应当清晰统一的。

4. 事件之间若存在父子关系,子事件也会被定订阅其父事件的方法接收,触发的先后顺序未验证(貌似是先子类后父类)

5. Subscriber methods cannot accept primitives 方法参数不接受原始数据类型,同时也只接受一个参数。


源码分析

一、事件总线EventBus

代码很简单,公开的方法仅有这几个,主要关注构造方法,注册,事件分发

 1. 属性说明

public class EventBus {

  private static final Logger logger = Logger.getLogger(EventBus.class.getName());
  //id标识符
  private final String identifier;
  //发送事件的线程池
  private final Executor executor;
  //订阅者异常处理器
  private final SubscriberExceptionHandler exceptionHandler;
  //订阅者注册中心
  private final SubscriberRegistry subscribers = new SubscriberRegistry(this);
  //事件分发策略
  private final Dispatcher dispatcher;
  ...
}

2. 构造说明

  public EventBus() {
    this("default");
  }

  public EventBus(String identifier) {
    this(
        identifier,
        MoreExecutors.directExecutor(),
        Dispatcher.perThreadDispatchQueue(),
        LoggingHandler.INSTANCE);
  }

  public EventBus(SubscriberExceptionHandler exceptionHandler) {
    this(
        "default",
        MoreExecutors.directExecutor(),
        Dispatcher.perThreadDispatchQueue(),
        exceptionHandler);
  }

  EventBus(
      String identifier,
      Executor executor,
      Dispatcher dispatcher,
      SubscriberExceptionHandler exceptionHandler) {
    this.identifier = checkNotNull(identifier);
    this.executor = checkNotNull(executor);
    this.dispatcher = checkNotNull(dispatcher);
    this.exceptionHandler = checkNotNull(exceptionHandler);
  }

可以看到,EventBus对外暴露的构造方法,只能去修改identifier和exceptionHandler两个参数

对于其中涉及到的executor和dispatcher,后面会再介绍,exceptionHandler简单提一下

  /** Handles the given exception thrown by a subscriber with the given context. */
  void handleSubscriberException(Throwable e, SubscriberExceptionContext context) {
    checkNotNull(e);
    checkNotNull(context);
    try {
      exceptionHandler.handleException(e, context);
    } catch (Throwable e2) {
      // if the handler threw an exception... well, just log it
      logger.log(
          Level.SEVERE,
          String.format(Locale.ROOT, "Exception %s thrown while handling exception: %s", e2, e),
          e2);
    }
  }

查看一下 handleSubscriberException 方法的调用,发现只有一处,即订阅者被分发到事件,进行事件处理时。方法的入参我们无需关心,会自动包装好。也就是说这是一个通用的事件处理过程中的异常处理回调方法,如果我们事件处理过程中上抛了未被捕获的异常,会调用该方法进行处理。不指定exceptionHandler的话,会使用默认的异常处理器LoggingHandler

  /** Dispatches {@code event} to this subscriber using the proper executor. */
  // 使用适当的executor将事件分派给该订阅者
  final void dispatchEvent(final Object event) {
    // 这里的executor默认为DirectExecutor,在EventBus中不支持修改
    executor.execute(
        new Runnable() {
          @Override
          public void run() {
            try {
              invokeSubscriberMethod(event);
            } catch (InvocationTargetException e) {
              bus.handleSubscriberException(e.getCause(), context(event));
            }
          }
        });
  }

3. 注册,入参为Object,即注册的事件对象可以是任意对象,实际的注册行为是由subscribers完成的,所以放在SubscriberRegistry中去介绍。

  public void register(Object object) {
    subscribers.register(object);
  }

  public void unregister(Object object) {
    subscribers.unregister(object);
  }

 4. 事件分发,从事件注册中心subscribers中找到该事件的所有订阅者,由事件分发器dispatcher进行分发处理,核心在SubscriberRegistry和Dispatcher中,放在后边介绍

当事件发出,但是未匹配到订阅者,且自身并不是DeadEvent实例时,会发出一个DeadEvent事件,DeadEvent没有默认的监听者,我们也可以自己定义一个。

  /**
   * Posts an event to all registered subscribers. This method will return successfully after the
   * event has been posted to all subscribers, and regardless of any exceptions thrown by
   * subscribers.
   *
   * <p>If no subscribers have been subscribed for {@code event}'s class, and {@code event} is not
   * already a {@link DeadEvent}, it will be wrapped in a DeadEvent and reposted.
   *
   * @param event event to post.
   */
  public void post(Object event) {
    Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
    if (eventSubscribers.hasNext()) {
      dispatcher.dispatch(event, eventSubscribers);
    } else if (!(event instanceof DeadEvent)) {
      // the event had no subscribers and was not itself a DeadEvent
      post(new DeadEvent(this, event));
    }
  }

二、订阅者注册中心 SubscriberRegistry

1. 内部属性

1.1 bus

一个事件总线eventBus对应一个订阅者注册中心,一个注册中心归属于一个事件总线,一一对应

  /** The event bus this registry belongs to. */
  @Weak private final EventBus bus;

1.2 subscribers

subscribers 维护所有订阅者与其订阅事件的关系,键为事件类型,值为订阅该事件的所有监听者

  /**
   * All registered subscribers, indexed by event type.
   *
   * <p>The {@link CopyOnWriteArraySet} values make it easy and relatively lightweight to get an
   * immutable snapshot of all current subscribers to an event without any locking.
   */
  private final ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers =
      Maps.newConcurrentMap();

1.3 subscriberMethodsCache

subscriberMethodsCache,静态变量,所有实例共享,当一个事件对象注册到多个其他事件总线EventBus的实例对象时,这些缓存能够很大提高效率,初始化后无数据,会在进行load调用后尝试缓存加载到的数据。

  /**
   * A thread-safe cache that contains the mapping from each class to all methods in that class and
   * all super-classes, that are annotated with {@code @Subscribe}. The cache is shared across all
   * instances of this class; this greatly improves performance if multiple EventBus instances are
   * created and objects of the same class are registered on all of them.
   */
  private static final LoadingCache<Class<?>, ImmutableList<Method>> subscriberMethodsCache =
      CacheBuilder.newBuilder()
          .weakKeys()
          .build(
              new CacheLoader<Class<?>, ImmutableList<Method>>() {
                @Override
                public ImmutableList<Method> load(Class<?> concreteClass) throws Exception {
                  return getAnnotatedMethodsNotCached(concreteClass);
                }
              });

getAnnotatedMethodsNotCached方法:当缓存中未缓存该监听者信息时,会调用CacheLoader重写的load方法,从而调用此方法寻找@Subscribe注解的方法

  private static ImmutableList<Method> getAnnotatedMethodsNotCached(Class<?> clazz) {
    // 得到其自身及其所有父类的Class文件,也就是说会自动注册其所有父类
    Set<? extends Class<?>> supertypes = TypeToken.of(clazz).getTypes().rawTypes();
    Map<MethodIdentifier, Method> identifiers = Maps.newHashMap();
    for (Class<?> supertype : supertypes) {
      for (Method method : supertype.getDeclaredMethods()) {
        if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) {
          // TODO(cgdecker): Should check for a generic parameter type and error out
          Class<?>[] parameterTypes = method.getParameterTypes();
          // 限制了@Subscribe方法参数只能有1个
          checkArgument(
              parameterTypes.length == 1,
              "Method %s has @Subscribe annotation but has %s parameters. "
                  + "Subscriber methods must have exactly 1 parameter.",
              method,
              parameterTypes.length);
           // 限制了@Subscribe方法参数不能为原始数据类型
          checkArgument(
              !parameterTypes[0].isPrimitive(),
              "@Subscribe method %s's parameter is %s. "
                  + "Subscriber methods cannot accept primitives. "
                  + "Consider changing the parameter to %s.",
              method,
              parameterTypes[0].getName(),
              Primitives.wrap(parameterTypes[0]).getSimpleName());

          MethodIdentifier ident = new MethodIdentifier(method);
          if (!identifiers.containsKey(ident)) {
            identifiers.put(ident, method);
          }
        }
      }
    }
    return ImmutableList.copyOf(identifiers.values());
  }

1.4 flattenHierarchyCache

一个全局缓存,维护了订阅者与其所有超类的对应关系,键为订阅者,值为超类的集合

  /** Global cache of classes to their flattened hierarchy of supertypes. */
  private static final LoadingCache<Class<?>, ImmutableSet<Class<?>>> flattenHierarchyCache =
      CacheBuilder.newBuilder()
          .weakKeys()
          .build(
              new CacheLoader<Class<?>, ImmutableSet<Class<?>>>() {
                // <Class<?>> is actually needed to compile
                @SuppressWarnings("RedundantTypeArguments")
                @Override
                public ImmutableSet<Class<?>> load(Class<?> concreteClass) {
                  return ImmutableSet.<Class<?>>copyOf(
                      TypeToken.of(concreteClass).getTypes().rawTypes());
                }
              });

2. 主要方法

2.1 注册

以下这几个方法是环环相扣的,关于Subscriber可以看下第三点的介绍

  /** Registers all subscriber methods on the given listener object. */
  void register(Object listener) {
    // 找到该订阅者订阅的所有事件,以及事件对应的方法信息(封装成了Subscriber对象)
    Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);

    for (Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {
      Class<?> eventType = entry.getKey();
      Collection<Subscriber> eventMethodsInListener = entry.getValue();

      CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);

      if (eventSubscribers == null) {
        CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<>();
        eventSubscribers =
            MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
      }

      eventSubscribers.addAll(eventMethodsInListener);
    }
  }

  /**
   * Returns all subscribers for the given listener grouped by the type of event they subscribe to.
   * 按事件类型划分,返回该订阅者对每个事件的订阅信息(一个事件可以对应多个Subscriber)
   */
  private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {
    Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();
    Class<?> clazz = listener.getClass();
    for (Method method : getAnnotatedMethods(clazz)) {
      Class<?>[] parameterTypes = method.getParameterTypes();
      Class<?> eventType = parameterTypes[0];
      methodsInListener.put(eventType, Subscriber.create(bus, listener, method));
    }
    return methodsInListener;
  }

  private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) {
    try {
      /*
       * 从缓存中获取,如果没有,会自动加载并写入缓存中
       * 该过程过于繁琐,不细究,自动加载的逻辑主要在getAnnotatedMethodsNotCached方法中
       */
      return subscriberMethodsCache.getUnchecked(clazz);
    } catch (UncheckedExecutionException e) {
      throwIfUnchecked(e.getCause());
      throw e;
    }
  }

/**
 * 当缓存中未记录该订阅信息时,进行获取注解订阅信息的主要方法
 */  
private static ImmutableList<Method> getAnnotatedMethodsNotCached(Class<?> clazz) {
    Set<? extends Class<?>> supertypes = TypeToken.of(clazz).getTypes().rawTypes();
    Map<MethodIdentifier, Method> identifiers = Maps.newHashMap();
    for (Class<?> supertype : supertypes) {
      for (Method method : supertype.getDeclaredMethods()) {
        if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) {
          // TODO(cgdecker): Should check for a generic parameter type and error out
          Class<?>[] parameterTypes = method.getParameterTypes();
          checkArgument(
              parameterTypes.length == 1,
              "Method %s has @Subscribe annotation but has %s parameters. "
                  + "Subscriber methods must have exactly 1 parameter.",
              method,
              parameterTypes.length);

          checkArgument(
              !parameterTypes[0].isPrimitive(),
              "@Subscribe method %s's parameter is %s. "
                  + "Subscriber methods cannot accept primitives. "
                  + "Consider changing the parameter to %s.",
              method,
              parameterTypes[0].getName(),
              Primitives.wrap(parameterTypes[0]).getSimpleName());

          MethodIdentifier ident = new MethodIdentifier(method);
          if (!identifiers.containsKey(ident)) {
            identifiers.put(ident, method);
          }
        }
      }
    }
    return ImmutableList.copyOf(identifiers.values());
  }

2.2 查找订阅者

当事件总线EventBus抛出事件后,会从注册中心查找所有与此事件相关的订阅者信息

  /**
   * Gets an iterator representing an immutable snapshot of all subscribers to the given event at
   * the time this method is called.
   */
  Iterator<Subscriber> getSubscribers(Object event) {
    // 从缓存中找到事件对象event的所有超类事件类型(包含自身)
    ImmutableSet<Class<?>> eventTypes = flattenHierarchy(event.getClass());

    List<Iterator<Subscriber>> subscriberIterators =
        Lists.newArrayListWithCapacity(eventTypes.size());

    for (Class<?> eventType : eventTypes) {
      CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);
      if (eventSubscribers != null) {
        // eager no-copy snapshot
        subscriberIterators.add(eventSubscribers.iterator());
      }
    }

    return Iterators.concat(subscriberIterators.iterator());
  }

  /**
   * 方法原理同subscriberMethodsCache缓存的逻辑,不再多聊
   */
  static ImmutableSet<Class<?>> flattenHierarchy(Class<?> concreteClass) {
    try {
      return flattenHierarchyCache.getUnchecked(concreteClass);
    } catch (UncheckedExecutionException e) {
      throw Throwables.propagate(e.getCause());
    }
  }

三、Subscriber订阅者

1. 属性

对用户实际自定义的listener对象(可以是任意类型Object)进行的封装,bus描述了该对象注册到了哪个事件总线中,target即为该对象,method描述了对应的@Subscribe方法,executor描述了用于向此订阅者分派事件的执行器。一个listener对象可以对应多个Subscriber对象(取决于@Subscribe方法的个数)

  /** The event bus this subscriber belongs to. */
  @Weak private EventBus bus;

  /** The object with the subscriber method. */
  @VisibleForTesting final Object target;

  /** Subscriber method. */
  private final Method method;

  /** Executor to use for dispatching events to this subscriber. */
  private final Executor executor;

2. 构造

构造方法私有化,同时提供了一个支持同步的静态内部类实现,重写invokeSubscriberMethod方法,通过关键字synchronized来保证在并发环境下的安全问题。仅当@Subscribe方法同时被@AllowConcurrentEvents注解修饰时,才会创建Subscriber实例

  static Subscriber create(EventBus bus, Object listener, Method method) {
    return isDeclaredThreadSafe(method)
        ? new Subscriber(bus, listener, method)
        : new SynchronizedSubscriber(bus, listener, method);
  }

  private static boolean isDeclaredThreadSafe(Method method) {
    return method.getAnnotation(AllowConcurrentEvents.class) != null;
  }

  private Subscriber(EventBus bus, Object target, Method method) {
    this.bus = bus;
    this.target = checkNotNull(target);
    this.method = method;
    method.setAccessible(true);
    // 所有的执行器都来源于事件总线,默认为DirectExecutor
    this.executor = bus.executor();
  }

  static final class SynchronizedSubscriber extends Subscriber {

    private SynchronizedSubscriber(EventBus bus, Object target, Method method) {
      super(bus, target, method);
    }

    @Override
    void invokeSubscriberMethod(Object event) throws InvocationTargetException {
      synchronized (this) {
        super.invokeSubscriberMethod(event);
      }
    }
  }
  /** Dispatches {@code event} to this subscriber using the proper executor. */
  final void dispatchEvent(final Object event) {
    executor.execute(
        new Runnable() {
          @Override
          public void run() {
            try {
              invokeSubscriberMethod(event);
            } catch (InvocationTargetException e) {
              bus.handleSubscriberException(e.getCause(), context(event));
            }
          }
        });
  }

  void invokeSubscriberMethod(Object event) throws InvocationTargetException {
    try {
      method.invoke(target, checkNotNull(event));
    } catch (IllegalArgumentException e) {
      throw new Error("Method rejected target/argument: " + event, e);
    } catch (IllegalAccessException e) {
      throw new Error("Method became inaccessible: " + event, e);
    } catch (InvocationTargetException e) {
      if (e.getCause() instanceof Error) {
        throw (Error) e.getCause();
      }
      throw e;
    }
  }
dispatchEvent方法中的executor决定了事件的处理是异步还是同步的,而EventBus中的executor默认是DirectExecutor,且不允许修改,所以实际上是同步处理。如果需要异步处理,Guava EventBus 为了简化操作,提供了一个简化的方案AsyncEventBus,其构造方法中允许修改executor
public class AsyncEventBus extends EventBus {


  public AsyncEventBus(String identifier, Executor executor) {
    super(identifier, executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
  }


  public AsyncEventBus(Executor executor, SubscriberExceptionHandler subscriberExceptionHandler) {
    super("default", executor, Dispatcher.legacyAsync(), subscriberExceptionHandler);
  }


  public AsyncEventBus(Executor executor) {
    super("default", executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
  }
}
enum DirectExecutor implements Executor {
  INSTANCE;

  @Override
  public void execute(Runnable command) {
    command.run();
  }

  @Override
  public String toString() {
    return "MoreExecutors.directExecutor()";
  }
}

 

四、Dispatcher 事件分发器

 抽象类,定义了dispatch方法模板,并提供了3种内部实现以及获取这3种实现的静态方法

abstract class Dispatcher {

  static Dispatcher perThreadDispatchQueue() {
    return new PerThreadQueuedDispatcher();
  }

  static Dispatcher legacyAsync() {
    return new LegacyAsyncDispatcher();
  }

  static Dispatcher immediate() {
    return ImmediateDispatcher.INSTANCE;
  }

 abstract void dispatch(Object event, Iterator<Subscriber> subscribers);

// ...

}

4.1 ImmediateDispatcher

最简单的一种,不做任何顺序限制,按照Subscribers自身遍历的顺序进行分发

 /** Implementation of {@link #immediate()}. */
  private static final class ImmediateDispatcher extends Dispatcher {
    private static final ImmediateDispatcher INSTANCE = new ImmediateDispatcher();

    @Override
    void dispatch(Object event, Iterator<Subscriber> subscribers) {
      checkNotNull(event);
      while (subscribers.hasNext()) {
        subscribers.next().dispatchEvent(event);
      }
    }
  }

4.2 PerThreadQueuedDispatcher

默认方式,保证发布在单个线程上的所有事件都按照发布的顺序调度给所有订阅者

  /** Implementation of a {@link #perThreadDispatchQueue()} dispatcher. */
  private static final class PerThreadQueuedDispatcher extends Dispatcher {

    // This dispatcher matches the original dispatch behavior of EventBus.

    /** Per-thread queue of events to dispatch. */
    private final ThreadLocal<Queue<Event>> queue =
        new ThreadLocal<Queue<Event>>() {
          @Override
          protected Queue<Event> initialValue() {
            return Queues.newArrayDeque();
          }
        };

    /** Per-thread dispatch state, used to avoid reentrant event dispatching. 防重入*/
    private final ThreadLocal<Boolean> dispatching =
        new ThreadLocal<Boolean>() {
          @Override
          protected Boolean initialValue() {
            return false;
          }
        };

    @Override
    void dispatch(Object event, Iterator<Subscriber> subscribers) {
      checkNotNull(event);
      checkNotNull(subscribers);
      Queue<Event> queueForThread = queue.get();
      queueForThread.offer(new Event(event, subscribers));

      if (!dispatching.get()) {
        dispatching.set(true);
        try {
          Event nextEvent;
          while ((nextEvent = queueForThread.poll()) != null) {
            while (nextEvent.subscribers.hasNext()) {
              nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
            }
          }
        } finally {
          dispatching.remove();
          queue.remove();
        }
      }
    }

    private static final class Event {
      private final Object event;
      private final Iterator<Subscriber> subscribers;

      private Event(Object event, Iterator<Subscriber> subscribers) {
        this.event = event;
        this.subscribers = subscribers;
      }
    }
  }

4.3 LegacyAsyncDispatcher

它对在单个全局队列中发布的事件进行排队。 此行为与 AsyncEventBus 的原始行为完全匹配,但在其他方面并不是特别有用,注释上说明了在保证顺序问题上的局限性。

 /** Implementation of a {@link #legacyAsync()} dispatcher. */
  private static final class LegacyAsyncDispatcher extends Dispatcher {

    // This dispatcher matches the original dispatch behavior of AsyncEventBus.
    //
    // We can't really make any guarantees about the overall dispatch order for this dispatcher in
    // a multithreaded environment for a couple reasons:
    //
    // 1. Subscribers to events posted on different threads can be interleaved with each other
    //    freely. (A event on one thread, B event on another could yield any of
    //    [a1, a2, a3, b1, b2], [a1, b2, a2, a3, b2], [a1, b2, b3, a2, a3], etc.)
    // 2. It's possible for subscribers to actually be dispatched to in a different order than they
    //    were added to the queue. It's easily possible for one thread to take the head of the
    //    queue, immediately followed by another thread taking the next element in the queue. That
    //    second thread can then dispatch to the subscriber it took before the first thread does.
    //
    // All this makes me really wonder if there's any value in queueing here at all. A dispatcher
    // that simply loops through the subscribers and dispatches the event to each would actually
    // probably provide a stronger order guarantee, though that order would obviously be different
    // in some cases.

    /** Global event queue. */
    private final ConcurrentLinkedQueue<EventWithSubscriber> queue =
        Queues.newConcurrentLinkedQueue();

    @Override
    void dispatch(Object event, Iterator<Subscriber> subscribers) {
      checkNotNull(event);
      while (subscribers.hasNext()) {
        queue.add(new EventWithSubscriber(event, subscribers.next()));
      }

      EventWithSubscriber e;
      while ((e = queue.poll()) != null) {
        e.subscriber.dispatchEvent(e.event);
      }
    }

    private static final class EventWithSubscriber {
      private final Object event;
      private final Subscriber subscriber;

      private EventWithSubscriber(Object event, Subscriber subscriber) {
        this.event = event;
        this.subscriber = subscriber;
      }
    }
  }

Android EventBus源码解析 带你深入理解EventBus 知道就行,非核心代码,主要用于isRegister的判断。其实不用发布者,订阅者,事件,总线这几个词或许更好理解,以后大家问了EventBus,可以说,就是在一个单例内部维持着一个map对象存储了一堆的方法;到此,我们完整的源码分析就结束了,总结一下:register会把当前类中匹配的方法,存入一个map,而post会根据实参去map查找进行反射调用。好了,到此我们的源码解析就结束了,希望大家不仅能够了解这些优秀框架的内部机理,更能够体会到这些框架的很多细节之处,并发的处理,很多地方,为什么它这么做等等。 阅读详情

相关推荐

菜鸟-需求预测与分仓规划

通过修改模型的损失函数对问题进行求解,比其它单模型的效果好很多。

永不理解_fdu的博客 4882

EventBus3.0源码解析——01.使用方法、@Subscribe

使用方法 //注册事件 EventBus.getDefault().register(Object subscriber); //注销事件 EventBus.getDefault().unregister(Object subscriber); //发送 EventBus.getDefault().post(Object event); //接收 //这里的方法名不是关键所在,关键是发送的O...

boolean的博客 4028

(保姆式教程:从下数据到画图)python如何利用EOF分析SSTA海温异常现象并画图

保姆式教程,教你如何利用python分析1979-2004年的太平洋地区的海温异常SSTA,并且绘制出空间模态和时间序列的图片。

shayuxing的博客 2万+

EventBus:基本使用详解

简介 EventBus项目:https://github.com/greenrobot/EventBus EventBus 3.0.0 API:http://greenrobot.org/files/eventbus/javadoc/3.0/ EventBus是一种用于Android/Java的事件发布-订阅总线框架。 特点: 简化组件之间的通信 分离事件发送者和接收者 很好地处理A...

尘埃zza的博客 2277

Cadence Virtuoso IC617:从零构建传输门与反相器的Schematic设计实战

本文详细介绍了使用Cadence Virtuoso IC617进行传输门与反相器Schematic设计的完整流程,包括环境准备、原理图绘制、Symbol创建、仿真验证及实用技巧。通过分步指导和实战经验分享,帮助初学者快速掌握集成电路设计基础,提升EDA工具应用能力。

weixin_29194693的博客 600

RS485两线制接线实战:从差分信号原理到Modbus避坑指南

本文深入解析RS485两线制接线的核心原理与实战步骤。从差分信号的抗干扰机制出发,详细拆解总线规划、屏蔽层接地、终端电阻安装等关键实操细节,并针对Modbus应用中常见的信号反射、极性接反、共模干扰等问题提供系统化的排查指南与解决方案,助力工程师构建稳定可靠的工业通讯网络。

7up55的博客 734

设计模式-观察者模式eventBus(下)

1、实现方式类型: 同步阻塞是最经典的实现方式,主要是为了代码解耦;异步非阻塞除了能实现代码解耦之外,还能提高代码的执行效率;进程间的观察者模式解耦更加彻底,一般是基于消息队列来实现,用来实现不同进程间的被观察者和观察者之间的交互。 2、异步非阻塞观察者模式的简易实现: 其中一种是:在每个 regSuccess() 函数中创建一个新的线程执行代码逻辑;另一种是:在 UserController 的 register() 函数中使用线程池来执行每个观察者的 handleRe...

Java开发者Peng博客 1258

数据结构实验1.2—高精度计算PI值(西工大)

这是数据结构第二题的题目,是让我们根据用户的需求来计算派的值 这是题目的详细描述,刚看到这道题的时候,我是真的头大,完全没有思路,于是查找了一些大佬的笔记后才开始编写 这是我在写这些代码是所运用到的数学反三角函数幂次展开式的知识。 下面是正确的代码以及我的一些见解,和需要注意的一些地方。希望能对你有所帮助。如有更加精简的算法,请在评论区留下你的算法!!!谢谢!!! #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdl

abcdef0623的博客 5831

JAVA进阶——注解

文章目录 目录 文章目录 前言 一、注解是什么? 1.1注释和注解的区别 二、注解的使用 2.1 如何声明一个注解 2.1.1 元注解 2.1.2 @Target注解 2.1.3 @Retention注解 2.1.4 给注解添加参数 三、注解处理器 3.1 创建自己的注解处理器 3.2 注册注解处理器 3.2.2 自动注册注解处理器 3.3 使用注解处理器 总结 前言 JAVA中的注解我们每个人都用过,通常是以@开头,最常见的就是@Ovrride注解。那么注解到...

bugmaker的博客 878

基于RTL8367S与XS2184的工业级POE交换机硬件设计全解析

本文深入解析基于RTL8367S与XS2184芯片的工业级POE交换机硬件设计,涵盖核心价值、芯片选型、原理图设计、PCB布局及可靠性验证等关键环节。重点探讨双芯片协同工作优势,如RTL8367S的自适应调度算法和XS2184的智能功率分配功能,为工业环境下的稳定供电与数据传输提供专业解决方案。

weixin_42614269的博客 458

AD20/Altium designer——如何进行DRC检查、冲突的错误如何改正

对于一个画完的PCB,我们常常需要进行DRC检查,确保板子的电器连接及制作工艺在设定规则的范围内,本篇将介绍如何对PCB进行后期DRC检查处理,确保电路板出现不必要错误。 1.DRC检查入口 2.DRC设置 3.错误分析 对于错误的内容,依据个人实际情况不同,其出现的原因都是因为与设计规则中的设定标准冲突,这里按遇到的错误进行修改讲解,其他错误同理。 4.常见错误分析及解决方法 (1) 解决方法:将未连接网络连接(...

嵌入式创客工坊的博客 5万+

【EventBus】EventBus源码浅析

简单分析了Eventbus的构造方法,订阅事件、取消订阅,发送Post的源码

摸鱼小小虫的博客 2334

『言善信』Fiddler工具 — 1、Fiddler介绍与安装

文章目录1、Fiddler简介2、Fiddler功能3、Fiddler工作原理4、Fiddler安装 1、Fiddler简介 Fiddler是强大的抓包工具之一,它能记录所有客户端和服务器之间的HTTP和HTTPS请求,是web调试的利器。 Fiddler是位于客户端和服务端之间的HTTP代理,它的原理是以web代理服务器的形式进行工作的,使用的代理地址是:127.0.0.1,端口默认为8888,我们也可以通过设置进行修改。 Fiddler可以抓取支持HTTP代理的任意程序的数据包,如果要抓取https会

繁华似锦Fighting 1473

EventListener与EventBus

EventListener:JDK、Spring @EventListener、Spring ApplicationListener、@EventListener与ApplicationListener对比、JDK和Spring对比; EventBus:Guava EventBus、框架、原理、示例、源码、AsyncEventBus、AllowConcurrentEvents; 拓展:EventListener和EventBus、EventBus对比MQ、Netflix EventBus;

lonelymanontheway的博客 1045

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Android EventBus的使用

参考文章:https://blog.youkuaiyun.com/bskfnvjtlyzmv867/article/details/71480647 使用场景:应用程序内各组件间、组件与后台线程间的通信。 比如请求网络,等网络返回时通过Handler或Broadcast通知UI,两个Fragment之间需要通过Listener通信,这些需求都可以通过EventBus实现。 EventBus的三要素 : ...

NewActivity的博客 1万+

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

EventBus之EventBusBuilder

Configuration EventBusBuilder类配置EventBus的各个方面。例如,下面是如何构建一个EventBus,在发布的事件没有订阅者时保持安静: EventBus eventBus = EventBus.builder() .logNoSubscriberMessages(false) .sendNoSubscriberEvent(false) ...

WindfallSheng的博客 887

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

EventBus解读

### 一。Subscribe注解 ##### 1.1 使用Subscribe注解时,可以根据需求,指定threadMode,sticky,priority三个属性

云梦九章~ 9740

EventBus解析并实现手写EventBus

前言 其实在我们开发当中需要应用组件与后台线程间进行通信,比如在子线程中进行请求数据,当数据请求完毕后通过Handler或者是广播通知UI,而两个Fragment之家可以通过Listener进行通信等等。当我们的项目越来越复杂,使用Intent、Handler、Broadcast进行模块间通信、模块与后台线程进行通信时,代码量大,而且高度耦合;最近公司正在做了一款项目是要求模块化来做,前期考虑是想...

gwz10250330的博客 1115

GWT EventBus

绝大部分开发人员都清楚MVC结构: M - model:代表数据模型 V - view:代表显示层 C - controller:代表逻辑控制层 这种结构降低了M-V-C之间的耦合性,使得模型可以独立于显示层和逻辑层的实现,即系统从struts改到struts 2的时候模型不需要做过多的更改;也使得显示层可以专注于显示,不需要对逻辑有任何了解或者参与,这样的好处是可以让前台开发人员专注于前

广告业的程序员 3825

动手实现一个EventBus

根据Google Guava中的EventBus,动手实现一个简单的EventBus,深入理解其原理。

hefrankeleyn的博客 715

解析EventBus

EventBus是一款针对Android优化的发布-订阅事件总线。它简化了应用程序内各组件间、组件与后台线程间的通信。优点是开销小,代码更优雅,将发送者和接收者解耦。

m0_74000465的博客 1084

EventBus使用

#第一步 引入类库 implementation 'org.greenrobot:eventbus:3.1.1' #第二步 定义事件 public class MessageEvent { public final String message; public MessageEvent(String message) { this.message = mes...

weixin_43966265的博客 186

EventBus原理、实现与Android开发最佳实践

事件总线(EventBus)是软件架构中实现发布-订阅模式的核心组件,通过中央调度器解耦消息发布者与订阅者。其技术原理基于观察者模式,支持跨组件异步通信,在Android开发中尤为常见。主流实现如GreenRobot EventBus通过注解处理器优化性能,提供线程调度和粘性事件等特性。合理使用事件总线能提升代码可维护性,但需注意避免内存泄漏和事件混淆。典型应用场景包括MVVM架构中的全局状态管理、Fragment间通信等,配合RxJava或LiveData可构建更健壮的响应式系统。本文重点解析EventB

weixin_34280237的博客 444

java guava eventbus_Guava之EventBus使用总结

1.将EventBus封装为单例模式使用1 packagepriv.jack.demo.listener;23 importjava.util.Map;45 importcom.google.common.collect.Maps;6 importcom.google.common.eventbus.EventBus;89 /**10 * 事件总线工厂11 * 将EventBus封装为单例模式使用...

weixin_42312227的博客 552

EventBus、Otto

Android工程搭建A、EventBus介绍B、EventBus三大要素C、EventBus四种线程模型D、代码 A、EventBus介绍 EventBus能够简化各组件间的通信,让我们的代码书写变得简单,能有效的分离事件发送方和接收方(也就是解耦的意思)。 B、EventBus三大要素 Event 事件。它可以是任意类型。 Subscriber 事件订阅者。 Publisher 事件的发布者...

XinHao_Sir的博客 371
上一篇: 平衡二叉树AVL及其旋转
下一篇: 队列总结(一)
MrMoving
博客等级 码龄10年 54粉丝 145原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值