EventBus3.0源碼解析

来源:http://www.cnblogs.com/all88/archive/2016/03/30/5338412.html
-Advertisement-
Play Games

本文主要介紹EventBus3.0的源碼 EventBus是一個Android事件發佈/訂閱框架,通過解耦發佈者和訂閱者簡化 Android 事件傳遞。 EventBus使用簡單,並將事件發佈和訂閱充分解耦,從而使代碼更簡潔。 本文主要從以下幾個模塊來介紹 1、EventBus使用 2、EventB ...


本文主要介紹EventBus3.0的源碼

EventBus是一個Android事件發佈/訂閱框架,通過解耦發佈者和訂閱者簡化 Android 事件傳遞。 EventBus使用簡單,並將事件發佈和訂閱充分解耦,從而使代碼更簡潔。 本文主要從以下幾個模塊來介紹 1、EventBus使用 2、EventBus註冊源碼解析 3、EventBus事件分發解析 4、EventBus取消註冊解析   一、EventBus使用 1、首先是註冊
1  EventBus.getDefault().register(this);

2、響應事件方法

1  @Subscribe(threadMode = ThreadMode.BACKGROUND, sticky = true, priority = 100)
2     public void jiaoTest(String str) {
3         System.out.println("響應方法:" + str);
4     }
參數解析: threadMode :方法執行的線程 sticky:是否接受粘性事件 priority:優先順序 String str:方法接受對象類型 3、事件分發
1  EventBus.getDefault().post("Test");

4、解除註冊

1 EventBus.getDefault().unregister(this);

以上就是EventBus的使用過程,用起來非常簡單方便,非常實用。

二、註冊源碼解析

對應以上的註冊方式,我們就從EventBus.getDefault().register(this);入手,首先查看EventBus.getDefault()

看看EventBus是如何初始化的;

 1 /** Convenience singleton for apps using a process-wide EventBus instance. */
 2     public static EventBus getDefault() {
 3         if (defaultInstance == null) {
 4             synchronized (EventBus.class) {
 5                 if (defaultInstance == null) {
 6                     defaultInstance = new EventBus();
 7                 }
 8             }
 9         }
10         return defaultInstance;
11     }

可以看出來,EventBus是單例模式存在的,一個項目中只能有一個EventBus這樣有利於管理訂閱者和訂閱方法,這會在下麵的介紹中體現出來。

接下來看register(this)

 1 public void register(Object subscriber) {
 2         //訂閱者
 3         Class<?> subscriberClass = subscriber.getClass();
 4         List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
 5         synchronized (this) {
 6             for (SubscriberMethod subscriberMethod : subscriberMethods) {
 7                 subscribe(subscriber, subscriberMethod);
 8             }
 9         }
10     }
11     

可以看出首先獲取訂閱者的類對象Class<?> subscriberClass = subscriber.getClass();

在看這段代碼之前,我們首先要瞭解SubscriberMethod和subscriberMethodFinder.findSubscriberMethods方法到底做了什麼

首先來看SubscriberMethod

 1 public class SubscriberMethod {
 2     final Method method;//方法
 3     final ThreadMode threadMode;//執行線程
 4     final Class<?> eventType;//接收的事件類型
 5     final int priority;//優先順序
 6     final boolean sticky;
 7     /** Used for efficient comparison */
 8     String methodString;
 9 ....
10 }

可以看出SubscriberMethod其實就是一個訂閱方法的實體類,裡面保存了訂閱方法信息

接著看subscriberMethodFinder.findSubscriberMethods

該方法的作用其實就是從訂閱類中獲取所有的訂閱方法信息;

 1 List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
 2      
 3         //首先從緩存中讀取 
 4         List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
 5         if (subscriberMethods != null) {
 6             return subscriberMethods;
 7         }
 8         
 9 
