Eventbus 3.3.1源码分析
EventBus
EventBus is a publish/subscribe event bus for Android and Java.
EventBus是适用于Android和Java的发布/订阅事件总线。
优点:
- 简化组件之间的通信
- 将事件发送方和接收方解耦
- 在活动、片段和后台线程中表现良好
- 避免复杂且容易出错的依赖关系和生命周期问题
- 使代码更简单
- 速度快
- 很小(~60k jar)
- 在实践中被安装量超过 1,000,000,000 的应用证明
- 具有高级功能,如交付线程,订阅者优先级等。
1 简单使用
1.1 定义事件
class MessageEvent(val msg:String)
1.2 定义订阅
class HomeActivity : AppCompatActivity() {override fun onStart() {super.onStart()EventBus.getDefault().register(this)}override fun onStop() {super.onStop()EventBus.getDefault().unregister(this)}@Subscribe(threadMode = ThreadMode.MAIN)fun onMessageEvent(messageEvent: MessageEvent){val msg = messageEvent.msgwhen(msg){"day"->{//...}}}}
1.3 发送事件
EventBus.getDefault().post(MessageEvent("day"))
2 主要原理
2.1 角色
-
Event(事件):自定义事件,EventBus会根据事件的类型全局发送
-
Subscriber(订阅者):主要指需要接收事件的 Activitys 和 Fragments 。在 Activitys 和 Fragments 的生命周期中订阅/注销订阅,接收事件的方法以注解
@subscribe
进行标示,同时指定线程模型,线程模型默认为POSTING
的方式。 -
Publisher(发布者):事件的发布者。可以在任意线程中发布事件。利用
EventBus.getDefault()
获取一个全局静态对象EventBus,再通过链式编程调用post()将不同类型的事件发送。
2.2 线程模型
ThreadMode(线程模型)描述事件订阅者和事件发布者所在线程的关系。
ThreadMode主要有5种模式:POSTING、MAIN、MAIN_ORDERED、BACKGROUND、ASYNC(异步)
-
POSTING
事件订阅的默认线程。即事件订阅和事件发布在同一个线程。避免了切换线程的开销。对于已知在非常短的时间内完成而不需要主线程的简单任务,这是推荐的模式。使用此模式的事件处理程序必须快速返回,以避免阻塞可能是主线程的发布线程。
-
MAIN
事件处理程序方法在Android 的主线程(UI线程)被调用。使用该模式的订阅器必须快速执行, 避免阻塞主线程
-
如果发布线程是主线程,则直接调用事件处理方法(即上方的onMessageEvent()),此时为同步调用,阻塞主线程,与POSTING效果一样
-
否则,事件将排队等待传递(非阻塞)。
-
-
MAIN_ORDERED
事件处理程序方法在Android 的主线程(UI线程)被调用。与MAIN模式不同,事件将始终排队等待传递(非阻塞)。
使用该模式的订阅器也必须快速执行, 避免阻塞主线程。
MAIN与MAIN_ORDERED线程模式区别:
MAIN :事件处理程序中发布另一个事件,则第二个事件处理程序将在第一个事件处理程序之前完成(因为它是同步调用的)。
MAIN_ORDERED:第一个事件处理程序将完成,然后第二个事件处理程序将在稍后的时间点(一旦主线程具有容量)被调用。
-
BACKGROUND
事件处理程序方法在后台线程被调用。
-
发布线程不是主线程,直接在发布线程调用事件处理方法
-
发布线程是主线程,EventBus 将使用单个后台线程,该线程将按顺序传递其所有事件。使用此模式的事件处理程序应尝试快速返回,以避免阻塞后台线程。
-
-
ASYNC(异步)
事件处理程序方法在单独的线程中调用。始终独立于发布线程和主线程。直接在单独的线程调用事件处理程序方法,不等待。
如果事件处理程序方法的执行可能需要一些时间(例如,用于网络访问),则应使用此模式。避免同时触发大量长时间运行的异步处理程序方法来限制并发线程数。EventBus 使用线程池来有效地重用已完成的异步事件处理程序通知中的线程。
2.3 事件类型
-
普通事件
普通事件是指已有的订阅者能够收到发送的事件,在事件发送之后注册的事件接收者将无法收到事件。
//发送普通事件 EventBus.getDefault().post(new MessageEvent("day"));
-
粘性事件
粘性事件是指,不管是在事件发送之前注册的事件接收者还是在事件发送之后注册的事件接收者都能够收到事件。默认为普通事件。
//发送粘性事件 EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));//事件处理 @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void onEvent(MessageEvent event) { textField.setText(event.message); }
事件优先级
订阅者优先级以影响事件传递顺序。在同一传递线程ThreadMode
中,优先级较高的订阅者将在优先级较低的其他订阅者之前接收事件。默认优先级为0
。
注意:优先级不影响具有不同ThreadMode
的订阅服务器之间的传递顺序
//定义事件接收的优先级
@Subscribe(priority = 1);
public void onEvent(MessageEvent event) {...
}
3 源码解析
1 初始化 getDafault()
public class EventBus {static volatile EventBus defaultInstance;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;}
}
采用单例模式的DCL式创建全局单例对象EventBus
EventBus的构造函数:
//EventBus.java
public class EventBus {//EventBuilder对象,初始化其内部属性private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();//事件类型缓存//key-事件Class对象,value-事件父类型及本类型private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();//根据不同事件类型存储订阅者: subscriptionsByEventType//key-事件Class对象,value-订阅者集合private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;//不同订阅者的事件集合: typesBySubscriber//key-具体订阅者对象,value-事件Class集合private final Map<Object, List<Class<?>>> typesBySubscriber;//粘性事件集合: stickyEvents//key-事件Class对象,value-具体事件对象private final Map<Class<?>, Object> stickyEvents;//主线程事件发送支持(含判断是否主线程,创建主线程事件发送者)private final MainThreadSupport mainThreadSupport;private final Poster mainThreadPoster;//主线程事件发送者private final BackgroundPoster backgroundPoster;//后台线程事件发送者private final AsyncPoster asyncPoster;//异步线程事件发送者//订阅事件查找:根据订阅者Class,找到订阅的事件集合private final SubscriberMethodFinder subscriberMethodFinder;private final ExecutorService executorService;public EventBus() {//传入静态EventBuilder对象this(DEFAULT_BUILDER);}EventBus(EventBusBuilder builder) {//日志logger = builder.getLogger(); subscriptionsByEventType = new HashMap<>(); typesBySubscriber = new HashMap<>();stickyEvents = new ConcurrentHashMap<>();mainThreadSupport = builder.getMainThreadSupport(); mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null; backgroundPoster = new BackgroundPoster(this); asyncPoster = new AsyncPoster(this); indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,builder.strictMethodVerification, builder.ignoreGeneratedIndex);logSubscriberExceptions = builder.logSubscriberExceptions;logNoSubscriberMessages = builder.logNoSubscriberMessages;sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;sendNoSubscriberEvent = builder.sendNoSubscriberEvent;throwSubscriberException = builder.throwSubscriberException;eventInheritance = builder.eventInheritance;executorService = builder.executorService;}//...
}
2 注册订阅 register(Object subscriber)
//EventBus.javapublic void register(Object subscriber) {//1. 反射获得订阅者的Class对象Class<?> subscriberClass = subscriber.getClass();//2. 通过subscriberMethodFinder找到订阅者所订阅的事件集合List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);synchronized (this) {for (SubscriberMethod subscriberMethod : subscriberMethods) {//3. 遍历集合进行注册subscribe(subscriber, subscriberMethod);}}}
- SubscriberMethodFinder.findSubscriberMethods(subscriberClass):根据订阅者Class找到订阅者订阅的事件集合
//SubscriberMethodFinder.javaprivate static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {//1. 从缓存获取List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);if (subscriberMethods != null) {return subscriberMethods;}//2. 获取订阅者自定义的订阅方法集合if (ignoreGeneratedIndex) {//a. findUsingReflectionsubscriberMethods = findUsingReflection(subscriberClass);} else {//b. findUsingInfosubscriberMethods = findUsingInfo(subscriberClass);}if (subscriberMethods.isEmpty()) {throw new EventBusException("Subscriber " + subscriberClass+ " and its super classes have no public methods with the @Subscribe annotation");} else {//3. 放入缓存METHOD_CACHE.put(subscriberClass, subscriberMethods);return subscriberMethods;}}
a. findUsingReflection
//SubscriberMethodFinder.javaprivate List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {FindState findState = prepareFindState();findState.initForSubscriber(subscriberClass);while (findState.clazz != null) {//利用反射获取订阅方法及注释属性findUsingReflectionInSingleClass(findState);findState.moveToSuperclass();}return getMethodsAndRelease(findState);}
b. findUsingInfo
//SubscriberMethodFinder.javaprivate List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {// 1. 从数组中获取FindState对象// 如果有直接返回,如果没有创建一个新的FindState对象FindState findState = prepareFindState();// 2. 根据事件订阅者初始化findStatefindState.initForSubscriber(subscriberClass);while (findState.clazz != null) {// 3. 获取subscriberInfo,初始化为nullfindState.subscriberInfo = getSubscriberInfo(findState);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 {// 4. 通过反射的方式获取订阅者中的MethodfindUsingReflectionInSingleClass(findState);}findState.moveToSuperclass();}return getMethodsAndRelease(findState);}// 从findState中获取订阅者所有方法并释放private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {// 获取订阅者所有订阅方法集合List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);// findState进行回收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;}}}return subscriberMethods;}
两个方法最后都会调用findUsingReflectionInSingleClass:利用反射获取订阅方法的注释属性
private void findUsingReflectionInSingleClass(FindState findState) {Method[] methods;try {// 1. 订阅者中所有声明的方法,放入数组中methods = findState.clazz.getDeclaredMethods();} catch (Throwable th) {// 2. 获取订阅者中声明的public方法,设置跳过父类methods = findState.clazz.getMethods();findState.skipSuperClasses = true;}for (Method method : methods) {// 3. 获取方法的修饰符:public、private等等int modifiers = method.getModifiers();// 4. 订阅方法为public同时不是abstract、staticif ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {//方法参数类型数组Class<?>[] parameterTypes = method.getParameterTypes();if (parameterTypes.length == 1) {// 5. 获取方法的注解Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);if (subscribeAnnotation != null) {Class<?> eventType = parameterTypes[0];// 6. 将method和eventType放入到findState进行检查if (findState.checkAdd(method, eventType)) {//7. 获取注解中的threadMode对象ThreadMode threadMode = subscribeAnnotation.threadMode();//8. 新建一个SubscriberMethod对象,同时加入到findState中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");} }}
SubscriberMethod
//SubscriberMethod.java
public class SubscriberMethod {final Method method;//实际事件处理方法对象final ThreadMode threadMode;//线程模式final Class<?> eventType;//事件类型final int priority;//优先级final boolean sticky;//粘性事件String methodString;
}
Subscription
final class Subscription {final Object subscriber;final SubscriberMethod subscriberMethod;
}
- 订阅者进行注册:subscribe(Object subscriber, SubscriberMethod subscriberMethod)
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {//1. 获取事件类型Class<?> eventType = subscriberMethod.eventType;//2. 将订阅者和订阅方法封装成Subscription对象Subscription newSubscription = new Subscription(subscriber, subscriberMethod);//3. 通过事件类型获取该事件的订阅者集合CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);if (subscriptions == null) {// 如果没有订阅者订阅该事件,创建集合,存入subscriptionsByEventType集合中subscriptions = new CopyOnWriteArrayList<>();subscriptionsByEventType.put(eventType, subscriptions);} else {// 如果有订阅者已经订阅了该事件,判断这些订阅者中是否有重复订阅的现象if (subscriptions.contains(newSubscription)) {throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType);}}int size = subscriptions.size();//4. 遍历该事件的所有订阅者for (int i = 0; i <= size; i++) {// 按照优先级高低进行插入,如果优先级最低,插入到集合尾部if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {subscriptions.add(i, newSubscription);break;}}//5. 获取该事件订阅者订阅的所有事件集合,将该事件加入到集合中List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {subscribedEvents = new ArrayList<>();typesBySubscriber.put(subscriber, subscribedEvents);}subscribedEvents.add(eventType);//6. 判断该事件是否是粘性事件if (subscriberMethod.sticky) {if (eventInheritance) {//判断事件的继承性,默认是不可继承//获取所有粘性事件并遍历,判断继承关系Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();for (Map.Entry<Class<?>, Object> entry : entries) {Class<?> candidateEventType = entry.getKey();if (eventType.isAssignableFrom(candidateEventType)) {Object stickyEvent = entry.getValue();//7. 调用checkPostStickyEventToSubscription方法checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}} else {Object stickyEvent = stickyEvents.get(eventType);checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}}
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {if (stickyEvent != null) {postToSubscription(newSubscription, stickyEvent, isMainThread());}}//根据ThreadMode线程模式不同,来处理事件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);}}
void invokeSubscriber(PendingPost pendingPost) {Object event = pendingPost.event;Subscription subscription = pendingPost.subscription;PendingPost.releasePendingPost(pendingPost);if (subscription.active) {invokeSubscriber(subscription, event);}}void invokeSubscriber(Subscription subscription, Object event) {try {//利用反射调用实际的处理事件方法subscription.subscriberMethod.method.invoke(subscription.subscriber, event);} catch (InvocationTargetException e) {handleSubscriberException(subscription, event, e.getCause());} catch (IllegalAccessException e) {throw new IllegalStateException("Unexpected exception", e);}}
订阅事件步骤
- 根据单例设计模式创建一个
EventBus
对象,同时创建一个EventBus.Builder
对象对EventBus
进行初始化 - 调用
register
方法,首先通过反射获取到订阅者的Class
对象。 - 通过
SubscriberMethodFinder
对象获取订阅者中所有订阅的事件集合,它先从缓存中获取;如果缓存中没有,通过反射的方式去遍历订阅者内部被注解的方法,将这些方法放入到集合中进行返回。 - 遍历第三步获取的集合,将订阅者和事件进行绑定。
- 在绑定之后会判断绑定的事件是否是粘性事件,如果是粘性事件,直接调用
postToSubscription
方法,将之前发送的粘性事件发送给订阅者。
3 发送事件
3.1 post(Object event)
//EventBus.javapublic void post(Object event) {// 1、获取当前线程的PostingThreadStatePostingThreadState postingState = currentPostingThreadState.get();// 2、当前线程的事件集合List<Object> eventQueue = postingState.eventQueue;// 3、将要发送的事件加入到集合中eventQueue.add(event);if (!postingState.isPosting) {// 判断是否是主线程postingState.isMainThread = isMainThread();postingState.isPosting = true;if (postingState.canceled) {throw new EventBusException("Internal error. Abort state was not reset");}try {// 4、只要事件集合中还有事件,就一直发送while (!eventQueue.isEmpty()) {postSingleEvent(eventQueue.remove(0), postingState);}} finally {postingState.isPosting = false;postingState.isMainThread = false;}}}
currentPostingThreadState是存储PostingThreadState的ThreadLocal对象
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据, 并且线程之间的数据是相互独立的。其内部通过创建一个它包裹的泛型对象的数组,不同的线程对应不同的数组索引,每个线程通过get方法获取对应的线程数据。
如果一个变量只要被某个线程独享,可以通过java.lang.ThreadLocal类来实现线程本地存储的功能。每一个线程的Thread对象中都有一个ThreadLocalMap对象,这个对象存储了一组以ThreadLocal.threadLocalHashCode为键,以本地线程变量为值的K-V值对,ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在线程K-V值对中找回对应的本地线程变量。
//EventBus.javaprivate final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {@Overrideprotected PostingThreadState initialValue() {return new PostingThreadState();}};// 每个线程中存储的数据final static class PostingThreadState {final List<Object> eventQueue = new ArrayList<>();// 线程的事件队列boolean isPosting;//是否正在发送中boolean isMainThread;//是否主线程Subscription subscription;//事件订阅者和订阅事件的封装Object event;//事件对象boolean canceled;//是否取消}
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {//1. 获取事件的Class对象Class<?> eventClass = event.getClass();boolean subscriptionFound = false;if (eventInheritance) {//2. 找到当前的event的所有 父类和实现的接口 的class集合List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);int countTypes = eventTypes.size();for (int h = 0; h < countTypes; h++) {Class<?> clazz = eventTypes.get(h);//3. 遍历集合发送单个事件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);}if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&eventClass != SubscriberExceptionEvent.class) {post(new NoSubscriberEvent(this, event));}}
}
private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {synchronized (eventTypesCache) {//获取事件集合List<Class<?>> eventTypes = eventTypesCache.get(eventClass);if (eventTypes == null) {eventTypes = new ArrayList<>();Class<?> clazz = eventClass;while (clazz != null) {//添加事件eventTypes.add(clazz);//添加当前事件的接口classaddInterfaces(eventTypes, clazz.getInterfaces());// 获取当前事件的父类clazz = clazz.getSuperclass();}eventTypesCache.put(eventClass, eventTypes);}return eventTypes;}}//循环添加当前事件的接口classstatic void addInterfaces(List<Class<?>> eventTypes, Class<?>[] interfaces) {for (Class<?> interfaceClass : interfaces) {if (!eventTypes.contains(interfaceClass)) {eventTypes.add(interfaceClass);addInterfaces(eventTypes, interfaceClass.getInterfaces());}}}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {CopyOnWriteArrayList<Subscription> subscriptions;synchronized (this) {//1. 根据事件获取所有订阅它的订阅者subscriptions = subscriptionsByEventType.get(eventClass);}if (subscriptions != null && !subscriptions.isEmpty()) {//遍历集合for (Subscription subscription : subscriptions) {postingState.event = event;postingState.subscription = subscription;boolean aborted;try {//2. 将事件发送给订阅者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;
}
根据订阅方法的线程模式调用订阅方法
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 {// temporary: technically not correct as poster not decoupled from subscriberinvokeSubscriber(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);}}
发布事件步骤
- 获取当前线程的事件集合,将要发送的事件加入到集合中。
- 通过循环,只要事件集合中还有事件,就一直发送。
- 获取事件的
Class
对象,找到当前的event
的所有父类和实现的接口的class
集合。遍历这个集合,调用发送单个事件的方法进行发送。 - 根据事件获取所有订阅它的订阅者集合,遍历集合,将事件发送给订阅者。
- 发送给订阅者时,根据订阅方法的线程模式调用订阅方法,如果需要线程切换,则切换线程进行调用;否则,直接调用。
3.2 postSticky(Object event)
public void postSticky(Object event) {synchronized (stickyEvents) {stickyEvents.put(event.getClass(), event);}post(event);}
1、将粘性事件加入到EventBus
对象的粘性事件集合中,当有新的订阅者进入后,如果该订阅者订阅了该粘性事件,可以直接发送给订阅者。
2、将粘性事件发送给已有的事件订阅者。
4 取消注册 unregister(Object subscriber)
public synchronized void unregister(Object subscriber) {//1. 获取订阅者订阅的所有事件List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);if (subscribedTypes != null) {// 2. 遍历集合for (Class<?> eventType : subscribedTypes) {// 3. 将该订阅者的从订阅该事件的所有订阅者集合中移除unsubscribeByEventType(subscriber, eventType);}// 4. 将订阅者从集合中移除typesBySubscriber.remove(subscriber);} else {logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());}}private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {// 获取该事件的所有订阅者List<Subscription> 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--;}}}}
1、获取订阅者的所有订阅方法,遍历这些方法。然后拿到每个方法对应的所有订阅者集合,将订阅者从集合中移除。
2、移除订阅者中所有的订阅方法。
5 线程切换
根据订阅方法的线程模式调用订阅方法
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 {// temporary: technically not correct as poster not decoupled from subscriberinvokeSubscriber(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);}}
5.1 mainThreadPoster
mainThreadPoster
是EventBus
进行初始化时创建的
EventBus(EventBusBuilder builder) {mainThreadSupport = builder.getMainThreadSupport(); mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;}
主线程事件发送支持:MainThreadSupport
public interface MainThreadSupport {boolean isMainThread();Poster createPoster(EventBus eventBus);
}
发送者接口(Poster)
public interface Poster {void enqueue(Subscription subscription, Object event);
}
MainThreadSupport getMainThreadSupport() {if (mainThreadSupport != null) {return mainThreadSupport;} else if (AndroidComponents.areAvailable()) {return AndroidComponents.get().defaultMainThreadSupport;} else {return null;}}
通过AndroidComponents.get().defaultMainThreadSupport获得mainThreadPoster,AndroidComponents是一个抽象类,实现类是AndroidComponentsImpl
public abstract class AndroidComponents {private static final AndroidComponents implementation;static {implementation = AndroidDependenciesDetector.isAndroidSDKAvailable()? AndroidDependenciesDetector.instantiateAndroidComponents(): null;}public static boolean areAvailable() {return implementation != null;}public static AndroidComponents get() {return implementation;}public final Logger logger;public final MainThreadSupport defaultMainThreadSupport;public AndroidComponents(Logger logger, MainThreadSupport defaultMainThreadSupport) {this.logger = logger;this.defaultMainThreadSupport = defaultMainThreadSupport;}
}
AndroidComponentsImpl里面又创建了DefaultAndroidMainThreadSupport
public class AndroidComponentsImpl extends AndroidComponents {public AndroidComponentsImpl() {super(new AndroidLogger("EventBus"), new DefaultAndroidMainThreadSupport());}
}
mainThreadPoster最终其实返回的是HandlerPoster对象
public class DefaultAndroidMainThreadSupport implements MainThreadSupport {@Overridepublic boolean isMainThread() {return Looper.getMainLooper() == Looper.myLooper();}@Overridepublic Poster createPoster(EventBus eventBus) {return new HandlerPoster(eventBus, Looper.getMainLooper(), 10);}
}
HandlerPoster
HandlerPoster继承Handler并实现了Poster接口
public class HandlerPoster extends Handler implements Poster {private final PendingPostQueue queue;private final int maxMillisInsideHandleMessage;private final EventBus eventBus;private boolean handlerActive;public 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) {//将事件封装成PendingPostPendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);synchronized (this) {//将事件入队queue.enqueue(pendingPost);if (!handlerActive) {handlerActive = true;//调用sendMessage向主线程发送if (!sendMessage(obtainMessage())) {throw new EventBusException("Could not send handler message");}}}}@Overridepublic void handleMessage(Message msg) {boolean rescheduled = false;try {long started = SystemClock.uptimeMillis();while (true) {PendingPost pendingPost = queue.poll();if (pendingPost == null) {synchronized (this) {// Check again, this time in synchronizedpendingPost = 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;}}
}
5.2 backgroundPoster
public class EventBus {private final BackgroundPoster backgroundPoster;private final ExecutorService executorService;//线程池EventBus(EventBusBuilder builder) {backgroundPoster = new BackgroundPoster(this); executorService = builder.executorService;}
}public class EventBusBuilder {private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
}public class Executors {public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}
}
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);}}}@Overridepublic void run() {try {try {while (true) {PendingPost pendingPost = queue.poll(1000);if (pendingPost == null) {synchronized (this) {// Check again, this time in synchronizedpendingPost = 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;}}}
5.3 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);}@Overridepublic void run() {PendingPost pendingPost = queue.poll();if(pendingPost == null) {throw new IllegalStateException("No pending post available");}eventBus.invokeSubscriber(pendingPost);}}
asyncPoster原理还是线程池,不过它是在单独的线程中调用
线程切换总结
线程切换主要分析了三个Poster,他们之间的原理区别:
- mainThreadPoster:利用Handler发送信息到主线程。
- backgroundPoster:利用线程池和事件队列实现,EventBus 将使用单个后台线程,该线程将按顺序传递其所有事件。
- asyncPoster:利用线程池,在单独的线程中回调事件处理
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 典型相关分析案例
目录 题目概要 城市竞争力与基础设施的典型性相关分析 摘要 一、变量说明 二、典型相关系数及其检验 2.1计算典型相关系数并对其进行检验 三、建立典型相关模型 四、典型结构分析 题目概要 利用典型性相关分析法分析城市竞争力与基础设施与基础设施的相关性。 相关数据…...
2024/4/14 9:18:11 - day 1 - day 4
文章目录前言一、day 1 : JAVA概述与环境相关1、java常用DOS命令及快捷键2、安装JDK及环境配置1、JDK、JRE、JVM之间的关系2、JAVA加载和执行过程3、为什么要配置环境变量?如何配置?二、day 2 : 变量、数据类型与运算、标识符1、变量2、数据类型3、运算符…...
2024/4/19 15:31:55 - 2022-01-22每日刷题打卡
2022-01-22每日刷题打卡 一本通 1213:八皇后问题 【题目描述】 在国际象棋棋盘上放置八个皇后,要求每两个皇后之间不能直接吃掉对方。 【输入】 (无) 【输出】 按给定顺序和格式输出所有八皇后问题的解(见样例)。 【输入…...
2024/4/14 9:18:01 - JPA 在多模块中无法找到对应的 Bean
Spring JPA 在多模块项目中的使用问题(一) 具体的项目结构如下图所示: 其中,score-application 层为应用层,用于定义实际的操作;score-domain 表示领域层,用于处理业务之间的关联关系&#x…...
2024/4/17 22:26:33 - 如何正确pip install pyscipopt
1、看错误日志,如果出现了: error: Microsoft Visual C 14.0 is required 看这篇博文解决方案,下载对应的版本就好了(注意:要细看评论区的内容) 我选择了百度网盘下载 Microsoft Visual C 15.0。 网盘链接…...
2024/4/27 14:42:29 - 牛客小测验(C语言/C++)总结 第二弹(值得一看
1. a3*5 , a*4; 最终a为60 是否正确? 错误❌ 解析:a3*5,a*4; 首先执行第一步:a3*515 成功给a赋值为15;再计算a*460;但这一步并未牵扯赋值运算!所以a的值仍未15。但如果将题目改为:b(a3*5,a*4);…...
2024/4/14 15:07:49 - 算法入个门——动态规划
导语 Hello大家好,这里是算法入个门系列 作者就是想通过算法入个门系列与大家一同探讨如何理解并掌握常用算法,对,这个就是开设这个专栏要达到的目标,共勉之 引言 相信大家LeetCode也刷了有一段时间了,但可能存在一…...
2024/4/17 16:51:46 - day03-流程控制
day03-流程控制 流程控制语句:Java提供了一些流程控制语句,来控制程序的执行流程 提供了三种控制形式 顺序结构:程序默认 分支结构:if、switch 循环结构:for、while、do while java-分支结构 if语句 Switch语句…...
2024/4/20 17:06:49 - 行为型模式 —— 命令模式
命令模式 1 定义 将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开,这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。 2 结构 命令模式包含以下角色: 抽象命令类角…...
2024/4/14 9:18:06 - 某度网盘不限速下载,这里有个新方法~
今天,给大家分享一个方法,即使你不是百度网盘VIP,也可拥有会员的下载速度。 某度网盘不限速下载方法 速度测试效果如下: 适用手机:安卓 使用方法: 1、安卓手机下载百度网盘APP,然后选择需要下载…...
2024/4/17 15:21:58 - React 调度(Scheduler)原理理解
文章目录异步调度时间分片异步调度原理总结异步调度 问题:由于对于大型的 React 应用,会存在一次更新,递归遍历大量的虚拟 DOM ,造成占用 js 线程,使得浏览器没有时间去做一些动画效果,伴随项目越来越大&am…...
2024/4/28 21:54:43 - 《数据结构》9.1.4 时间复杂度下界
因为该序列有9个逆序对,所以冒泡排序和插入排序都需要交换操作9次...
2024/4/14 9:18:37 - 【VS 2022】未能加载文件或程序集“sapnco”或它的某一个依赖项。试图加载格式不正确的程序。
将开发工具 VS 2019 升级 到 VS 2022 之后,原来 的 MVC 3.0 项目启动的时候报错: 未能加载文件或程序集“sapnco”或它的某一个依赖项。试图加载格式不正确的程序。 网上找了半天的解决办法,无果........ 最后,总结了一下网友的分…...
2024/4/14 9:19:12 - 人工智能学习笔记一之强化学习(Q-learning)
强化学习RF简介 强化学习是机器学习中的一种重要类型,一个其中特工通过 执行操作并查看查询查询结果来学习如何在环境中表现行为。 机器学习算法可以分为3种:有监督学习(Supervised Learning)、无监督学习(Unsupervi…...
2024/4/14 9:19:02 - 每日一题- 剑指 Offer 13. 机器人的运动范围
目录 题目描述 示例 1: 思想:回溯 代码 题目描述 地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外&…...
2024/4/14 9:18:52 - 邮件的发送和接收实现
邮件的发送和接收流程 发送方通过SMTP协议连接到网易的服务器,向网易的服务器发送一封邮件,网易收到后通过SMTP协议转交给qq的smtp服务器,qq的smtp服务器把邮件存储到这个接收者的账号中,接收者通过POP3协议连接qq的POP3服务器&a…...
2024/4/20 2:29:23 - Spring IoC 扩展特性
目录一、lazy-init 延迟加载1、定义1)原本默认的配置2)延迟加载的配置3)全局配置4)注意事项2、应用场景二、FactoryBean 和 BeanFactory1、BeanFactory2、FactoryBean例子:三、后置处理器1、两者区别2、BeanPostProces…...
2024/4/19 12:45:18 - uWSGI 未授权访问漏洞
漏洞简介 uWSGI是一款Web应用程序服务器,它实现了WSGI、uwsgi和http等协议,并支持通过插件来运行各种语言,通常被用于运行Python WEB应用。uwsgi除了是应用容器的名称之外,它和Fastcgi之类的一样,也是前端server与后端…...
2024/4/21 0:01:14 - 图解HTTP学习笔记——第五章
与 HTTP 协作的 Web 服务器 5.1 用单台虚拟主机实现多个域名 HTTP/1.1 规范允许一台 HTTP 服务器搭建多个 Web 站点。 这是利用了虚拟主机(Virtual Host, 又称虚拟服务器) 的功能。即使物理层面只有一台服务器, 但只要使用虚拟主…...
2024/4/19 13:21:36 - Spring IoC 源码刨析
目录一、源码前准备1、刨析源码的好处2、源码刨析原则1)定焦原则2)宏观原则3、读源码的方法和技巧1)断点2)反调3)经验4、Spring 源码构建5、Spring IoC 容器体系6、Bean 生命周期关键时机点1)LagouBean 类2…...
2024/4/16 22:00:36
最新文章
- 开了个新店!
大家好,我是麦鸽。 一言难尽,五一之前,把大A里的钱都提出来了,又整了一个新的小店。熟悉我的老读者应该都知道,我主业是做嵌入式的,后面慢慢转了技术管理的路线。平时也搞点副业,餐饮店就是其中…...
2024/5/4 14:09:12 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - llama.cpp运行qwen0.5B
编译llama.cp 参考 下载模型 05b模型下载 转化模型 创建虚拟环境 conda create --prefixD:\miniconda3\envs\llamacpp python3.10 conda activate D:\miniconda3\envs\llamacpp安装所需要的包 cd G:\Cpp\llama.cpp-master pip install -r requirements.txt python conver…...
2024/5/1 13:25:36 - 设计模式:组合模式
定义 组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端可以统一对待单个对象和组合对象。 应用场景 组合模式适用于以下场景: 表达对象的部分-整体层次结构:当你想要表示对象的部分-整…...
2024/5/3 22:02:39 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/5/1 17:30:59 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/5/2 16:16:39 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/4/29 2:29:43 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/5/3 23:10:03 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/4/27 17:58:04 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/4/27 14:22:49 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/4/28 1:28:33 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/4/30 9:43:09 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/4/27 17:59:30 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/5/2 15:04:34 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/4/28 1:34:08 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/4/26 19:03:37 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/4/29 20:46:55 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/4/30 22:21:04 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/5/1 4:32:01 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/5/4 2:59:34 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/4/28 5:48:52 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/4/30 9:42:22 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/5/2 9:07:46 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/4/30 9:42:49 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57