Android消息機(jī)制源碼分析

作者簡(jiǎn)介? 原創(chuàng)微信公眾號(hào)郭霖 WeChat ID: guolin_blog

本篇是小楠的第二篇投稿,從源碼的角度分析了Handler機(jī)制,因?yàn)樯婕霸创a,內(nèi)容還是挺多的,需要花費(fèi)一些時(shí)間來(lái)閱讀理解了。最后,希望對(duì)大家有所幫助。

小楠的博客地址:

http://www.itdecent.cn/u/70c12759d4fe

前言

很多讀者,尤其是初學(xué)者特別抗拒去看源碼,這里我說(shuō)明一下為什么要進(jìn)行源碼分析。其中包括下面一些好處:

學(xué)習(xí)Android源碼有助于我們學(xué)習(xí)其中的設(shè)計(jì)模式、思想、架構(gòu)。

熟悉整個(gè)源碼的架構(gòu),有助于我們更加正確地調(diào)用Android提供的SDK,寫(xiě)出高效正確的代碼。

學(xué)習(xí)源碼有助于我們面試,因?yàn)榇蠊径枷矚g問(wèn)這些。

學(xué)習(xí)源碼有助于我們學(xué)習(xí)一些黑科技,比如學(xué)習(xí)插件化的從時(shí)候我們需要學(xué)習(xí)Hook機(jī)制,但是學(xué)習(xí)Hook機(jī)制的時(shí)候我們需要掌握Activity的啟動(dòng)流程、消息機(jī)制等等機(jī)制。

我個(gè)人覺(jué)得,只懂得去調(diào)用API,跟掌握API底層的實(shí)現(xiàn),這是一個(gè)碼農(nóng)跟高級(jí)工程師的區(qū)別。只會(huì)用API每天只能做很多重復(fù)性的工作,但是學(xué)習(xí)了源碼以后,我們能夠做到很多原生API做不到的事情,這就是我們所說(shuō)的黑科技,這樣能夠讓我們的知識(shí)面更加廣泛,因?yàn)?,即使一個(gè)人天資再好也罷,如果他的見(jiàn)識(shí)面不夠廣泛,很多東西(比如說(shuō)熱更新、插件化、NDK)沒(méi)有接觸過(guò)的話,永遠(yuǎn)只能停留在他所到達(dá)的瓶頸上面。

對(duì)于像本人一樣在做系統(tǒng)APP、系統(tǒng)Framework層開(kāi)發(fā)和定制來(lái)說(shuō)源碼可能比較重要,但是這并不意味著做第三方APP的時(shí)候就不重要。當(dāng)然,學(xué)習(xí)源碼需要有一定的耐心,也可能需要你在分析的過(guò)程中去畫(huà)一些圖(圖片更加直觀)、花額外的時(shí)間去學(xué)習(xí)源碼用到的設(shè)計(jì)模式等等,學(xué)習(xí)源碼是一個(gè)比較痛苦的事情,因?yàn)槟銜?huì)發(fā)現(xiàn)掌握了源碼并不意味者你就能夠一步登天。但是隨著親們慢慢地掌握了整個(gè)Android的系統(tǒng)架構(gòu)的時(shí)候,相信你不會(huì)后悔當(dāng)初自己的付出。因?yàn)槲乙恢倍枷嘈?,付出必定?huì)有所收獲。

這里扯個(gè)題外話,剛剛提到NDK,我覺(jué)得NDK也是一塊比較重要的模塊,它能夠利用C/C++來(lái)實(shí)現(xiàn)Java實(shí)現(xiàn)不了,或者用Java去實(shí)現(xiàn)的時(shí)候效率很低的事情,比如說(shuō)QQ的變聲功能、全民K歌的音頻混合、視頻處理、直播等等,所以有時(shí)間的話我將會(huì)寫(xiě)一些關(guān)于NDK的文章。

應(yīng)用程序的入口分析

應(yīng)用程序的入口是 在ActivityThread 的 main 方法中的(當(dāng)應(yīng)用程序啟動(dòng)的時(shí)候,會(huì)通過(guò)底層的C/C++去調(diào)用main方法),這個(gè)方法在 ActivityThread類(lèi) 的最后一個(gè)函數(shù)里面,核心代碼如下:

在分析源碼的時(shí)候,你可能會(huì)發(fā)現(xiàn)一些 if(false){} 之類(lèi)的語(yǔ)句,這種寫(xiě)法是方便調(diào)試的,通過(guò)一個(gè)標(biāo)志就可以控制某些代碼是否執(zhí)行,比如說(shuō)是否輸出一些系統(tǒng)的Log。

在 main 方法里面,首先初始化了我們的 Environment對(duì)象,然后創(chuàng)建了 Looper,然后開(kāi)啟消息循環(huán)。根據(jù)我們的常識(shí)知道,如果程序沒(méi)有死循環(huán)的話,執(zhí)行完 main函數(shù)(比如構(gòu)建視圖等等代碼)以后就會(huì)立馬退出了。之所以我們的APP能夠一直運(yùn)行著,就是因?yàn)?Looper.loop() 里面是一個(gè)死循環(huán):

publicstaticvoidloop() {

for(;;) { ? ?}}

這里有一個(gè)小小的知識(shí),就是之所以用 for (;;) 而不是用 while(true) 是因?yàn)榉乐挂恍┤送ㄟ^(guò)黑科技去修改這個(gè)循環(huán)的標(biāo)志(比如通過(guò)反射的方式)

在非主線程里面我們也可以搞一個(gè) Handler,但是需要我們主動(dòng)去為當(dāng)前的子線程綁定一個(gè) Looper,并且啟動(dòng)消息循環(huán)。

Looper 主要有兩個(gè)核心的方法,一是 prepare,而是開(kāi)始 loop 循環(huán)。通過(guò) Looper、Handler、Message、MessageQueue 等組成了 Android 的消息處理機(jī)制,也叫事件、反饋機(jī)制。

為什么需要這樣一個(gè)消息機(jī)制

我們知道每一個(gè)應(yīng)用程序都有一個(gè)主線程,主線程一直循環(huán)的話,那么我們的自己的代碼就無(wú)法執(zhí)行了。而系統(tǒng)在主線程綁定一個(gè) Looper 循環(huán)器以及消息隊(duì)列,Looper 就像是一個(gè)水泵一樣不斷把消息發(fā)送到主線程。如果沒(méi)有消息機(jī)制,我們的代碼需要直接與主線程進(jìn)行訪問(wèn),操作,切換,訪問(wèn)主線程的變量等等,這樣做會(huì)帶來(lái)不安全的問(wèn)題,另外APP的開(kāi)發(fā)的難度也會(huì)提高,同時(shí)也不利于整個(gè) Android 系統(tǒng)的運(yùn)作。有了消息機(jī)制,我們可以簡(jiǎn)單地通過(guò)發(fā)送消息,然后 Looper 把消息發(fā)送到主線程,然后就可以執(zhí)行了。

消息其中包括:

我們自己的操作消息(客戶(hù)端的 Handler)

系統(tǒng)的操作消息(系統(tǒng) Handler):比如啟動(dòng) Activity 等四大組件(例如突然來(lái)電話的時(shí)候跳轉(zhuǎn)到電話界面)

我們的思路是先分析系統(tǒng)的 Handler,然后再去深入理解消息機(jī)制里面各個(gè)部件。

主線程與Looper的關(guān)系

舉個(gè)例子,廣播:AMS 發(fā)送消息到 MessageQueue,然后 Looper 循環(huán),系統(tǒng)的 Handler 取出來(lái)以后才處理。(AMS 是處理四大組件的生命周期的一個(gè)比較重要的類(lèi),在以后我們分析IPC機(jī)制以及Activity啟動(dòng)流程的時(shí)候會(huì)提到)

系統(tǒng)的Handler在哪里

在 ActivityThread 的成員變量里面有一個(gè)這樣的 大H(繼承Handler),這個(gè)就是系統(tǒng)的Handler:

finalHmH=newH();

回顧一下 ActivityThread 的 main 方法可以知道,在 new ActivityThread 的時(shí)候,系統(tǒng)的 Handler 就就初始化了,這是一種餓加載的方法,也就是在類(lèi)被new的時(shí)候就初始化成員變量了。另外還有一種懶加載,就是在需要的時(shí)候才去初始化,這兩種方式在單例設(shè)計(jì)模式里面比較常見(jiàn)。