10         //是否忽略註解器生成的MyEventBusIndex類
11 
12         if (ignoreGeneratedIndex) {
13             //利用反射來獲取訂閱類中的訂閱方法信息
14             subscriberMethods = findUsingReflection(subscriberClass);
15         } else {
16             //從註解器生成的MyEventBusIndex類中獲得訂閱類的訂閱方法信息
17             subscriberMethods = findUsingInfo(subscriberClass);
18         }
19         if (subscriberMethods.isEmpty()) {
20             throw new EventBusException("Subscriber " + subscriberClass
21                     + " and its super classes have no public methods with the @Subscribe annotation");
22         } else {
23             //保存進緩存
24             METHOD_CACHE.put(subscriberClass, subscriberMethods);
25             return subscriberMethods;
26         }
27     }

我們看到,該方法首先從緩存中獲取訂閱類的訂閱方法信息,如果沒有則通過兩種方式來獲取

1、通過EventBusAnnotationProcessor(註解處理器)生成的MyEventBusIndex中獲取
2、利用反射來讀取訂閱類中訂閱方法信息

EventBusAnnotationProcessor是什麼東東?(此處參考:文/達達達達sky(簡書作者)原文鏈接:http://www.jianshu.com/p/f057c460c77e)

在3.0版本中,EventBus提供了一個EventBusAnnotationProcessor註解處理器來在編譯期通過讀取@Subscribe()註解並解析,
處理其中所包含的信息,然後生成java類來保存所有訂閱者關於訂閱的信息,這樣就比在運行時使用反射來獲得這些訂閱者的
信息速度要快.我們可以參考EventBus項目里的EventBusPerformance這個例子,編譯後我們可以在build文件夾里找到這個類
,MyEventBusIndex 類,當然類名是可以自定義的.我們大致看一下生成的MyEventBusIndex類是什麼樣的:

 1 /**
 2  * This class is generated by EventBus, do not edit.
 3  */
 4 public class MyEventBusIndex implements SubscriberInfoIndex {
 5     private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
 6 
 7     static {
 8         SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
 9 
10         putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscriberClassEventBusAsync.class,
11                 true, new SubscriberMethodInfo[]{
12                 new SubscriberMethodInfo("onEventAsync", TestEvent.class, ThreadMode.ASYNC),
13         }));
14 
15         putIndex(new SimpleSubscriberInfo(TestRunnerActivity.class, true, new SubscriberMethodInfo[]{
16                 new SubscriberMethodInfo("onEventMainThread", TestFinishedEvent.class, ThreadMode.MAIN),
17         }));
18     }
19 
20     private static void putIndex(SubscriberInfo info) {
21         SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
22     }
23 
24     @Override
25     public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
26         SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
27         if (info != null) {
28             return info;
29         } else {
30             return null;
31         }
32     }
33 }


可以看出是使用一個靜態HashMap即:SUBSCRIBER_INDEX來保存訂閱類的信息,其中包括了訂閱類的class對象,

是否需要檢查父類,以及訂閱方法的信息SubscriberMethodInfo的數組,SubscriberMethodInfo中又保存了,訂閱方法的方法名,

訂閱的事件類型,觸發線程,是否接收sticky事件以及優先順序priority.這其中就保存了register()的所有需要的信息;

我們重點研究一下通過反射來獲取訂閱方法信息即:findUsingReflection(subscriberClass);

1  private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
2         FindState findState = prepareFindState();
3         findState.initForSubscriber(subscriberClass);
4         while (findState.clazz != null) {
5             findUsingReflectionInSingleClass(findState);
6             findState.moveToSuperclass();
7         }
8         return getMethodsAndRelease(findState);
9     }

FindState其實就是一個裡面保存了訂閱者和訂閱方法信息的一個實體類,包括訂閱類中所有訂閱的事件類型和所有的訂閱方法等。

