摘要:第二種是通過(guò)方法來(lái)檢查,源碼如下通過(guò)以及來(lái)生成一個(gè),來(lái)存儲(chǔ)方法所在的類。先看下的方法繼承了,調(diào)用方法后會(huì)向事件隊(duì)列中插入一個(gè)事件,然后將標(biāo)記位設(shè)置為表示正在處理事件,然后調(diào)用發(fā)送消息通知處理事件。
首先從訂閱開(kāi)始
EventBus.getDefault().register(this);
register方法會(huì)獲取傳入的object對(duì)象的class,通過(guò)findSubscriberMethods方法來(lái)查找這個(gè)class中訂閱的方法,如下
public void register(Object subscriber) {
Class<");synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
findSubscriberMethods方法實(shí)現(xiàn)如下,其中有一個(gè)ConcurrentHashMap類型的靜態(tài)對(duì)象METHOD_CACHE,是用來(lái)緩存對(duì)應(yīng)類的訂閱方法的,以便后續(xù)再次訂閱時(shí)不用重新去findMethods,可以直接從緩存中讀取。
List findSubscriberMethods(Class<"); {
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
查找訂閱方法通過(guò)ignoreGeneratedIndex字段分為兩種方式
第一種findUsingReflection是通過(guò)反射來(lái)查找,找到被@Subscribe注解修飾的方法,并且根據(jù)具體的注解以及方法參數(shù)生成一個(gè)SubscriberMethod對(duì)象:
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
第二種findUsingInfo是通過(guò)apt的方式,提前找到訂閱的方法,可以避免通過(guò)反射查找方法帶來(lái)的耗時(shí)。
具體使用方法:在gradle配置apt,rebuild項(xiàng)目,會(huì)生成一個(gè)注解方法索引類,在EventBusBuilder中通過(guò)addIndex方法新建一個(gè)該類的對(duì)象傳入即可。
這邊還有一個(gè)問(wèn)題,對(duì)于子類重寫(xiě)父類的訂閱方法如何處理。在上面的兩種方式中在查找完子類的訂閱方法后都會(huì)繼續(xù)去查找父類的訂閱方法,都通過(guò)一個(gè)叫做checkAdd的方法進(jìn)行支撐,該方法返回true表示可以添加到訂閱方法的集合中去。
boolean checkAdd(Method method, Class<"); {
// 2 level check: 1st level with event type only (fast), 2ndlevelwith complete signature when required.
// Usually a subscriber doesn"t have methods listening to thesameevent type.
Object existing = anyMethodByEventType.put(eventType, method);
if (existing == null) {
return true;
} else {
if (existing instanceof Method) {
if (!checkAddWithMethodSignature((Method)existing,eventType)) {
// Paranoia check
throw new IllegalStateException();
}
// Put any non-Method object to "consume" theexistingMethod
anyMethodByEventType.put(eventType, this);
}
return checkAddWithMethodSignature(method, eventType);
}
}
checkAdd中設(shè)置了兩種檢查方式,第一種是通過(guò)eventType也就是訂閱方法的入?yún)?lái)檢查,這種方式比較快,只需要看下之前有沒(méi)有這種入?yún)⒌姆椒ň涂梢粤?。注釋中也指出了通常一個(gè)類不會(huì)有多個(gè)入?yún)⑾嗤挠嗛喎椒ā?/p>
第二種是通過(guò)checkAddWithMethodSignature方法來(lái)檢查,源碼如下:
private boolean checkAddWithMethodSignature(Method method,Class<"); {
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(method.getName());
methodKeyBuilder.append(">").append(eventType.getName());
String methodKey = methodKeyBuilder.toString();
Class<");if (methodClassOld == null||methodClassOld.isAssignableFrom(methodClass)) {
// Only add if not already found in a sub class
return true;
} else {
// Revert the put, old class is further down theclasshierarchy
subscriberClassByMethodKey.put(methodKey, methodClassOld);
return false;
}
}
通過(guò)method以及eventType來(lái)生成一個(gè)key,來(lái)存儲(chǔ)方法所在的類。其中methodClassOld == null ||methodClassOld.isAssignableFrom(methodClass)這個(gè)判斷條件對(duì)應(yīng)著兩種情況,methodClassOld == null說(shuō)明是入?yún)⑾嗤欠椒煌姆椒ㄕ诒惶砑?,直接返?b>true就可以了methodClassOld.isAssignableFrom(methodClass)這個(gè)條件是為了過(guò)濾掉父類被子類重寫(xiě)的方法,前面說(shuō)過(guò)了查找訂閱方法是從子類開(kāi)始遍歷的,此時(shí)如果子類重寫(xiě)了父類的訂閱方法,那么methodClassOld對(duì)應(yīng)的是子類,methodClass對(duì)應(yīng)的是父類,顯然這個(gè)判斷就會(huì)為false,之后進(jìn)入下面的else分支return false,也就是忽略掉父類被子類重寫(xiě)的方法。
查找訂閱方法基本就這么點(diǎn),查找完畢之后需要執(zhí)行訂閱操作,也就是register方法中的subscribe(subscriber, subscriberMethod);操作,直接看下該方法的實(shí)現(xiàn):
private void subscribe(Object subscriber, SubscriberMethodsubscriberMethod) {
Class<");new Subscription(subscriber,subscriberMethod);
CopyOnWriteArrayList subscriptions =subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
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();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority >subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
Listif (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
if (subscriberMethod.sticky) {
if (eventInheritance) {
// Existing sticky events of all subclasses of eventTypehave to be considered.
// Note: Iterating over all events may be inefficientwith lots of sticky events,
// thus data structure should be changed to allow a moreefficient lookup
// (e.g. an additional map storing sub classes of superclasses: Class -> List).
Setfor (Map.Entryif (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscriptin, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription,stickyEvent);
}
}
}
subscriptionsByEventType這個(gè)Map是將eventType作為key保存其所對(duì)應(yīng)的訂閱方法的集合。該方法將剛查找到的方法添加到對(duì)應(yīng)的集合中去,添加時(shí)有這樣一層判斷i == size || subscriberMethod.priority >subscriptions.get(i).subscriberMethod.priority這表示這個(gè)集合里的方法會(huì)按照所設(shè)定的優(yōu)先級(jí)進(jìn)行排序。緊接著又出現(xiàn)了個(gè)MaptypesBySubscriber將訂閱者作為key保存一個(gè)Class的集合,暫時(shí)看不出有啥用,就先不管,最后再檢查下是不是粘性事件,如果是粘性事件就根據(jù)所保存的粘性事件來(lái)執(zhí)行該方法。eventInheritance也是在bulider中設(shè)置的,如果為true則會(huì)考慮事件的繼承性,如果現(xiàn)在有eventType為正在訂閱的方法的eventType的子類的粘性事件存在,那么這個(gè)粘性事件也會(huì)被正在訂閱的方法接收到,直接說(shuō)可能比較繞,舉個(gè)栗子,現(xiàn)在我有兩個(gè)事件,其中一個(gè)是另一個(gè)的子類,并且有兩個(gè)粘性訂閱方法,如下:
class EventMessage {
}
class SubEventMessage extends EventMessage {
}
@Subscribe(sticky = true)
public void onEvent(EventMessage message) {
// do something
}
@Subscribe(sticky = true)
public void onSubEvent(SubEventMessage message) {
// do something
}
當(dāng)執(zhí)行register時(shí),如果內(nèi)存中存在著一個(gè)類型為SubEventMessage的事件,那么訂閱的時(shí)候onEvent方法會(huì)被執(zhí)行,入?yún)⑹莾?nèi)存中類型為SubEventMessage的事件。
現(xiàn)在register大致就分析完了,再來(lái)看下unregister方法:
public synchronized void unregister(Object subscriber) {
Listif (subscribedTypes != null) {
for (Class<");else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
unregister方法十分簡(jiǎn)單,typesBySubscriber是剛才在進(jìn)行訂閱的時(shí)候不知道用來(lái)干什么的Map,現(xiàn)在知道是在取消訂閱時(shí)用到的,這個(gè)Map將訂閱者作為key,將其所有的訂閱方法的eventType存入到對(duì)應(yīng)的List中去,取消訂閱時(shí)將這個(gè)List取出來(lái),遍歷去移除對(duì)應(yīng)的訂閱方法,具體實(shí)現(xiàn)在unsubscribeByEventType中,也十分簡(jiǎn)單,就不贅述了。
訂閱和取消訂閱都看過(guò)了,還差個(gè)發(fā)送事件,發(fā)送事件分為post和postSticky兩種,先看post:
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List
currentPostingThreadState是個(gè)ThreadLocal,然后從中取出當(dāng)前線程的postingState,也就是說(shuō)每個(gè)線程都會(huì)維護(hù)一個(gè)自己的posting狀態(tài),之后會(huì)有個(gè)循環(huán)將事件隊(duì)列清空,通過(guò)postSingleEvent方法來(lái)進(jìn)一步處理:
private void postSingleEvent(Object event, PostingThreadStatepostingState) throws Error {
Class<");boolean subscriptionFound = false;
if (eventInheritance) {
Listint countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<");else {
subscriptionFound = postSingleEventForEventType(event,postingState, eventClass);
}
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered forevent " + eventClass);
}
if (sendNoSubscriberEvent && eventClass !=NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
同樣是通過(guò)eventInheritance來(lái)判斷是否要涉及eventType的父類,之后再通過(guò)postSingleEventForEventType方法的返回值來(lái)得到該事件是否被處理,如果沒(méi)有被處理,那么會(huì)返回false進(jìn)入下一個(gè)分支,logNoSubscriberMessages和sendNoSubscriberEvents都是在builder中傳入的,前者用于沒(méi)有訂閱者處理事件時(shí)打印日志,后者用于沒(méi)有訂閱者處理事件時(shí)發(fā)送一個(gè)NoSubscriberEvent類型的事件,所以具體是怎么處理事件的還要繼續(xù)看postSingleEventForEventType方法:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<"); {
CopyOnWriteArrayList subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
postSingleEventForEventType方法從subscriptionsByEventType中去獲取對(duì)應(yīng)事件類型的所有訂閱者,如果沒(méi)有訂閱者就返回false表示事件沒(méi)有被處理,否則就遍歷所有的訂閱者,通過(guò)postToSubscription方法來(lái)處理事件,接著往里看:
private void postToSubscription(Subscription subscription, Objectevent, 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 notdecoupled from subscriber
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);
}
}
在這個(gè)方法內(nèi)終于看到通過(guò)區(qū)分注解中的threadMode來(lái)區(qū)分不同的處理方式了,先來(lái)看下這幾種threadMode分別代表什么意思:
Mode | 含義 |
---|---|
POSTING | 在當(dāng)前線程執(zhí)行 |
MAIN | 在主線程執(zhí)行 |
MAIN_ORDERED | 在主線程有序執(zhí)行 |
BACKGROUND | 在后臺(tái)線程執(zhí)行 |
ASYNC | 在新的線程執(zhí)行 |
可以看到有幾個(gè)差不多,那具體有什么區(qū)別呢?直接從代碼里看,先說(shuō)明幾個(gè)東西,invokeSubscriber就是直接調(diào)用訂閱方法,還有幾個(gè)后綴為poster的變量暫時(shí)先理解為調(diào)用了enqueue方法后,訂閱方法就會(huì)在某個(gè)時(shí)間被執(zhí)行,后面再詳細(xì)講。
現(xiàn)在可以看代碼了,POSTING沒(méi)什么好說(shuō)的,直接調(diào)用invokeSubscriber,也就是說(shuō)在調(diào)用eventBus.post的線程執(zhí)行。
MAIN和MAIN_ORDERED都是在主線程執(zhí)行,后者的ORDERED體現(xiàn)在什么地方呢,先看下MAIN的分支,其中通過(guò)mainThreadPoster.enqueue插入的事件會(huì)在主線程執(zhí)行,判斷當(dāng)前線程是否是主線程來(lái)決定直接調(diào)用訂閱方法還是通過(guò)mainThreadPoster來(lái)發(fā)布,這里應(yīng)該沒(méi)什么疑惑的,主要是MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster notdecoupled from subscriber
invokeSubscriber(subscription, event);
}
mainThreadPoster不為空時(shí),通過(guò)mainThreadPoster來(lái)發(fā)布事件,為空時(shí)直接調(diào)用訂閱方法,說(shuō)好的在主線程調(diào)用呢?這里注釋也說(shuō)明了是不正確的,實(shí)際上mainThreadPoster為空本身就是種異常情況,具體可以看下它的初始化過(guò)程,這里就不細(xì)說(shuō)了。所以下面的else分支就先不管了,那么為什么說(shuō)通過(guò)mainThreadPoster發(fā)布的事件就是“有序”的呢,實(shí)際上mainThreadPoster內(nèi)部實(shí)現(xiàn)是個(gè)handler,可以將事件post到主線程中去執(zhí)行,所以說(shuō)是有序的,這里簡(jiǎn)單說(shuō)明下原因:
主線程維護(hù)著一個(gè)消息隊(duì)列,循環(huán)從里面取出消息來(lái)處理,我們知道可以通過(guò)view的post方法來(lái)獲取它繪制完成之后的寬高,原因是post方法里的事件會(huì)被插入到消息隊(duì)列的尾部,而view的measure,layout,draw都在新插入的消息的前面,所以當(dāng)post的方法執(zhí)行時(shí),view肯定已經(jīng)繪制好了。
而handler通過(guò)sendMessage發(fā)送的消息也會(huì)被插入到主線程消息隊(duì)列的尾部,這就是“有序”,比如現(xiàn)在有一個(gè)ImageView,在它的onMeasure中去發(fā)布一個(gè)事件,如果訂閱方法的模式是MAIN那么會(huì)在onMeasure中調(diào)用訂閱方法,而如果模式是MAIN_ORDERED那么會(huì)在ImageView繪制完成后調(diào)用訂閱方法。
再來(lái)看下BACKGROUND和ASYNC的區(qū)別:
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
其中backgroundPoster和asyncPoster都會(huì)開(kāi)啟一個(gè)新線程來(lái)執(zhí)行訂閱方法,暫時(shí)當(dāng)成是一樣的就行,那么區(qū)別就是BACKGROUND模式如果在子線程post一個(gè)事件,那么會(huì)直接在該線程調(diào)用訂閱方法,只有在主線程post事件才會(huì)開(kāi)啟一個(gè)新線程。而ASYNC模式,不管是在哪post事件,都會(huì)開(kāi)啟一個(gè)新線程來(lái)調(diào)用訂閱方法。
最后再看下幾個(gè)poster基本上就看完了,幾個(gè)poster都實(shí)現(xiàn)了同一個(gè)接口Poster:
interface Poster {
/**
* Enqueue an event to be posted for a particular subscription.
*
* @param subscription Subscription which will receive the event.
* @param event Event that will be posted to subscribers.
*/
void enqueue(Subscription subscription, Object event);
}
可以看到里面只有一個(gè)需要實(shí)現(xiàn)的方法enqueue,是用來(lái)插入事件的,這個(gè)接口被三個(gè)類實(shí)現(xiàn),分別是HandlerPoster,BackgroundPoster和AsyncPoster,上面的mainThreadPoster對(duì)應(yīng)的就是HandlerPoster,這三個(gè)類中都有個(gè)類型為PendingPostQueue的成員變量,這是個(gè)事件隊(duì)列,具體實(shí)現(xiàn)就不看了,這個(gè)隊(duì)列提供了入隊(duì)和出隊(duì)的方法。
先看下HandlerPoster的enqueue方法:
public class HandlerPoster extends Handler implements Poster {
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
}
HandlerPoster繼承了Handler,調(diào)用enqueue方法后會(huì)向事件隊(duì)列中插入一個(gè)事件,然后將標(biāo)記位handlerActive設(shè)置為true表示正在處理事件,然后調(diào)用sendMessage發(fā)送消息通知處理事件。PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);這行是用來(lái)獲取一個(gè)消息隊(duì)列的Node用來(lái)插入到隊(duì)列中去,EventBus維護(hù)著一個(gè)pool用來(lái)保存閑置的Node當(dāng)有需要時(shí)從中取出一個(gè)給事件使用,pool不夠用時(shí)才會(huì)new新的Node出來(lái),具體可以看下PendingPost,這樣做的好處是可以避免頻繁創(chuàng)建對(duì)象帶來(lái)的開(kāi)銷(xiāo)。
再看下HandlerPoster的handleMessage方法:
public 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 synchronized
pendingPost = 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;
}
}
首先會(huì)記錄下開(kāi)始處理事件的時(shí)間,然后從事件隊(duì)列中取出事件,如果為空就將handlerActive設(shè)置為false直接return了,如果不為空,就調(diào)用eventBus.invokeSubscriber(pendingPost);來(lái)調(diào)用訂閱方法,執(zhí)行完后,再看下時(shí)間,如果超出了規(guī)定的時(shí)間那么重新發(fā)送一條消息,本次消息處理結(jié)束,等下次輪到自己的時(shí)候再處理事件,畢竟不能一直處理隊(duì)列里的事件而阻塞了主線程,如果沒(méi)有超出規(guī)定事件,那么說(shuō)明還可以有事件可以處理下一個(gè)事件,就會(huì)再次進(jìn)入循環(huán)。
BackgroundPoster和AsyncPoster其實(shí)和HandlerPoster差不多,只是沒(méi)有用Handler而是用了線程池去處理事件,具體就不看了。
對(duì)了,還有個(gè)發(fā)送粘性事件:
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriberwants to remove immediately
post(event);
}
就是在stickyEvents這個(gè)map里存一下。
好了,完了。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/7397.html
摘要:進(jìn)入源碼分析我們從的注冊(cè)開(kāi)始入手。關(guān)閉此功能將改善事件的發(fā)布。創(chuàng)建線程池我們將此段代碼逐步分析這步主要是進(jìn)行初始化話一下必要的參數(shù),如代碼注解所示。必須在線程同步中進(jìn)行。必須保持線程安全的,所以這里使用了。 簡(jiǎn)介 前面我學(xué)習(xí)了如何使用EventBus,還有了解了EventBus的特性,那么接下來(lái)我們一起來(lái)學(xué)習(xí)EventBus的源碼,查看EventBus的源碼,看看EventBus給我們...
摘要:音樂(lè)團(tuán)隊(duì)分享數(shù)據(jù)綁定運(yùn)行機(jī)制分析一個(gè)項(xiàng)目搞定所有主流架構(gòu)單元測(cè)試一個(gè)項(xiàng)目搞定所有主流架構(gòu)系列的第二個(gè)項(xiàng)目。代碼開(kāi)源,展示了的用法,以及如何使用進(jìn)行測(cè)試,還有用框架對(duì)的進(jìn)行單元測(cè)試。 Android 常用三方框架的學(xué)習(xí) Android 常用三方框架的學(xué)習(xí) likfe/eventbus3-intellij-plugin AS 最新可用 eventbus3 插件,歡迎品嘗 簡(jiǎn)單的 MVP 模...
閱讀 3670·2023-04-25 20:09
閱讀 3831·2022-06-28 19:00
閱讀 3193·2022-06-28 19:00
閱讀 3227·2022-06-28 19:00
閱讀 3341·2022-06-28 19:00
閱讀 2999·2022-06-28 19:00
閱讀 3236·2022-06-28 19:00
閱讀 2777·2022-06-28 19:00