下面看系統(tǒng) Handler 的定義(下方高能,看的時(shí)候可以跳過(guò)一些case,粗略地看即可):

從系統(tǒng)的 Handler 中,在 handleMessage 我們可以看到很多關(guān)于四大組件的生命周期操作,比如創(chuàng)建、銷(xiāo)毀、切換、跨進(jìn)程通信,也包括了整個(gè)Application進(jìn)程的銷(xiāo)毀等等。

比如說(shuō)我們有一個(gè) 應(yīng)用程序A 通過(guò) Binder 去跨進(jìn)程啟動(dòng)另外一個(gè) 應(yīng)用程序B 的 Service(或者同一個(gè)應(yīng)用程序中不同進(jìn)程的Service),如圖:

跨進(jìn)程啟動(dòng)Service

最后是 AMS 接收到消息以后,發(fā)送消息到 MessageQueue 里面,最后由系統(tǒng)的 Handler 處理啟動(dòng) Service 的操作:

在 handleCreateService 里通過(guò)反射的方式去 newInstance(),并且回調(diào)了 Service 的 onCreate方法:

又例如我們可以通過(guò)發(fā)SUICIDE消息可以自殺,這樣來(lái)退出應(yīng)用程序。

caseSUICIDE:Process.killProcess(Process.myPid());

break;

應(yīng)用程序的退出過(guò)程

實(shí)際上我們要退出應(yīng)用程序的話,就是讓主線程結(jié)束,換句話說(shuō)就是要讓 Looper 的循環(huán)結(jié)束。這里是直接結(jié)束 Looper 循環(huán),因此我們四大組件的生命周期方法可能就不會(huì)執(zhí)行了,因?yàn)樗拇蠼M件的生命周期方法就是通過(guò) Handler 去處理的,Looper 循環(huán)都沒(méi)有了,四大組件還玩毛線!因此我們平常寫(xiě)程序的時(shí)候就要注意了,onDestroy 方法是不一定能夠回調(diào)的。

這里實(shí)際上是調(diào)用了 MessageQueue 的 quit,清空所有 Message。

publicvoidquit() { ? ?mQueue.quit(false);}

tips:看源碼一定不要慌,也不要一行一行看,要抓住核心的思路去看即可。

消息機(jī)制的分析

消息對(duì)象Message的分析

提到消息機(jī)制,在 MessageQueue 里面存在的就是我們的 Message對(duì)象:

首先我們可以看到 Message 對(duì)象是實(shí)現(xiàn)了 Parcelable 接口的,因?yàn)?Message 消息可能需要跨進(jìn)程通信,這時(shí)候就需要進(jìn)程序列化以及反序列化操作了。

Message 里面有一些我們常見(jiàn)的參數(shù),arg1 arg2 obj callback when 等等。這里要提一下的就是這個(gè) target 對(duì)象,這個(gè)對(duì)象就是發(fā)送這個(gè)消息的 Handler對(duì)象,最終這條消息也是通過(guò)這個(gè) Handler 去處理掉的。

Message Pool消息池的概念——重復(fù)利用Message

Message里面中一個(gè)非常重要的概念,就是消息池Pool:

我們通過(guò)obtain方法取出一條消息的時(shí)候,如果發(fā)現(xiàn)當(dāng)前的消息池不為空,那就直接重復(fù)利用Message(已經(jīng)被創(chuàng)建過(guò)和handle過(guò)的);如果為空就重新 new 一個(gè)消息。這就是一種享元設(shè)計(jì)模式的概念。例如在游戲里面,發(fā)子彈,如果一個(gè)子彈是一個(gè)對(duì)象,一按下按鍵就發(fā)很多個(gè)子彈,那么這時(shí)候就需要利用享元模式去循環(huán)利用了。

這個(gè)消息池是通過(guò)鏈表的實(shí)現(xiàn)的,通過(guò)上面的代碼可以知道,sPool永遠(yuǎn)指向這個(gè)消息池的頭,取消息的時(shí)候,先拿到當(dāng)前的頭sPool,然后使得sPool指向下一個(gè)結(jié)點(diǎn),最后返回剛剛?cè)〕鰜?lái)的結(jié)點(diǎn),如下圖所示:

上面我們知道了消息可以直接創(chuàng)建,也可以通過(guò)obtain方法循環(huán)利用。所以我們平常編程的時(shí)候就要養(yǎng)成好的習(xí)慣,循環(huán)利用。

消息的回收機(jī)制

有消息的創(chuàng)建,必然有回收利用,下面兩個(gè)是Message的回收相關(guān)的核心方法:

recycleUnchecked 中拿到消息池,清空當(dāng)前的消息,next 指向當(dāng)前的頭指針,頭指針指向當(dāng)前的 Message對(duì)象,也就是在消息池頭部插入當(dāng)前的消息。

關(guān)于消息的回收還有一點(diǎn)需要注意的就是,我們平時(shí)寫(xiě) Handler 的時(shí)候不需要我們手動(dòng)回收,因?yàn)楣雀璧墓こ處熞呀?jīng)有考慮到這方面的問(wèn)題了。消息是在 Handler 分發(fā)處理之后就會(huì)被自動(dòng)回收的,我們回到 Looper 的 loop方法 里面:

msg.target.dispatchMessage(msg) 就是處理消息,緊接著在 loop方法 的最后調(diào)用了msg.recycleUnchecked() 這就是回收了 Message。

消息的循環(huán)過(guò)程分析

下面我們繼續(xù)分析這個(gè)死循環(huán):

1、首先拿到 Looper 對(duì)象(me),如果當(dāng)前的線程沒(méi)有 Looper,那么就會(huì)拋出異常,這就是為什么在子線程里面創(chuàng)建Handler如果不手動(dòng)創(chuàng)建和啟動(dòng) Looper 會(huì)報(bào)錯(cuò)的原因。

2、然后拿到 Looper 的成員變量 MessageQueue,在 MessageQueue 里面不斷地去取消息,關(guān)于 MessageQueue 的 next方法 如下:

這里可以看到消息的取出用到了一些native方法,這樣做是為了獲得更高的效率,消息的去取出并不是直接就從隊(duì)列的頭部取出的,而是根據(jù)了消息的when時(shí)間參數(shù)有關(guān)的,因?yàn)槲覀兛梢园l(fā)送延時(shí)消息、也可以發(fā)送一個(gè)指定時(shí)間點(diǎn)的消息。因此這個(gè)函數(shù)有點(diǎn)復(fù)雜,我們點(diǎn)到為止即可。

3、繼續(xù)分析 loop方法:如果已經(jīng)沒(méi)有消息了,那么就可以退出循環(huán),那么整個(gè)應(yīng)用程序就退出了。什么情況下會(huì)發(fā)生呢?還記得我們分析應(yīng)用退出嗎?

在 系統(tǒng)Handler 收到 EXIT_APPLICATION 消息的時(shí)候,就會(huì)調(diào)用 Looper 的 quit方法:

Looper 的 quit方法 如下,實(shí)際上就是調(diào)用了消息隊(duì)列的 quit方法:

publicvoidquit() { ? ?mQueue.quit(false);}

而消息隊(duì)列的 quit方法 實(shí)際上就是執(zhí)行了消息的清空操作,然后在 Looper 循環(huán)里面如果取出消息為空的時(shí)候,程序就退出了:

removeAllFutureMessagesLocked 方法如下:

4、msg.target.dispatchMessage(msg)就是處理消息,這里就會(huì)調(diào)用Handler的dispatchMessage方法:

在這個(gè)方法里面會(huì)先去判斷 Message 的 callback 是否為空,這個(gè) callback 是在 Message類(lèi) 里面定義的:

Runnablecallback;

這是一個(gè) Runnable對(duì)象,handleCallback方法 里面做的事情就是拿到這個(gè) Runnable 對(duì)象,然后在 Handler 所創(chuàng)建的線程(例如主線程)執(zhí)行run方法:

Handler(Looper)在哪個(gè)線程創(chuàng)建的,就在哪個(gè)線程回調(diào),沒(méi)毛病,哈哈!

這就是我們平常使用post系列的方法:post、postAtFrontOfQueue、postAtTime、postDelayed。其實(shí)最終也是通過(guò)Message包裝一個(gè)Runnable實(shí)現(xiàn)的,我們看其中一個(gè)即可:

通過(guò) post 一個(gè)Runnable的方式我們可以很簡(jiǎn)單地做一個(gè)循環(huán),比如無(wú)限輪播的廣告條Banner:

當(dāng)然,我們的 Handler 自己也可以有一個(gè) mCallback 對(duì)象:

如果自身的 Callback 不為空的話,就會(huì)回調(diào) Callback 的方法。例如我們創(chuàng)建 Handler 的時(shí)候可以帶上 Callback:

如果自身的 Callback 執(zhí)行之后沒(méi)有返回 true(沒(méi)有攔截),那么最后才會(huì)回調(diào)我們經(jīng)常需要復(fù)寫(xiě)的 handleMessage 方法,這個(gè)方法的默認(rèn)實(shí)現(xiàn)是空處理:

publicvoidhandleMessage(Messagemsg) {}

5、最后是回收消息:msg.recycleUnchecked()。所以說(shuō):我們平時(shí)在處理完handleMessage之后并不需要我們程序員手動(dòng)去進(jìn)行回收哈!系統(tǒng)已經(jīng)幫我們做了這一步操作了。

6、通過(guò)上面就完成了一次消息的循環(huán)。

消息的發(fā)送

分析完消息的分發(fā)與處理,最后我們來(lái)看看消息的發(fā)送:

消息的發(fā)送有這一系列方法,甚至我們的一系列post方法(封裝了帶Runnable的Message),最終都是調(diào)用sendMessageAtTime方法,把消息放到消息隊(duì)列里面:

MessageQueue的進(jìn)入隊(duì)列的方法如下,核心思想就是時(shí)間比較小的(越是需要馬上執(zhí)行的消息)就越防到越靠近頭指針的位置:

消息并不是一直在隊(duì)列的尾部添加的,而是可以指定時(shí)間,如果是立馬需要執(zhí)行的消息,就會(huì)插到隊(duì)列的頭部,就會(huì)立馬處理,如此類(lèi)推。

關(guān)于這一點(diǎn)這里我們可以從MessageQueue的next方法知道,next是考慮消息的時(shí)間when變量的,下面回顧一下MessageQueue的next方法里面的一些核心代碼:next方法并不是直接從頭部取出來(lái)的,而是會(huì)去遍歷所有消息,根據(jù)時(shí)間戳參數(shù)等信息來(lái)取消息的。

線程與Looper的綁定

線程里面默認(rèn)情況下是沒(méi)有 Looper 循環(huán)器的,因此我們需要調(diào)用 prepare方法 來(lái)關(guān)聯(lián)線程和 Looper:

此處調(diào)用了?ThreadLocal 的 set方法,并且 new 了一個(gè) Looper 放進(jìn)去。

可以看到 Looper 與線程的關(guān)聯(lián)是通過(guò) ThreadLocal 來(lái)進(jìn)行的,如下圖所示:

ThreadLocal 是JDK提供的一個(gè)解決線程不安全的類(lèi),線程不安全問(wèn)題歸根結(jié)底主要涉及到變量的多線程訪問(wèn)問(wèn)題,例如變量的臨界問(wèn)題、值錯(cuò)誤、并發(fā)問(wèn)題等。這里利用ThreadLocal 綁定了 Looper 以及線程,就可以避免其他線程去訪問(wèn)當(dāng)前線程的 Looper 了。

ThreadLocal 通過(guò) get 以及 set方法 就可以綁定線程和 Looper 了,這里只需要傳入 Value 即可,因?yàn)榫€是可以通過(guò) Thread.currentThread() 去拿到的:

為什么可以綁定線程了呢?

map.set(this, value) 通過(guò)把自身(ThreadLocal以及值(Looper)放到了一個(gè)Map里面,如果再放一個(gè)的話,就會(huì)覆蓋,因?yàn)閙ap不允許鍵值對(duì)中的鍵是重復(fù)的)

因此ThreadLocal綁定了線程以及Looper。

因?yàn)檫@里實(shí)際上把變量(這里是指Looper)放到了Thread一個(gè)成員變量Map里面,關(guān)鍵的代碼如下:

ThreadLocal的getMap方法實(shí)際上是拿到線程的MAP,底層是通過(guò)數(shù)組(實(shí)際上數(shù)據(jù)結(jié)構(gòu)是一種散列列表)實(shí)現(xiàn)的,具體的實(shí)現(xiàn)就點(diǎn)到為止了。

如果android系統(tǒng)主線程Looper可以隨隨便便被其他線程訪問(wèn)到的話就會(huì)很麻煩了,啊哈哈,你懂的。

Handler、Looper是怎么關(guān)聯(lián)起來(lái)的呢?

我們知道,Looper是與線程相關(guān)聯(lián)的(通過(guò)ThreadLocal),而我們平常使用的Handler是這樣的:

其實(shí) Handler 在構(gòu)造的時(shí)候,有多個(gè)重載方法,根據(jù)調(diào)用關(guān)系鏈,所以最終會(huì)調(diào)用下面這個(gè)構(gòu)造方法:

這里只給出了核心的代碼,可以看到我們?cè)跇?gòu)造Handler的時(shí)候,是通過(guò)Looper的靜態(tài)方法myLooper()去拿到一個(gè)Looper對(duì)象的:

看,我們的又出現(xiàn)了ThreadLocal,這里就是通過(guò)ThreadLocal的get方法去拿到當(dāng)前線程的Looper,因此Handler就跟線程綁定在一起了,在一起,在一起,啊哈哈。

一般們是在Activity里面使用Handler的,而Activity的生命周期是在主線程回調(diào)的,因此我們一般使用的Handler是跟主線程綁定在一起的。

主線程一直在循環(huán),為什么沒(méi)有卡死,還能響應(yīng)我們的點(diǎn)擊之類(lèi)的呢?

通過(guò)子線程去訪問(wèn)主線程的代碼,有代碼注入、回調(diào)機(jī)制嘛。

切入到消息隊(duì)列里面的消息去訪問(wèn)主線程,例如傳消息,然后回調(diào)四大組件的生命周期等等。

IPC跨進(jìn)程的方式也可以實(shí)現(xiàn)。

雖然主線程一直在執(zhí)行,但是我們可以通過(guò)外部條件、注入的方法來(lái)執(zhí)行自己的代碼,而不是一直死循環(huán)。

總結(jié)

如圖所示,在主線程ActivityThread中的main方法入口中,先是創(chuàng)建了系統(tǒng)的Handler(H),創(chuàng)建主線程的Looper,將Looper與主線程綁定,調(diào)用了Looper的loop方法之后開(kāi)啟整個(gè)應(yīng)用程序的主循環(huán)。Looper里面有一個(gè)消息隊(duì)列,通過(guò)Handler發(fā)送消息到消息隊(duì)列里面,然后通過(guò)Looper不斷去循環(huán)取出消息,交給Handler去處理。通過(guò)系統(tǒng)的Handler,或者說(shuō)Android的消息處理機(jī)制就確保了整個(gè)Android系統(tǒng)有條不紊地運(yùn)作,這是Android系統(tǒng)里面的一個(gè)比較重要的機(jī)制。

我們的APP也可以創(chuàng)建自己的Handler,可以是在主線程里面創(chuàng)建,也可以在子線程里面創(chuàng)建,但是需要手動(dòng)創(chuàng)建子線程的Looper并且手動(dòng)啟動(dòng)消息循環(huán)。

花了一天的時(shí)間,整個(gè)Android消息機(jī)制源碼分析就到這里結(jié)束了,今天的天氣真不錯(cuò),但是我選擇了在自己的房間學(xué)習(xí)Android的消息機(jī)制,我永遠(yuǎn)相信,付出總會(huì)有所收獲的!


文章原創(chuàng)作者GuoLin 書(shū)籍推薦

郭林大神原創(chuàng)android 書(shū)籍:《第一行代碼 android》

淘寶鏈接:

https://s.click.taobao.com/t?e=m%3D2%26s%3DgKUfuKdAZKocQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67p2n%2BQBNMyE6Rku8%2Bpj6eJall3bs%2B3NRhNHnsKI%2BqxhyM0iVZhTFBom4YIorMPnmg8G0g2OJi%2FzmXHfenomYtn5EW9vzeG8LzfPUwktUBEmkxg5p7bh%2BFbQ%3D&pvid=10_106.6.161.154_3367_1490163222155

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容