我們看到會首先創建一個FindState對象並執行findUsingReflectionInSingleClass(findState);來獲取訂閱類的方法信息

 1 private void findUsingReflectionInSingleClass(FindState findState) {
 2         Method[] methods;
 3         try {
 4             // This is faster than getMethods, especially when subscribers are fat classes like Activities
 5             methods = findState.clazz.getDeclaredMethods();
 6         } catch (Throwable th) {
 7             // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
 8             //通過反射獲取到訂閱類中的所有方法
 9             methods = findState.clazz.getMethods();
10             findState.skipSuperClasses = true;
11         }
12         //遍歷所有方法,忽略private類型的,最後如果是公有,並且不是 
13         //java編譯器 生成的方法名,那麼就是我們要的了。
14         for (Method method : methods) {
15             int modifiers = method.getModifiers();
16             if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
17                 Class<?>[] parameterTypes = method.getParameterTypes();
18                 //保證只有一個事件參數
19                 if (parameterTypes.length == 1) {
20                     //得到註解
21                     Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
22                     if (subscribeAnnotation != null) {
23                         Class<?> eventType = parameterTypes[0];
24                         //校驗是否添加該方法
25                         if (findState.checkAdd(method, eventType)) {
26                             ThreadMode threadMode = subscribeAnnotation.threadMode();
27                             //添加方法
28                             findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
29                                     subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
30                         }
31                     }
32                 } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
33                     String methodName = method.getDeclaringClass().getName() + "." + method.getName();
34                     throw new EventBusException("@Subscribe method " + methodName +
35                             "must have exactly 1 parameter but has " + parameterTypes.length);
36                 }
37             } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
38                 String methodName = method.getDeclaringClass().getName() + "." + method.getName();
39                 throw new EventBusException(methodName +
40                         " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
41             }
42         }
43     }

可以看到,首先會得到訂閱類的class對象並通過反射獲取訂閱類中的所有方法信息,然後通過篩選獲取到訂閱方法集合。

程式執行到此我們就獲取到了訂閱類中的所有的訂閱方法信息,接下來我們就要對訂閱方法進行註冊;

subscribe(subscriber, subscriberMethod);//參數:1訂閱者2訂閱方法集

 1  // Must be called in synchronized block
 2     private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
 3         //獲取訂閱方法的參數類型
 4         Class<?> eventType = subscriberMethod.eventType;
 5         Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
 6         //根據訂閱的事件類型獲取所有的訂閱者
 7         CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
 8         //將訂閱者添加到subscriptionsByEventType集合中
 9         if (subscriptions == null) {
10             subscriptions = new CopyOnWriteArrayList<>();
11             subscriptionsByEventType.put(eventType, subscriptions);
12         } else {
13             if (subscriptions.contains(newSubscription)) {
14                 throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
15                         + eventType);
16             }
17         }
18 
19         //根據優先順序,將訂閱者插入到指定的位置
20         int size = subscriptions.size();
21         for (int i = 0; i <= size; i++) {
22             if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
23                 subscriptions.add(i, newSubscription);
24                 break;
25             }
26         }
27         
28         //獲取訂閱者所有訂閱的事件類型
29 
30         List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
31         if (subscribedEvents == null) {
32             subscribedEvents = new ArrayList<>();
33             typesBySubscriber.put(subscriber, subscribedEvents);
34         }
35         //將該事件類型添加到typesBySubscriber中
36         subscribedEvents.add(eventType);
37 
38 
39         //如果接收sticky事件,立即分發sticky事件
40         if (subscriberMethod.sticky) {
41             if (eventInheritance) {
42                 // Existing sticky events of all subclasses of eventType have to be considered.
43                 // Note: Iterating over all events may be inefficient with lots of sticky events,
44                 // thus data structure should be changed to allow a more efficient lookup
45                 // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
46                 Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
47                 for (Map.Entry<Class<?>, Object> entry : entries) {
48                     Class<?> candidateEventType = entry.getKey();
49                     if (eventType.isAssignableFrom(candidateEventType)) {
50                         Object stickyEvent = entry.getValue();
51                         checkPostStickyEventToSubscription(newSubscription, stickyEvent);
52                     }
53                 }
54             } else {
55                 Object stickyEvent = stickyEvents.get(eventType);
56                 checkPostStickyEventToSubscription(newSubscription, stickyEvent);
57             }
58         }
59     }

