转载

Android EventBus源码解析

项目地址 :https://github.com/greenrobot/EventBus

这个项目个人感觉就是为了解决回调事件过多的,比方说A函数在做完以后 要调用b类的c函数,那我们通常的做法就是 定义一个接口 然后再A函数所属的类里面注册这个接口。

然后a函数做完以后 直接调用这个接口即可。但是这种方法写多了以后确实很麻烦,于是EventBus就是用来解决这种场景的。

和以往一样,我们只解析他的源码,如果你要学习他的用法请自行谷歌。

我们就从register函数开始说起。

1  private synchronized void register(Object subscriber, boolean sticky, int priority) { 2         //这个list就是方法的集合 3         List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass()); 4         //这个循环的目的就是为了用  方法对象 来构造 Subscription对象 5         for (SubscriberMethod subscriberMethod : subscriberMethods) { 6             subscribe(subscriber, subscriberMethod, sticky, priority); 7         } 8     }

首先来看一下SubscriberMethod这个类,

 1 /*  2  * Copyright (C) 2012 Markus Junginger, greenrobot (http://greenrobot.de)  3  *  4  * Licensed under the Apache License, Version 2.0 (the "License");  5  * you may not use this file except in compliance with the License.  6  * You may obtain a copy of the License at  7  *  8  *      http://www.apache.org/licenses/LICENSE-2.0  9  * 10  * Unless required by applicable law or agreed to in writing, software 11  * distributed under the License is distributed on an "AS IS" BASIS, 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13  * See the License for the specific language governing permissions and 14  * limitations under the License. 15  */ 16 package de.greenrobot.event; 17  18 import java.lang.reflect.Method; 19  20 import android.util.Log; 21  22 /** 23  * 这个类就是描述方法用的 24  */ 25 final class SubscriberMethod { 26     final Method method; 27     final ThreadMode threadMode; 28     final Class<?> eventType; 29     /** Used for efficient comparison */ 30     /** 31      * 这个methodString 实际上就是用来描述SubscriberMethod对象的,尤其是在重写的equals方法里 起到关键的作用 32      * 就类似于这种结构 33      * com.example.administrator.eventbustest.ItemDetailFragment#onEventMainThread(com.example.administrator.eventbustest.Item 34      *其实也很好理解就是 包名+类名#方法名(参数的类型 35      * 注意这里参数的类型也是全路径名 包名+类名 36      */ 37     String methodString; 38  39     SubscriberMethod(Method method, ThreadMode threadMode, Class<?> eventType) { 40         this.method = method; 41         this.threadMode = threadMode; 42         this.eventType = eventType; 43     } 44  45     @Override 46     public boolean equals(Object other) { 47         if (other instanceof SubscriberMethod) { 48             checkMethodString(); 49             SubscriberMethod otherSubscriberMethod = (SubscriberMethod) other; 50             otherSubscriberMethod.checkMethodString(); 51             // Don't use method.equals because of http://code.google.com/p/android/issues/detail?id=7811#c6 52             return methodString.equals(otherSubscriberMethod.methodString); 53         } else { 54             return false; 55         } 56     } 57  58     private synchronized void checkMethodString() { 59         if (methodString == null) { 60             // Method.toString has more overhead, just take relevant parts of the method 61             StringBuilder builder = new StringBuilder(64); 62             builder.append(method.getDeclaringClass().getName()); 63             builder.append('#').append(method.getName()); 64             builder.append('(').append(eventType.getName()); 65             methodString = builder.toString(); 66         } 67     } 68  69     @Override 70     public int hashCode() { 71         return method.hashCode(); 72     } 73 }

然后我们来看看这个SubscriberMethod对象组成的集合 是怎么构造出来的。

 1 List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {  2         String key = subscriberClass.getName();  3         List<SubscriberMethod> subscriberMethods;  4         synchronized (methodCache) {  5             subscriberMethods = methodCache.get(key);  6         }  7         8         if (subscriberMethods != null) {  9             return subscriberMethods; 10         } 11         subscriberMethods = new ArrayList<SubscriberMethod>(); 12         Class<?> clazz = subscriberClass; 13         HashSet<String> eventTypesFound = new HashSet<String>(); 14         StringBuilder methodKeyBuilder = new StringBuilder(); 15         while (clazz != null) { 16             String name = clazz.getName(); 17             //这个地方判断如果是这些类,那么就直接跳出这个while循环 18             //注意name的值 也是包名+类名,所以这里就是过滤掉基础的sdk的那些方法 19             //如果你有引用其他公共lib库的话 你也可以过滤他们的包, 20             if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) { 21                 // Skip system classes, this just degrades performance 22                 break; 23             } 24  25             // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again) 26             Method[] methods = clazz.getDeclaredMethods(); 27             for (Method method : methods) { 28                 String methodName = method.getName(); 29                 Log.e("burning", "methodName == " + methodName); 30                 //只有那些以onEvent开头的方法才是我们需要的 31                 if (methodName.startsWith(ON_EVENT_METHOD_NAME)) { 32                     int modifiers = method.getModifiers(); 33                     //注意这个地方判断方法属性的技巧 与 操作 34                     if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { 35                         Class<?>[] parameterTypes = method.getParameterTypes(); 36                         //如果参数只有一个 37                         if (parameterTypes.length == 1) { 38                             String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length()); 39                             //取ThreadMode 40                             ThreadMode threadMode; 41                             if (modifierString.length() == 0) { 42                                 threadMode = ThreadMode.PostThread; 43                             } else if (modifierString.equals("MainThread")) { 44                                 threadMode = ThreadMode.MainThread; 45                             } else if (modifierString.equals("BackgroundThread")) { 46                                 threadMode = ThreadMode.BackgroundThread; 47                             } else if (modifierString.equals("Async")) { 48                                 threadMode = ThreadMode.Async; 49                             } else { 50                                 if (skipMethodVerificationForClasses.containsKey(clazz)) { 51                                     continue; 52                                 } else { 53                                     throw new EventBusException("Illegal onEvent method, check for typos: " + method); 54                                 } 55                             } 56                             Class<?> eventType = parameterTypes[0]; 57                             methodKeyBuilder.setLength(0); 58                             methodKeyBuilder.append(methodName); 59                             methodKeyBuilder.append('>').append(eventType.getName()); 60                             //onEventMainThread>java.lang.String 61                             //methodKey就是上面的形式,可以看出来是方法名>参数  的格式 62                             String methodKey = methodKeyBuilder.toString(); 64                             //这个地方先去这个hashset里面add这个key,当然了,如果你这个hashset里面已经有这个key 65                             //那必然是add不成功的,只有add成功返回true以后括号内的代码才会得到执行 66                             if (eventTypesFound.add(methodKey)) { 67                                 // Only add if not already found in a sub class 68                                 //这个地方就是构造SubscriberMethod对象放到list里 准备返回 69                                 subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType)); 70                             } 71                         } 72                     } else if (!skipMethodVerificationForClasses.containsKey(clazz)) { 73                         Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "." 74                                 + methodName); 75                     } 76                 } 77             } 78             //这里注意还在大的while循环内,所以你传进去的类自己查完一遍方法以后 还会去找他的父类继续查询方法 79             //一直遍历到父类为 那些java android开头的基类为止! 80             clazz = clazz.getSuperclass(); 81  82         } 83         if (subscriberMethods.isEmpty()) { 84             throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called " 85                     + ON_EVENT_METHOD_NAME); 86         } else { 87             synchronized (methodCache) { 88                 methodCache.put(key, subscriberMethods); 89             } 90             return subscriberMethods; 91         } 92     }

然后我们来看看register函数里面 5-6行 那个循环遍历做了什么

首先我们看看这个循环调用的方法:

 1 /**  2      * @param subscriber       方法所述的类的 包名+类名  3      * @param subscriberMethod  4      * @param sticky  5      * @param priority  6      */  7     private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {  8         //这个eventtype就是方法的参数的类名  9         Class<?> eventType = subscriberMethod.eventType; 10   11         CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); 12         Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority); 13         if (subscriptions == null) { 14             subscriptions = new CopyOnWriteArrayList<Subscription>(); 15             subscriptionsByEventType.put(eventType, subscriptions); 16         } else { 17             if (subscriptions.contains(newSubscription)) { 18                 throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " 19                         + eventType); 20             } 21         } 22  23         // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again) 24         // subscriberMethod.method.setAccessible(true); 25         //这个就是优先级高的位置在前面 26         int size = subscriptions.size(); 27         for (int i = 0; i <= size; i++) { 28             if (i == size || newSubscription.priority > subscriptions.get(i).priority) { 29                 subscriptions.add(i, newSubscription); 30                 break; 31             } 32         } 33  34         List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); 35         if (subscribedEvents == null) { 36             subscribedEvents = new ArrayList<Class<?>>(); 37             typesBySubscriber.put(subscriber, subscribedEvents); 38         } 39         subscribedEvents.add(eventType); 40  41         if (sticky) { 42          43             if (eventInheritance) { 44                 // Existing sticky events of all subclasses of eventType have to be considered. 45                 // Note: Iterating over all events may be inefficient with lots of sticky events, 46                 // thus data structure should be changed to allow a more efficient lookup 47                 // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>). 48                 Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet(); 49                 for (Map.Entry<Class<?>, Object> entry : entries) { 50                     Class<?> candidateEventType = entry.getKey(); 51                     if (eventType.isAssignableFrom(candidateEventType)) { 52                         Object stickyEvent = entry.getValue(); 53                         checkPostStickyEventToSubscription(newSubscription, stickyEvent); 54                     } 55                 } 56             } else { 57                 Object stickyEvent = stickyEvents.get(eventType); 58                 checkPostStickyEventToSubscription(newSubscription, stickyEvent); 59             } 60         } 61     }

10-13行 我们可以看出来 这个函数 主要是为了构造subscription这个list对象。

/**      * 这个map 存储方法的地方 key就是eventType,value就是copyOnWriteArrayList value就是方法的一切      */     private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

我们可以看看这个类是什么

 1 /**  2  * 这个类里面包含有SubscriberMethod类对象,  3  * subscriber  4  */  5 final class Subscription {  6     //这个实际上就是描述方法所属的类的  7     final Object subscriber;  8     //描述方法的类  9     final SubscriberMethod subscriberMethod; 10     //优先级 11     final int priority; 12     /** 13      * Becomes false as soon as {@link EventBus#unregister(Object)} is called, which is checked by queued event delivery 14      * {@link EventBus#invokeSubscriber(PendingPost)} to prevent race conditions. 15      */ 16     volatile boolean active; 17  18     Subscription(Object subscriber, SubscriberMethod subscriberMethod, int priority) { 19         this.subscriber = subscriber; 20         this.subscriberMethod = subscriberMethod; 21         this.priority = priority; 22         active = true; 23     } 24  25     @Override 26     public boolean equals(Object other) { 27         if (other instanceof Subscription) { 28             Subscription otherSubscription = (Subscription) other; 29             return subscriber == otherSubscription.subscriber 30                     && subscriberMethod.equals(otherSubscription.subscriberMethod); 31         } else { 32             return false; 33         } 34     } 35  36     @Override 37     public int hashCode() { 38         return subscriber.hashCode() + subscriberMethod.methodString.hashCode(); 39     } 40 }

所以总结起来,SubscriberMethod 就是对方法的描述,而我们的SubscriberMethod 实际上就是Subscription的子集,Subscription除了有描述方法的对象以外,还有这个方法所属的类,

而我们的register方法总体来说 就是先通过findSubscriberMethods方法 取得我们注册类(就是你register调用的时候传的this)所需要的的那些方法(注意不是每个方法都需要 只选择自己需要的)

然后把这些方法 做一个list,最后再通过便利这个list : 用每一个SubscriberMethod 对象和这个方法所需的类(包名+类名) 来构造出一个Subscription对象,然后把这个对象

存储在SubscriptionsByEventType里,注意这个map的key 实际上就是eventType,而value则代表方法的list,换句话说。

这个SubscriptionsByEventType 是一个键值对,它的key 实际上就是我们的类名,value则是这个类里面我们需要存储的方法的list!

这就是register的大致流程,我们再来看看post 流程即可。

 1  /**  2      * Posts the given event to the event bus.  3      */  4     public void post(Object event) {  5         PostingThreadState postingState = currentPostingThreadState.get();  6         //这个地方可以看出来是每次有人调用post方法的时候 都会从postingState取出这个队列,然后把这个事件放到这个队列里  7         List<Object> eventQueue = postingState.eventQueue;  8         eventQueue.add(event);  9  10         //这个判断isPosting 主要是为了保证同一时间只能有一个线程在处理括号体里的内容 11         //currentPostingThreadState 是用threadlocal来构造的 所以保证了同步性 12         if (!postingState.isPosting) { 13             postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper(); 14             postingState.isPosting = true; 15             if (postingState.canceled) { 16                 throw new EventBusException("Internal error. Abort state was not reset"); 17             } 18             try { 19                 while (!eventQueue.isEmpty()) { 20                     //队列不为空就处理 21                     postSingleEvent(eventQueue.remove(0), postingState); 22                 } 23             } finally { 24                 postingState.isPosting = false; 25                 postingState.isMainThread = false; 26             } 27         } 28     }

先看看第5行的postingState是什么

 1  /**  2      * 静态类,里面除了有一个队列以外,还有几个标志位,以及一个Subscription  3      */  4     final static class PostingThreadState {  5         final List<Object> eventQueue = new ArrayList<Object>();  6         boolean isPosting;  7         boolean isMainThread;  8         Subscription subscription;  9         Object event; 10         boolean canceled; 11     }

这个地方就能看出来,我们每次调用post 都是往eventQueue里面添加一个事件,而12行开始则是从队列里面

取事件来处理,注意12行开始 一次性只能允许一个线程使用~同步的

然后继续看是怎么处理的。

 1  /**  2      * @param event        方法的参数的类名  3      * @param postingState  4      * @throws Error  5      */  6     private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {  7         8         Class<?> eventClass = event.getClass();  9         boolean subscriptionFound = false; 10         11         if (eventInheritance) { 12             List<Class<?>> eventTypes = lookupAllEventTypes(eventClass); 13             int countTypes = eventTypes.size(); 14             for (int h = 0; h < countTypes; h++) { 15                 Class<?> clazz = eventTypes.get(h); 16                 //这个地方就是取出Subscription对象的的所有信息!发消息也是在这个函数里发送的 17                 subscriptionFound |= postSingleEventForEventType(event, postingState, clazz); 18             } 19         } else { 20             subscriptionFound = postSingleEventForEventType(event, postingState, eventClass); 21         } 22         if (!subscriptionFound) { 23             if (logNoSubscriberMessages) { 24                 Log.d(TAG, "No subscribers registered for event " + eventClass); 25             } 26             if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && 27                     eventClass != SubscriberExceptionEvent.class) { 28                 post(new NoSubscriberEvent(this, event)); 29             } 30         } 31     }

继续跟进去

 1 private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {  2         CopyOnWriteArrayList<Subscription> subscriptions;  3         synchronized (this) {  4             subscriptions = subscriptionsByEventType.get(eventClass);  5         }  6         if (subscriptions != null && !subscriptions.isEmpty()) {  7             for (Subscription subscription : subscriptions) {  8                 postingState.event = event;  9                 postingState.subscription = subscription; 10                 boolean aborted = false; 11                 try { 12                     //这个地方就是真正发消息的地方了 13                     postToSubscription(subscription, event, postingState.isMainThread); 14                     aborted = postingState.canceled; 15                 } finally { 16                     postingState.event = null; 17                     postingState.subscription = null; 18                     postingState.canceled = false; 19                 } 20                 if (aborted) { 21                     break; 22                 } 23             } 24             return true; 25         } 26         return false; 27     }

可以看出来 2-6行 就是从我们register流程里存储的键值对里 把我们存放的方法给取出来。,

取出来以后 就可以反射调用他们的方法了

 1   /**  2      * 这个类就是反射执行方法 并且是最终执行回调方法的地方  3      *  4      * @param subscription  5      * @param event  6      * @param isMainThread  7      */  8     private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {  9         switch (subscription.subscriberMethod.threadMode) { 10             case PostThread: 11                 invokeSubscriber(subscription, event); 12                 break; 13             case MainThread: 14                 if (isMainThread) { 15                     invokeSubscriber(subscription, event); 16                 } else { 17                     mainThreadPoster.enqueue(subscription, event); 18                 } 19                 break; 20             case BackgroundThread: 21                 if (isMainThread) { 22                     backgroundPoster.enqueue(subscription, event); 23                 } else { 24                     invokeSubscriber(subscription, event); 25                 } 26                 break; 27             case Async: 28                 asyncPoster.enqueue(subscription, event); 29                 break; 30             default: 31                 throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); 32         } 33     }

13-18行 这个case 如果是主线程,那么就直接反射方法,如果不是的话 则要放到主线程handler里执行。

1  private final HandlerPoster mainThreadPoster;
1  //这个就是主线程handler初始化 2         mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
 1 /*  2  * Copyright (C) 2012 Markus Junginger, greenrobot (http://greenrobot.de)  3  *  4  * Licensed under the Apache License, Version 2.0 (the "License");  5  * you may not use this file except in compliance with the License.  6  * You may obtain a copy of the License at  7  *  8  *      http://www.apache.org/licenses/LICENSE-2.0  9  * 10  * Unless required by applicable law or agreed to in writing, software 11  * distributed under the License is distributed on an "AS IS" BASIS, 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13  * See the License for the specific language governing permissions and 14  * limitations under the License. 15  */ 16 package de.greenrobot.event; 17  18 import android.os.Handler; 19 import android.os.Looper; 20 import android.os.Message; 21 import android.os.SystemClock; 22  23 final class HandlerPoster extends Handler { 24  25     private final PendingPostQueue queue; 26     private final int maxMillisInsideHandleMessage; 27     private final EventBus eventBus; 28     private boolean handlerActive; 29  30     HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) { 31         super(looper); 32         this.eventBus = eventBus; 33         this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage; 34         queue = new PendingPostQueue(); 35     } 36  37     void enqueue(Subscription subscription, Object event) { 38         PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); 39         synchronized (this) { 40             queue.enqueue(pendingPost); 41             if (!handlerActive) { 42                 handlerActive = true; 43                 if (!sendMessage(obtainMessage())) { 44                     throw new EventBusException("Could not send handler message"); 45                 } 46             } 47         } 48     } 49  50     @Override 51     public void handleMessage(Message msg) { 52         boolean rescheduled = false; 53         try { 54             long started = SystemClock.uptimeMillis(); 55             while (true) { 56                 PendingPost pendingPost = queue.poll(); 57                 if (pendingPost == null) { 58                     synchronized (this) { 59                         // Check again, this time in synchronized 60                         pendingPost = queue.poll(); 61                         if (pendingPost == null) { 62                             handlerActive = false; 63                             return; 64                         } 65                     } 66                 } 67                 eventBus.invokeSubscriber(pendingPost); 68                 long timeInMethod = SystemClock.uptimeMillis() - started; 69                 if (timeInMethod >= maxMillisInsideHandleMessage) { 70                     if (!sendMessage(obtainMessage())) { 71                         throw new EventBusException("Could not send handler message"); 72                     } 73                     rescheduled = true; 74                     return; 75                 } 76             } 77         } finally { 78             handlerActive = rescheduled; 79         } 80     } 81 }

51-77行 就是我们实际最终调用的地方。

同样的 我们在看看20-26行的这个background这个case

 1 final class BackgroundPoster implements Runnable {  2   3     private final PendingPostQueue queue;  4     private final EventBus eventBus;  5   6     private volatile boolean executorRunning;  7   8     BackgroundPoster(EventBus eventBus) {  9         this.eventBus = eventBus; 10         queue = new PendingPostQueue(); 11     } 12  13     public void enqueue(Subscription subscription, Object event) { 14         PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); 15         synchronized (this) { 16             queue.enqueue(pendingPost); 17             if (!executorRunning) { 18                 executorRunning = true; 19                 eventBus.getExecutorService().execute(this); 20             } 21         } 22     } 23  24     @Override 25     public void run() { 26         try { 27             try { 28                 while (true) { 29                     PendingPost pendingPost = queue.poll(1000); 30                     if (pendingPost == null) { 31                         synchronized (this) { 32                             // Check again, this time in synchronized 33                             pendingPost = queue.poll(); 34                             if (pendingPost == null) { 35                                 executorRunning = false; 36                                 return; 37                             } 38                         } 39                     } 40                     eventBus.invokeSubscriber(pendingPost); 41                 } 42             } catch (InterruptedException e) { 43                 Log.w("Event", Thread.currentThread().getName() + " was interruppted", e); 44             } 45         } finally { 46             executorRunning = false; 47         } 48     }

一看就知道 他是runnable对象 必然是在后台 在子线程内执行,同时他也是一次性只能做一次操作,完成一个事件,

最后我们来看看Async这个case:

 1 /*  2  * Copyright (C) 2012 Markus Junginger, greenrobot (http://greenrobot.de)  3  *  4  * Licensed under the Apache License, Version 2.0 (the "License");  5  * you may not use this file except in compliance with the License.  6  * You may obtain a copy of the License at  7  *  8  *      http://www.apache.org/licenses/LICENSE-2.0  9  * 10  * Unless required by applicable law or agreed to in writing, software 11  * distributed under the License is distributed on an "AS IS" BASIS, 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13  * See the License for the specific language governing permissions and 14  * limitations under the License. 15  */ 16 package de.greenrobot.event; 17  18  19 /** 20  * Posts events in background. 21  *  22  * @author Markus  并发执行任务,在线程池内执行 23  */ 24 class AsyncPoster implements Runnable { 25  26     private final PendingPostQueue queue; 27     private final EventBus eventBus; 28  29     AsyncPoster(EventBus eventBus) { 30         this.eventBus = eventBus; 31         queue = new PendingPostQueue(); 32     } 33  34     public void enqueue(Subscription subscription, Object event) { 35         PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); 36         queue.enqueue(pendingPost); 37         eventBus.getExecutorService().execute(this); 38     } 39  40     @Override 41     public void run() { 42         PendingPost pendingPost = queue.poll(); 43         if(pendingPost == null) { 44             throw new IllegalStateException("No pending post available"); 45         } 46         eventBus.invokeSubscriber(pendingPost); 47     } 48  49 }

这个地方和background相同的就是也是在非主线程,在子线程内执行,但是这个地方是在线程池内执行,可以并发执行多个任务,

而我们的background 则一次性只能执行一个任务,这是2者之间的区别。

正文到此结束
Loading...