上面這段代碼涉及到幾個對象我來介紹一下:

Subscription

//訂閱者信息
final class Subscription {
final Object subscriber;//訂閱者
final SubscriberMethod subscriberMethod;//訂閱方法
}

subscriptionsByEventType
key訂閱方法類型 values 所有訂閱了該類型的訂閱者集合
Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

typesBySubscriber
key訂閱者 values訂閱事件集合
Map<Object, List<Class<?>>> typesBySubscriber;

瞭解了這幾個對象,上面的代碼就很容易看懂了,

1、首先獲取訂閱方法的參數類型即訂閱事件類型

2、根據訂閱事件類型獲取該事件類型的所有訂閱者

3、將該訂閱者添加到該事件類型的訂閱者集合中即:subscriptionsByEventType

4、獲取訂閱者所有的訂閱事件類型

5、將該事件類型添加到該訂閱者的訂閱事件類型集中即:typesBySubscriber

至此,就完成了訂閱類中訂閱方法的註冊,我們來看一下整個流程

 

三、事件分發解析

接下來我們來分析EventBus的事件分發機制即:EventBus.getDefault().post("Test");

我們從post方法入手

 1 /** Posts the given event to the event bus. */
 2     public void post(Object event) {
 3         //獲取當前線程的postingState
 4         PostingThreadState postingState = currentPostingThreadState.get();
 5         //取得當前線程的事件隊列
 6         List<Object> eventQueue = postingState.eventQueue;
 7         //將該事件添加到當前的事件隊列中等待分發
 8         eventQueue.add(event);
 9 
10         if (!postingState.isPosting) {
11             //判斷是否是在主線程post
12             postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
13             postingState.isPosting = true;
14             if (postingState.canceled) {
15                 throw new EventBusException("Internal error. Abort state was not reset");
16             }
17             try {
18                 while (!eventQueue.isEmpty()) {
19                     //分發事件
20                     postSingleEvent(eventQueue.remove(0), postingState);
21                 }
22             } finally {
23                 postingState.isPosting = false;
24                 postingState.isMainThread = false;
25             }
26         }
27     }

什麼是PostingThreadState?

1 final static class PostingThreadState {
2         final List<Object> eventQueue = new ArrayList<Object>();//當前線程的事件隊列
3         boolean isPosting;//是否有事件正在分發
4         boolean isMainThread;//post的線程是否是主線程
5         Subscription subscription;//訂閱者
6         Object event;//訂閱事件
7         boolean canceled;//是否取消
8     }

PostingThreadState中包含了當前線程的事件隊列,就是當前線程所有分發的事件都保存在eventQueue事件隊列中

以及訂閱者訂閱事件等信息,有了這些信息我們就可以從事件隊列中取出事件分發給對應的訂閱者。

PostingThreadState怎麼獲得?

1 ThreadLocal 是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,而這段數據是不會與其他線程共用的。
2      private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
3         @Override
4         protected PostingThreadState initialValue() {
5             return new PostingThreadState();
6         }
7     };

可以看出currentPostingThreadState的實現是一個包含了PostingThreadStateThreadLocal對象,這樣可以保證取到的都是

自己線程對應的數據。

我們有了PostingThreadState獲取到了當前線程的事件隊列,接下來就是事件分發,我們來看

postSingleEvent(eventQueue.remove(0), postingState);

 1 事件分發
 2      private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
 3         //得到事件類型
 4         Class<?> eventClass = event.getClass();
 5         boolean subscriptionFound = false;
 6 
 7         //是否觸發訂閱了該事件(eventClass)的父類,以及介面的類的響應方法.
 8         if (eventInheritance) {
 9             List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
10             int countTypes = eventTypes.size();
11             for (int h = 0; h < countTypes; h++) {
12                 Class<?> clazz = eventTypes.get(h);
13                 subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
14             }
15         } else {
16             subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
17         }
18         if (!subscriptionFound) {
19             if (logNoSubscriberMessages) {
20                 Log.d(TAG, "No subscribers registered for event " + eventClass);
21             }
22             if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
23                     eventClass != SubscriberExceptionEvent.class) {
24                 post(new NoSubscriberEvent(this, event));
25             }
26         }
27     }

通過以上代碼我們可以發現,真正的事件分發是通過postSingleEventForEventType(event, postingState, eventClass);發出去的我們來看:

 1 private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
 2         CopyOnWriteArrayList<Subscription> subscriptions;
 3         synchronized (this) {
 4             //根據事件類型獲取所有的訂閱者
 5             subscriptions = subscriptionsByEventType.get(eventClass);
 6         }
 7         //向每個訂閱者分發事件
 8         if (subscriptions != null && !subscriptions.isEmpty()) {
 9             for (Subscription subscription : subscriptions) {
10                 postingState.event = event;
11                 postingState.subscription = subscription;
12                 boolean aborted = false;
13                 try {
14                     postToSubscription(subscription, event, postingState.isMainThread);
15                     aborted = postingState.canceled;
16                 } finally {
17                     postingState.event = null;
18                     postingState.subscription = null;
19                     postingState.canceled = false;
20                 }
21                 if (aborted) {
22                     break;
23                 }
24             }
25             return true;
26         }
27         return false;
28     }

可以看到首先根據事件類型獲取到所有的訂閱者,然後迴圈向每個訂閱者發送事件,通過

postToSubscription(subscription, event, postingState.isMainThread);發送出去

 1  private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
 2         switch (subscription.subscriberMethod.threadMode) {
 3             case POSTING://預設的 ThreadMode,表示在執行 Post 操作的線程直接調用訂閱者的事件響應方法,
 4             //不論該線程是否為主線程(UI 線程)。
 5                 invokeSubscriber(subscription, event);
 6                 break;
 7             case MAIN://在主線程中執行響應方法。
 8                 if (isMainThread) {
 9                     invokeSubscriber(subscription, event);
10                 } else {
11                     mainThreadPoster.enqueue(subscription, event);
12                 }
13                 break;
14             case BACKGROUND://在後臺線程中執行響應方法。
15                 if (isMainThread) {
16                     backgroundPoster.enqueue(subscription, event);
17                 } else {
18                     invokeSubscriber(subscription, event);
19                 }
20                 break;
21             case ASYNC://不論發佈線程是否為主線程,都使用一個空閑線程來處理。
22                 asyncPoster.enqueue(subscription, event);
23                 break;
24             default:
25                 throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
26         }
27     }

以上的四種threadMode可以看代碼註釋簡單瞭解一下,通過一下代碼我們來看一下訂閱方法最後是通過invokeSubscriber(subscription, event);來執行的

 1 //最終通過反射調用訂閱者的訂閱函數 並把event作為參數傳入
 2      void invokeSubscriber(Subscription subscription, Object event) {
 3         try {
 4             subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
 5         } catch (InvocationTargetException e) {
 6             handleSubscriberException(subscription, event, e.getCause());
 7         } catch (IllegalAccessException e) {
 8             throw new IllegalStateException("Unexpected exception", e);
 9         }
10     }

真相大白;最後是通過反射的方式,調用了訂閱類中的訂閱方法。我們來總結一下整個事件分發的過程

1、首先獲取當前線程的PostingThreadState對象從而獲取到當前線程的事件隊列

2、通過事件類型獲取到所有訂閱者集合

3、通過反射執行訂閱者中的訂閱方法

是不是很簡單。

我們來看一下整個事件分發的流程圖

 

 

四、取消註冊解析

我們簡單看一下取消註冊的源碼EventBus.getDefault().unregister(this);

 1  /** Unregisters the given subscriber from all event classes. */
 2     public synchronized void unregister(Object subscriber) {
 3         //獲取訂閱者的所有訂閱的事件類型
 4         List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
 5         if (subscribedTypes != null) {
 6             for (Class<?> eventType : subscribedTypes) {
 7                 //從事件類型的訂閱者集合中移除訂閱者
 8                 unsubscribeByEventType(subscriber, eventType);
 9             }
10             typesBySubscriber.remove(subscriber);
11         } else {
12             Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
13         }
14     }

再來看一下:unsubscribeByEventType(subscriber, eventType);

 1  /** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
 2     private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
 3         //獲取事件類型的所有訂閱者
 4         List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
 5         //遍歷訂閱者集合,將解除的訂閱者移除
 6         if (subscriptions != null) {
 7             int size = subscriptions.size();
 8             for (int i = 0; i < size; i++) {
 9                 Subscription subscription = subscriptions.get(i);
10                 if (subscription.subscriber == subscriber) {
11                     subscription.active = false;
12                     subscriptions.remove(i);
13                     i--;
14                     size--;
15                 }
16         

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文內容 環境 android-common 項目結構 演示 android-common 參考資料 android-common 主要包括如下內容: 緩存,包括圖片緩存、預取緩存、網路緩存。 公共 View,即功能封裝好的部件,包括下拉獲得最新和上拉載入更多 ListView、底部載入更多 Scr ...
  • 匿名內部類作為事件監聽器類實現頁面跳轉 @Overrideprotected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main1);/* ...
  • ```objc1 使用Crearte函數創建的併發隊列和全局併發隊列的主要區別: 1)全局併發隊列在整個應用程式中本身是預設存在的並且對應有高優先順序、預設優先順序、低優先順序和後臺優先順序一共四個併發隊列,我們只是選擇其中的一個直接拿來用。而Create函數是實打實的從頭開始去創建一個隊列。 2)在iOS ...
  • 效果如圖 1 首先這是一個自定義的Dialog,而不是AlertDialog,如果是AlertDialog的話,軟鍵盤彈出的時候在AlertDialog的後面,無法進行輸入。 2 Dialog的上面會有一個黑框,添加Style 3 輸入密碼用的是EditText,改變EditText的格式是 4 給 ...
  • 提到定時器,NStimer肯定是我們最為熟悉的。 但是NStimer有著很大的缺點,並不准確。 通俗點說,就是它該做他的事了,但是由於其他事件的影響,Nstimer會放棄他應該做的。 而GCD定時器,是不會發生這種事情的。 GCD嚴格按照規定好的規格去做事。 前面介紹RunLoop 的時候已經介紹了 ...
  • 本文會繼續深入學習OC記憶體管理,內容主要參考iOS高級編程,Objective C基礎教程,瘋狂iOS講義,是我學習記憶體管理的筆記 記憶體管理 1 記憶體管理的基本概念 1.1 Objective C中的記憶體管理 手動記憶體管理和自動釋放池 \ (Mannul Reference Counting) 自動 ...
  • 一 什麼是RunLoop? 從字面意思看就是運行迴圈,其實內部就是do-while迴圈,這個迴圈內部不斷地處理各種任務(比 如Source,Timer,Observer) 一個線程對應一個RunLoop,主線程的RunLoop預設已經啟動,子線程的RunLoop得手動啟動(run方法) RunLoo ...
  • 老師佈置了個作業:http://www.cnblogs.com/qingxu/p/5316897.html 作業中提到的 “玩了幾天以後,大家發現了一些很有意思的現象,比如黃金點在逐漸地往下移動。” 而只是提到而已,如果不保留歷史結果和不是比較明顯的顯示出來,大家也很難發現這個問題。於是我就想到了折 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...