Activity與調(diào)用線(三):Activity生命周期源碼解析

前言

很高興遇見你~ 歡迎閱讀我的文章呈野。

關(guān)于Activity生命周期的文章例获,網(wǎng)絡(luò)上真的很多汉额,有很多的博客也都講得相當(dāng)不錯,可見Activity的重要性是非常高的榨汤。事實上蠕搜,我猜測每個android開發(fā)者接觸的第一個android組件都是Activity。我們從新建第一個Activity開始收壕,運行了代碼妓灌,看到模擬機(jī)上顯示了一個MainActivity標(biāo)題和一行HolleWorld,從此打開Android世界的大門蜜宪。

本篇文章講解的重點是Activity的生命周期虫埂,在文章的最后也會涉及Activity的設(shè)計。不同于其他的博客設(shè)計圃验,文章采用系統(tǒng)化的講解掉伏,關(guān)于Activity生命周期的相關(guān)知識基本都會涉及到。

文章第一部分講解關(guān)于Activity狀態(tài)的認(rèn)知澳窑;
第二部分全面講解activity生命周期回調(diào)方法斧散;
第三部分是分析不同情景下的生命周期回調(diào)順序:
第四部分是原碼分析;
最后一部分是從更高的角度來思考activity以及生命周期摊聋。

文章全面的同時挣磨,免不了篇幅過長榜聂,造成閱讀障礙沿后。讀者可先觀看目錄彪蓬,查找最感興趣的章節(jié)閱讀,如若有時間悲立,仍建議閱讀全文。

這是筆者Android全面解析系列的新一篇文章新博。前幾篇文章在公眾號發(fā)布時薪夕,雖然受到了一些好評,但也被詬病文章實在是太長了赫悄。筆者也思考了是否刪減章節(jié)原献、切割部分等等馏慨。后來思考了一下,我寫這一系列文章的初衷就是為了能夠系統(tǒng)地講解每個知識點姑隅,而不是零散地介紹某個孤立的點写隶。筆者更喜歡看書的原因,是書本會更加系統(tǒng)全面地講每個知識點讲仰,讓我對整個知識有系統(tǒng)的認(rèn)知慕趴,而不像網(wǎng)絡(luò)博客,把每個知識點都孤立起來了鄙陡,很難做到有系統(tǒng)的感知冕房。

本文長達(dá)一萬多字,生命周期相關(guān)的知識點都有講到趁矾。限于筆者的認(rèn)知耙册,有一些地方講解錯誤或者沒有涉及到,歡迎評論區(qū)留言一起探討毫捣,共同打造一篇可以幫助到更多讀者的文章详拙。

那么,我們開始吧蔓同。

生命狀態(tài)概述

Activity是一個很重要饶辙、很復(fù)雜的組件,他的啟動不像我們平時直接new一個對象就完事了牌柄,他需要經(jīng)歷一系列的初始化畸悬。例如"剛創(chuàng)建狀態(tài)",“后臺狀態(tài)”珊佣,“可見狀態(tài)”等等蹋宦。當(dāng)我們在界面之間進(jìn)行切換的時候,activity也會在多種狀態(tài)之間進(jìn)行切換咒锻,例如可見或者不可見狀態(tài)冷冗、前臺或者后臺狀態(tài)。當(dāng)Activity在不同的狀態(tài)之間切換時惑艇,會回調(diào)不同的生命周期方法蒿辙。我們可以重寫這一些方法,當(dāng)進(jìn)入不同的狀態(tài)的時候滨巴,執(zhí)行對應(yīng)的邏輯思灌。

在ActivityLifecycleItem`抽象類中定義了9種狀態(tài)。這個抽象類有很多的子類恭取,是AMS管理Activity生命周期的事務(wù)類泰偿。(其實就像一個圣旨,AMS丟給應(yīng)用程序蜈垮,那么應(yīng)用程序就必須執(zhí)行這個圣旨)Activity主要使用其中6個(這里的6個是筆者在源碼中明確看到調(diào)用setState來設(shè)置狀態(tài)耗跛,其他的三種并未看到調(diào)用setState方法來設(shè)置狀態(tài)裕照,所以這里主要講這6種),如下:

// Activity剛被創(chuàng)建時
public static final int ON_CREATE = 1;
// 執(zhí)行完轉(zhuǎn)到前臺的最后準(zhǔn)備工作
public static final int ON_START = 2;
// 執(zhí)行完即將與用戶交互的最后準(zhǔn)備工作
// 此時該activity位于前臺
public static final int ON_RESUME = 3;
// 用戶離開调塌,activity進(jìn)入后臺
public static final int ON_PAUSE = 4;
// activity不可見
public static final int ON_STOP = 5;
// 執(zhí)行完被銷毀前最后的準(zhǔn)備工作
public static final int ON_DESTROY = 6;

狀態(tài)之間的跳轉(zhuǎn)不是隨意的晋南,例如不能從ON_CREATE直接跳轉(zhuǎn)到ON_PAUSE狀態(tài),狀態(tài)之間的跳轉(zhuǎn)收到AMS的管理羔砾。當(dāng)Activity在這些狀態(tài)之間切換的時候负间,就會回調(diào)對應(yīng)的生命周期。這里的狀態(tài)看著很不好理解蜒茄,筆者畫了個圖幫助理解一下:

image

這里按照「可交互」「可見」「可存在」三個維度來區(qū)分Activity的生命狀態(tài)唉擂。可交互則為是否可以與用戶操作檀葛;可見則為是否顯示在屏幕上玩祟;可存在,則為該activity是否被系統(tǒng)殺死或者調(diào)用了finish方法屿聋。箭頭的上方為進(jìn)入對應(yīng)狀態(tài)會調(diào)用的方法空扎。這里就先不展開講每個狀態(tài)之間的切換,主要是讓讀者可以更好地理解activity的狀態(tài)與狀態(tài)切換润讥。

注意转锈,這里使用的三個維度并不是非常嚴(yán)謹(jǐn)?shù)模墙Y(jié)合總體的顯示規(guī)則來進(jìn)行區(qū)分的楚殿。

在谷歌的官方文檔中對于onStart方法是這樣描述的:onStart() 調(diào)用使 Activity 對用戶可見撮慨,因為應(yīng)用會為 Activity 進(jìn)入前臺并支持互動做準(zhǔn)備。這也符合我們上面的維度的區(qū)分脆粥。而當(dāng)activity進(jìn)入ON_PAUSE狀態(tài)的時候砌溺,Activity是可能依舊可見的,但是不可交互变隔。如操作另一個應(yīng)用的懸浮窗口的時候规伐,當(dāng)前應(yīng)用的activity會進(jìn)入ON_PAUSE狀態(tài)。

但是匣缘!在activity啟動的流程中猖闪,直到onResume方法被調(diào)用,界面依舊是不可見的肌厨。這點在后面的源碼分析再詳細(xì)解釋培慌。所以這里的狀態(tài)區(qū)分維度,僅僅只是總體上的一種區(qū)分柑爸,可以這么認(rèn)為检柬,但細(xì)節(jié)上并不是非常嚴(yán)謹(jǐn)?shù)摹P枰x者注意一下。

生命周期的一個重要作用就是讓activity在不同狀態(tài)之間切換的時候何址,可以執(zhí)行對應(yīng)的邏輯。舉個栗子进胯。我們在界面A使用了相機(jī)資源用爪,當(dāng)我們切換到下個界面B的時候,那么界面A就必須釋放相機(jī)資源胁镐,這樣才不會導(dǎo)致界面B無法使用相機(jī)偎血;而當(dāng)我們切回界面A的時候,又希望界面A繼續(xù)保持擁有相機(jī)資源的狀態(tài)盯漂;那么我們就需要在界面不可見的時候釋放相機(jī)資源颇玷,而在界面恢復(fù)的時候再次獲取相機(jī)資源。每個Activity一般情況下可以認(rèn)為是一個界面或者說就缆,一個屏幕帖渠。當(dāng)我們在界面之間進(jìn)行導(dǎo)航切換的時候,其實就是在切換Activity竭宰。當(dāng)界面在不同狀態(tài)之間進(jìn)行切換的時候空郊,也就是Activity狀態(tài)的切換,就會回調(diào)activity相關(guān)的方法切揭。例如當(dāng)界面不可見的時候會回調(diào)onStop方法狞甚,恢復(fù)的時候會回調(diào)onReStart方法等。

在合適的生命周期做合適的工作會讓app變得更加有魯棒性廓旬。避免當(dāng)用戶跳轉(zhuǎn)別的app的時候發(fā)生崩潰哼审、內(nèi)存泄露、當(dāng)用戶切回來的時候失去進(jìn)度孕豹、當(dāng)用戶旋轉(zhuǎn)屏幕的時候失去進(jìn)度或者崩潰等等涩盾。這些都需要我們對生命周期有一定的認(rèn)知,才能在具體的場景下做出最正確的選擇巩步。

這一部分概述并沒有展開講生命周期旁赊,而是需要重點理解狀態(tài)與狀態(tài)之間的切換,生命周期的回調(diào)就發(fā)生在不同的狀態(tài)之間的切換椅野。我們學(xué)習(xí)生命周期的一個重要目的就是能夠在對應(yīng)的業(yè)務(wù)場景下做合適的工作终畅,例如資源的申請、釋放竟闪、存儲离福、恢復(fù),讓app更加具有魯棒性炼蛤。

重要生命周期解析

關(guān)于Activity重要的生命周期回調(diào)方法谷歌官方有了一張非常重要的流程圖妖爷,可以說是人人皆知。我在這張圖上加上了一些常用的回調(diào)方法。這些方法嚴(yán)格上并不算Activity的生命周期絮识,因為并沒有涉及到狀態(tài)的切換绿聘,但卻在Activity的整個生命歷程中發(fā)揮了非常大的作用,也是很重要的回調(diào)方法次舌。方法很多熄攘,我們先看圖,再一個個地解釋彼念∨不看具體方法解釋的時候一定要結(jié)合下面這張圖以及上一部分概述的圖一起理解。

image

主要生命周期

首先我們先看到最重要的七個生命周期逐沙,這七個生命周期是嚴(yán)格意義上的生命周期哲思,他符合狀態(tài)切換這個關(guān)鍵定義。這部分內(nèi)容建議結(jié)合概述部分的圖一起理解吩案。(onRestart并不涉及狀態(tài)變換棚赔,但因為執(zhí)行完他之后會馬上執(zhí)行onStart,所以也放在一起講)

  • onCreate:當(dāng)Activity創(chuàng)建實例完成务热,并調(diào)用attach方法賦值PhoneWindow忆嗜、ContextImpl等屬性之后,調(diào)用此方法崎岂。該方法在整個Activity生命周期內(nèi)只會調(diào)用一次捆毫。調(diào)用該方法后Activity進(jìn)入ON_CREATE狀態(tài)。

    該方法是我們使用最頻繁的一個回調(diào)方法冲甘。

    我們需要在這個方法中初始化基礎(chǔ)組件和視圖绩卤。如viewmodel,textview江醇。同時必須在該方法中調(diào)用setContentView來給activity設(shè)置布局濒憋。

    這個方法接收一個參數(shù),該參數(shù)保留之前狀態(tài)的數(shù)據(jù)陶夜。如果是第一次啟動凛驮,則該參數(shù)為空。該參數(shù)來自onSaveInstanceState存儲的數(shù)據(jù)条辟。只有當(dāng)activity暫時銷毀并且預(yù)期一定會被重新創(chuàng)建的時候才會被回調(diào)黔夭,如屏幕旋轉(zhuǎn)、后臺應(yīng)用被銷毀等

  • onStart:當(dāng)Activity準(zhǔn)備進(jìn)入前臺時會調(diào)用此方法羽嫡。調(diào)用后Activity會進(jìn)入ON_START狀態(tài)本姥。

    要注意理解這里的前臺的意思。雖然谷歌文檔中表示調(diào)用該方法之后activity可見杭棵,如下圖:

    <img src="https://s1.ax1x.com/2020/11/06/Bh0Uy9.png" width=700 border="0" />

    但是我們前面講到婚惫,前臺并不意味著Activity可見,只是表示activity處于活躍狀態(tài)。這也是谷歌文檔里讓我比較迷惑的地方之一先舷。(事實上谷歌文檔有挺多地方寫的缺乏嚴(yán)謹(jǐn)艰管,可能考慮到易懂性,就犧牲了一點嚴(yán)謹(jǐn)性吧)密浑。

    前臺activity一般只有一個蛙婴,所以這也意味著其他的activity都進(jìn)入后臺了。這里的前后臺需要結(jié)合activity返回棧來理解尔破,后續(xù)筆者再寫一篇關(guān)于返回棧的。

    這個方法一般用于從別的activity切回來本activity的時候調(diào)用浇衬。

  • onResume:當(dāng)Activity準(zhǔn)備與用戶交互的時候調(diào)用懒构。調(diào)用之后Activity進(jìn)入ON_RESUME狀態(tài)。

    注意耘擂,這個方法一直被認(rèn)為是activity一定可見胆剧,且準(zhǔn)備好與用戶交互的狀態(tài)。但事實并不一直是這樣醉冤。如果在onReume方法中彈出popupWindow你會收獲一個異常:token is null秩霍,表示界面尚沒有被添加到屏幕上。

    但是蚁阳,這種情況只出現(xiàn)在第一次啟動activity的時候铃绒。當(dāng)activity啟動后decorview就已經(jīng)擁有token了,再次在onReume方法中彈出popupWindow就不會出現(xiàn)問題了螺捐。

    因此颠悬,在onResume調(diào)用的時候activity是否可見要區(qū)分是否是第一次創(chuàng)建activity

    onStart方法是后臺與前臺的區(qū)分定血,而這個方法是是否可交互的區(qū)分赔癌。使用場景最多是在當(dāng)彈出別的activity的窗口時,原activity就會進(jìn)入ON_PAUSE狀態(tài)澜沟,但是仍然可見灾票;當(dāng)再次回到原activity的時候,就會回調(diào)onResume方法了茫虽。

  • onPause:當(dāng)前activity窗口失去焦點的時候刊苍,會調(diào)用此方法。調(diào)用后activity進(jìn)入ON_PAUSE狀態(tài)席噩,并進(jìn)入后臺班缰。

    這個方法一般在另一個activity要進(jìn)入前臺前被調(diào)用。只有當(dāng)前activity進(jìn)入后臺悼枢,其他的activity才能進(jìn)入前臺埠忘。所以,該方法不能做重量級的操作,不然則會引用界面切換卡頓莹妒。

    一般的使用場景為界面進(jìn)入后臺時的輕量級資源釋放名船。

    最好理解這個狀態(tài)就是彈出另一個activity的窗口的時候。因為前臺activity只能有一個旨怠,所以當(dāng)前可交互的activity變成另一個activity后渠驼,原activity就必須調(diào)用onPause方法進(jìn)入ON_PAUSE狀態(tài);但是<濉迷扇!仍然是可見的,只是無法進(jìn)行交互爽哎。這里也可以更好地體會前臺可交互與可見性的區(qū)別蜓席。

  • onStop:當(dāng)activity不可見的時候進(jìn)行調(diào)用。調(diào)用后activity進(jìn)入ON_STOP狀態(tài)课锌。

    這里的不可見是嚴(yán)謹(jǐn)意義上的不可見厨内。

    當(dāng)activity不可交互時會回調(diào)onPause方法并進(jìn)入ON_PAUSE狀態(tài)。但如果進(jìn)入的是另一個全屏的activity而不是小窗口渺贤,那么當(dāng)新的activity界面顯示出來的時候雏胃,原Activity才會進(jìn)入ON_STOP狀態(tài),并回調(diào)onStop方法志鞍。同時瞭亮,activity第一創(chuàng)建的時候,界面是在onResume方法之后才顯示出來述雾,所以onStop方法會在新activity的onResume方法回調(diào)之后再被回調(diào)街州。

    注意,被啟動的activity并不會等待onStop執(zhí)行完畢之后再顯示玻孟。因而如果onStop方法里做一些比較耗時的操作也不會導(dǎo)致被啟動的activity啟動延遲唆缴。

    onStop方法的目的就是做資源釋放操作。因為是在另一個activity顯示之后再被回調(diào)黍翎,所以這里可以做一些相對重量級的資源釋放操作面徽,如中斷網(wǎng)絡(luò)請求、斷開數(shù)據(jù)庫連接匣掸、釋放相機(jī)資源等趟紊。

    如果一個應(yīng)用的全部activity都處于ON_STOP狀態(tài),那么這個應(yīng)用是很有可能被系統(tǒng)殺死的碰酝。而如果一個ON_STOP狀態(tài)的activity被系統(tǒng)回收的話霎匈,系統(tǒng)會保留該activity中view的相關(guān)信息到bundle中,下一次恢復(fù)的時候送爸,可以在onCreate或者onRestoreInstanceState中進(jìn)行恢復(fù)铛嘱。

  • onRestart :當(dāng)從另一個activity切回到該activity的時候會調(diào)用暖释。調(diào)用該方法后會立即調(diào)用onStart方法,之后activity進(jìn)入ON_START狀態(tài)墨吓。

    這個方法一般在activity從ON_STOP狀態(tài)被重新啟動的時候會調(diào)用球匕。執(zhí)行該方法后會立即執(zhí)行onStart方法,然后Activity進(jìn)入ON_START狀態(tài)帖烘,進(jìn)入前臺亮曹。

  • onDestroy:當(dāng)activity被系統(tǒng)殺死或者調(diào)用finish方法之后,會回調(diào)該方法秘症。調(diào)用該方法之后activity進(jìn)入ON_DESTROY狀態(tài)照卦。

    這個方法是activity在被銷毀前回調(diào)的最后一個方法。我們需要在這個方法中釋放所有的資源乡摹,防止造成內(nèi)存泄漏問題窄瘟。

    回調(diào)該方法后的activity就等待被系統(tǒng)回收了。如果再次打開該activity需要從onCreate開始執(zhí)行趟卸,重新創(chuàng)建activity。

那到這里七個最關(guān)鍵的生命周期方法就講完了氏义。需要讀者注意的是锄列,在概述一圖中,我們使用了三個維度來進(jìn)行區(qū)分不同類型的狀態(tài)惯悠,但是很明顯邻邮,同一類型的狀態(tài)并不是等價的。如ON_START狀態(tài)表示activity進(jìn)入前臺克婶,而ON_PAUSE狀態(tài)卻表示activity進(jìn)入后臺筒严。這可能也是為什么谷歌要區(qū)分出on_start和on_pause兩個狀態(tài),他們代表并不是一致的狀態(tài)情萤。

這七個生命周期回調(diào)方法是最重要的七個生命周期回調(diào)方法鸭蛙,需要讀者好好理解每個回調(diào)方法設(shè)計到的activity的狀態(tài)轉(zhuǎn)換。而理解了狀態(tài)轉(zhuǎn)換后筋岛,也就可以寫出更加強(qiáng)壯的代碼了娶视。

其他生命周期回調(diào)方法

  • onActivityResult

這個方法也很常見,他需要結(jié)合startActivityForResult一起使用睁宰。

使用的場景是:啟動一個activity肪获,并期望在該activity結(jié)束的時候返回數(shù)據(jù)。

當(dāng)啟動的activity結(jié)束的時候柒傻,返回原activity孝赫,原activity就會回調(diào)onActivityResult方法了。該方法執(zhí)行在其他所有的生命周期方法前红符。關(guān)于onActivityResult如何使用這里就不展開了青柄,我們主要介紹生命周期伐债。

  • onSaveInstanceState/onRestoreInstanceState

這兩個方法,主要用于在Activity被意外殺死的情況下進(jìn)行界面數(shù)據(jù)存儲與恢復(fù)刹前。什么叫意外殺死呢泳赋?

如果你主動點擊返回鍵、調(diào)用finish方法喇喉、從多任務(wù)列表清除后臺應(yīng)用等等祖今,這些操作表示用戶想要完整得退出activity,那么就沒有必要保留界面數(shù)據(jù)了拣技,所以也不會調(diào)用這兩個方法千诬。而當(dāng)應(yīng)用被系統(tǒng)意外殺死,或者系統(tǒng)配置更改導(dǎo)致的activity銷毀膏斤,這個時候當(dāng)用戶返回activity時徐绑,期望界面的數(shù)據(jù)還在,則會通過回調(diào)onSaveInstanceState方法來保存界面數(shù)據(jù)莫辨,而在activity重新創(chuàng)建并運行的時候調(diào)用onRestoreInstanceState方法來恢復(fù)數(shù)據(jù)傲茄。事實上,onRestoreInstanceState方法的參數(shù)和onCreate方法的參數(shù)是一致的沮榜,只是他們兩個方法回調(diào)的時機(jī)不同盘榨。因此,判斷是否執(zhí)行的關(guān)鍵因素就是用戶是否期望返回該activity時界面數(shù)據(jù)仍然存在蟆融。

這里需要注意幾個點:

  1. 不同android版本下草巡,onSaveInstanceState方法的調(diào)用時機(jī)是不同的。目前筆者的源碼是api30型酥,在官方注釋中可以看到這么一句話:

    /*If called, this method will occur after {@link #onStop} for applications
     * targeting platforms starting with {@link android.os.Build.VERSION_CODES#P}.
     * For applications targeting earlier platform versions this method will occur
     * before {@link #onStop} and there are no guarantees about whether it will
     * occur before or after {@link #onPause}.
     */
    
    

    翻譯過來意思就是山憨,在api28及以上版本onSaveInstanceState是在onStop之后調(diào)用的,但是在低版本中弥喉,他是在onStop之前被調(diào)用的郁竟,且與onPause之間的順序是不確定的。

  2. 當(dāng)activity進(jìn)入后臺的時候档桃,onSaveInstanceState方法則會被調(diào)用枪孩,而不是異常情況下才會調(diào)用onSaveInstanceState方法,因為并不確定在后臺時藻肄,activity是否會被系統(tǒng)殺死蔑舞,所以以最保險的方法,先保存數(shù)據(jù)嘹屯。當(dāng)確實是因為異常情況被殺死時攻询,返回activity用戶期望界面需要恢復(fù)數(shù)據(jù),才會調(diào)用onRestoreInstanceState來恢復(fù)數(shù)據(jù)州弟。但是钧栖,activity直接按返回鍵或者調(diào)用finish方法直接結(jié)束Activity的時候低零,是不會回調(diào)onSaveInstanceState方法,因為非常明確下一次返回該activity用戶期望的是一個干凈界面的新activity拯杠。

  3. onSaveInstanceState不能做重量級的數(shù)據(jù)存儲掏婶。onSaveInstanceState存儲數(shù)據(jù)的原理是把數(shù)據(jù)序列化到磁盤中,如果存儲的數(shù)據(jù)過于龐大潭陪,會導(dǎo)致界面卡頓雄妥,掉幀等情況出現(xiàn)。

  4. 正常情況下依溯,每個view都會重寫這兩個方法老厌,當(dāng)activity的這兩個方法被調(diào)用的時候,會向上委托window去調(diào)用頂層viewGroup的這兩個方法黎炉;而viewGroup會遞歸調(diào)用子view的onSaveInstanceState/onRestoreInstanceState方法枝秤,這樣所有的view狀態(tài)就都被恢復(fù)了。

關(guān)于界面數(shù)據(jù)恢復(fù)這里也不展開細(xì)講了慷嗜,有興趣的讀者可以自行深入研究淀弹。

  • onPostCreate

這個方法其實和onPostResume是一樣的,同樣的還有onContextChange方法庆械。這三個方法都是不常用的垦页,這里也點出其中一個來統(tǒng)一講一下。

onPostCreate方法發(fā)生在onRestoreInstanceState之后干奢,onResume之前,他代表著界面數(shù)據(jù)已經(jīng)完全恢復(fù)盏袄,就差顯示出來與用戶交互了忿峻。在onStart方法被調(diào)用時這些操作尚未完成。

onPostResume是在Resume方法被完全執(zhí)行之后的回調(diào)辕羽。

onContentChange是在setContentView之后的回調(diào)逛尚。

這些方法都不常用,僅做了解刁愿。如果真的遇到了具體的業(yè)務(wù)需求绰寞,也可以拿出來用一下。

  • onNewIntent

這個方法涉及到的場景也是重復(fù)啟動铣口,但是與onRestart方法被調(diào)用的場景是不同的滤钱。

我們知道activity是有多種啟動模式的,其中singleInstance脑题、singleTop件缸、singleTask都保證了在一定情況下的單例狀態(tài)。如singleTop叔遂,如果我們啟動一個正處于棧頂且啟動模式為singleTop的activity他炊,那么他并不會在創(chuàng)建一個activity實例争剿,而是會回調(diào)該activity的onNewIntent方法。該方法接收一個intent參數(shù)痊末,該參數(shù)就是新的啟動Intent實例蚕苇。

其他的情況如singleTask、singleInstance凿叠,當(dāng)遇到這種強(qiáng)制單例情況時涩笤,都會回調(diào)onNewIntent方法。關(guān)于啟動模式這里也不展開幔嫂,后續(xù)筆者可能會再出一期文章講啟動模式辆它。

場景生命周期流程

這一部分主要是講解在一些場景下,生命周期方法的回調(diào)順序履恩。對于當(dāng)個Activity而言锰茉,上述流程圖已經(jīng)展示了各種情況下的生命周期回調(diào)順序了。但是切心,當(dāng)啟動另一個activity的時候飒筑,到底是onStop先執(zhí)行,還是被啟動的onStart先執(zhí)行呢绽昏?這些就變得比較難以確定协屡。

驗證生命周期回調(diào)順序最好的方法就是寫demo,通過日志打印全谤,可以很明顯地觀察到生命周期的回調(diào)順序肤晓。當(dāng)然,查看源碼也是一個不錯的方法认然,但是需要對系統(tǒng)源碼有一定的認(rèn)識补憾,我們還是選擇簡單粗暴的方法。

正常啟動與結(jié)束

onCreate -> onStart -> onResume -> onPause -> onStop -> onDestroy

這種情況的生命周期比較好理解卷员,就是常規(guī)的啟動與結(jié)束盈匾,也不會涉及到第二個activity。最后看日志打颖下狻:

image

Activity切換

Activity1:onPause
Activity2:onCreate -> onStart -> onResume
Activity1:onStop

當(dāng)切換到另一個activity的時候削饵,本activity會先調(diào)用onPause方法,進(jìn)入后臺未巫;被啟動的activity依次調(diào)用三個回調(diào)方法后準(zhǔn)備與用戶交互窿撬;這時原activity再調(diào)用onStop方法變得不可見,最后被啟動的activity才會顯示出來叙凡。

理解這個生命周期順序只需要記住兩個點:前后臺尤仍、是否可見。onPause調(diào)用之后狭姨,activity會進(jìn)入后臺宰啦。而前臺交互的activity只能有一個苏遥,所以原activity必須先進(jìn)入后臺后,目標(biāo)activity才能啟動并進(jìn)入前臺赡模。onStop調(diào)用之后activity變得不可見田炭,因而只有在目標(biāo)activity即將要與用戶交互的時候,需要進(jìn)行顯示了漓柑,原Activity才會調(diào)用onStop方法進(jìn)入不可見狀態(tài)教硫。

當(dāng)從Activity2回退到Activity1的時候,流程也是類似的辆布,只是Activity1會在其他生命周期之前執(zhí)行一次onRestart瞬矩,跟前面的流程是類似的。讀者可以看一下下面的日志打印锋玲,這里就不再贅述了景用。

下面看一下切換到另一個activity的生命周期日志打印:

image

這里我們看到最后回調(diào)了onSaveInstanceState方法惭蹂,前面我們講到了伞插,當(dāng)activity進(jìn)入后臺的時候,會調(diào)用該方法來保存數(shù)據(jù)盾碗。因為并不知道在后臺時activity是否會被系統(tǒng)殺死市俊。下面再看一下從activity2返回的時候叔锐,生命周期的日志打印:

image

屏幕旋轉(zhuǎn)

running -> onPause -> onStop -> onSaveInstanceState -> onDestroy

onCreate -> onStart -> onRestoreInstanceState -> onResume

當(dāng)因資源配置改變時欠窒,activity會銷毀重建伪嫁,最常見的就是屏幕旋轉(zhuǎn)弯汰。這個時候?qū)儆诋惓G闆r的Activity生命結(jié)束亥贸。因而导饲,在銷毀的時候,會調(diào)用onSaveInstanceState來保存數(shù)據(jù)谬盐,在重新創(chuàng)建新的activity的時候,會調(diào)用onRestoreInstanceState來恢復(fù)數(shù)據(jù)诚些。

看一下日志打臃煽:

image

后臺應(yīng)用被系統(tǒng)殺死

onDestroy

onCreate -> onStart -> onRestoreInstanceState -> onResume

這個流程跟上面的資源配置更改是很像的,只是每個activity不可見的時候诬烹,會回調(diào)onSaveInstanceState提前保存數(shù)據(jù)砸烦,那么在被后臺殺死的時候,就不需要再次保存數(shù)據(jù)了绞吁。

具有返回值的啟動

onActivityResult -> onRestart -> onResume

這里主要針對使用startActivityForResult方法啟動另一個activity幢痘,當(dāng)該activity銷毀并返回時,原activity的onActivityResult方法的執(zhí)行時機(jī)家破。大部分流程和activity切換是一樣的颜说。但在返回原Activity時购岗,onActivityResult方法會在其他所有的生命周期方法執(zhí)行前被執(zhí)行∶欧啵看一下日志打雍盎:

image

重復(fù)啟動

onPause -> onNewIntent -> onResume

這個流程是比較容易在學(xué)習(xí)生命周期的時候被忽略的。前面已經(jīng)有講到了關(guān)于onNewIntent的相關(guān)介紹玄妈,這里就不再贅述乾吻。主要是記得如果當(dāng)前activity正處于棧頂,那么會先回調(diào)onPause之后再回調(diào)onNewIntent拟蜻。關(guān)于啟動模式以及返回棧的設(shè)計這里就不展開講了绎签,記住生命周期就可以了≡凸看一下日志打庸畋亍:

image

從源碼看生命周期

到這里關(guān)于生命周期的一些應(yīng)用知識就已經(jīng)講得差不多了,這一部分是深入源碼屈张,去探究生命周期在源碼中是如何實現(xiàn)的擒权。這樣對生命周期會有更加深刻的理解,同時可以更加了解android系統(tǒng)的源碼設(shè)計阁谆。

由于生命周期方法很多碳抄,筆者不可能一一講解,這樣篇幅就太大了且沒有意義场绿。這一部分的內(nèi)容一共分為兩個部分:第一部分是概述一下ActivityThread中關(guān)于每個生命周期的調(diào)用方法剖效,這樣大家就懂得如何去尋找對應(yīng)的源碼來研究;第二部分是拿onResume這個方法來舉例講解焰盗,同時解釋為什么在第一次啟動時璧尸,當(dāng)onResume被調(diào)用時界面依然不可見。

part 1

我們都知道熬拒,Activity的啟動是受AMS調(diào)配的爷光,那具體的調(diào)配方式是如何的呢?

通過Handler機(jī)制一文我們知道澎粟,android的程序執(zhí)行是使用handler機(jī)制來實現(xiàn)消息驅(qū)動型的蛀序。AMS想要控制Activity的生命周期,就必須不斷地向主線程發(fā)送message活烙;而程序想要執(zhí)行AMS的命令徐裸,就必須handle這些message執(zhí)行邏輯,兩端配合啸盏,才能達(dá)到這種效率重贺。

打個比方,領(lǐng)導(dǎo)要吩咐下屬去工作,他肯定不會把工作的具體流程都給下屬气笙,而只是會發(fā)個命令次企,如:給明天的演講做個ppt,給我預(yù)約個下星期的飛機(jī)等等健民。那么下屬抒巢,就必須根據(jù)這些命令來執(zhí)行指定的邏輯。所以秉犹,在android程序蛉谜,肯定有一系列的邏輯,來分別執(zhí)行來自AMS的“命令”崇堵。這就是ActivityThread中的一系列handlexxx方法型诚。給個我在vs code中的搜索圖感受一下:

image

當(dāng)然,應(yīng)用程序不止收到AMS的管理鸳劳,同樣的還有WMS狰贯、PMS等等系統(tǒng)服務(wù)。系統(tǒng)服務(wù)是運行在系統(tǒng)服務(wù)進(jìn)程的赏廓,當(dāng)系統(tǒng)服務(wù)需要控制應(yīng)用程序的時候涵紊,會通過Binder跨進(jìn)程通信把消息發(fā)送給應(yīng)用程序。應(yīng)用程序的Binder線程會通過handler把消息發(fā)送給主線程去執(zhí)行幔摸。因而摸柄,從這里也可以看出,當(dāng)應(yīng)用程序剛被創(chuàng)建的時候既忆,必須初始化的有主線程驱负、binder線程、主線程handler患雇、以及提前編寫了命令的執(zhí)行邏輯的類ActivityThread跃脊。光說不畫假解釋,畫個圖感受一下:

image

回到我們的生命周期主題苛吱。關(guān)于生命周期命令的執(zhí)行方法主要有:

handleLaunchActivity;
handleStartActivity;
handleResumeActivity;
handlePauseActivity;
handleStopActivity;
handleDestroyActivity;

具體的方法當(dāng)然不止這么多酪术,只是列出一些比較常用的。這些方法都在ActivityThread中翠储。ActivityThread每個應(yīng)用程序有且只有一個绘雁,他是系統(tǒng)服務(wù)“命令”的執(zhí)行者。

了解了AMS如何調(diào)配之后彰亥,那么他們的執(zhí)行順序如何確定呢?AMS是先發(fā)送handleStartActivity命令呢衰齐,還是先發(fā)送handleResumeActivity任斋?這里就需要我們對Activity的啟動流程有一定的認(rèn)知,感興趣讀者可以點擊Activity啟動流程前往學(xué)習(xí),這里就不展開了废酷。

最后再延伸一下瘟檩,那,ActivityThread可不可以自己決定執(zhí)行邏輯澈蟆,而不理會AMS的命令呢墨辛?答案肯定是no。你想啊趴俘,如果在公司里睹簇,你沒有老板的同意 ,你能動用公司的資源嗎寥闪?回到Android系統(tǒng)也是一樣的太惠,沒有AMS的授權(quán),應(yīng)用程序是無法得到系統(tǒng)資源的疲憋,所以AMS就保證了每一個程序都必須符合一定的規(guī)范凿渊。關(guān)于這方面的設(shè)計,讀者感興趣可以閱讀context機(jī)制這篇文章了解一下,重新認(rèn)識一下context缚柳。

好了埃脏,扯得有點遠(yuǎn),我們回到主題秋忙。下面呢就不展開去跟蹤整個流程了彩掐,而是定位到具體的handle方法去看看具體執(zhí)行了什么邏輯,對源碼流程感性去的讀者可以自行研究翰绊,限于篇幅這里就不展開了佩谷。下面主要介紹handleResumeActivity方法。

part 2

根據(jù)我們前面的學(xué)習(xí)监嗜,handleResumeActivity肯定是在handleLaunchActivityhandleStartActivity之后被執(zhí)行的谐檀。我們直接來看源碼:

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {
    ...
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ...;
    if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
        ...
        if (r.activity.mVisibleFromClient) {
            r.activity.makeVisible();
        }
    }
    ...
}

代碼我截取了兩個非常重要的部分。performResumeActivity最終會執(zhí)行onResume方法裁奇;activity.makeVisible();是真正讓界面顯示在屏幕個上的方法桐猬,我們看一下makeVisible():

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

如果尚未添加到屏幕上,那么會調(diào)用windowManager的addView方法來添加刽肠,之后溃肪,activity界面才真正顯示在屏幕上∫粑澹回應(yīng)之前的問題:為什么在onResume彈出popupWindow會拋異常而彈出dialog卻不會惫撰?原因就是這個時候activity的界面尚未添加到屏幕上,而popupWindow需要依附于父界面躺涝,這個時候彈出就會拋出token is null異常了厨钻。而Dialog屬于應(yīng)用層級窗口,不需要依附于任何窗口,所以dialog在onCreate方法中彈出都是沒有問題的夯膀。為了驗證我們的判斷诗充,我在生命周期中打印decorView的windowToken,當(dāng)decorView被添加到屏幕上后诱建,就會被賦值token了蝴蜓,看日志打印:

image

可以看到俺猿,直到onPostResume方法執(zhí)行茎匠,界面依舊沒有顯示在屏幕上。而直到onWindowFocusChange被執(zhí)行時辜荠,界面才是真正顯示在屏幕上了汽抚。

好了,讓我們再回到一開始的源碼伯病,深入performResumeActivity方法中看看造烁,在哪里執(zhí)行了onResume方法:

public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
        String reason) {
    ...
    try {
        ...
        if (r.pendingIntents != null) {
            // 判斷是否需要執(zhí)行newIntent方法
            deliverNewIntents(r, r.pendingIntents);
            r.pendingIntents = null;
        }
        if (r.pendingResults != null) {
            // 判斷是否需要執(zhí)行onActivityResult方法
            deliverResults(r, r.pendingResults, reason);
            r.pendingResults = null;
        }
        // 回調(diào)onResume方法
        r.activity.performResume(r.startsNotResumed, reason);

        r.state = null;
        r.persistentState = null;
        // 設(shè)置狀態(tài)
        r.setState(ON_RESUME);

        reportTopResumedActivityChanged(r, r.isTopResumedActivity, "topWhenResuming");
    } 
    ...
}

這個方法的重點就是,先判斷是否是需要執(zhí)行onNewIntent或者onActivityResult的場景午笛,如果沒有則執(zhí)行調(diào)用performResume方法惭蟋,我們深入performResume看一下:

final void performResume(boolean followedByPause, String reason) {
    dispatchActivityPreResumed();
    performRestart(true /* start */, reason);
    ...
    mInstrumentation.callActivityOnResume(this);
    ...
    onPostResume();
    ...
}

public void callActivityOnResume(Activity activity) {
    activity.mResumed = true;
    activity.onResume();
    ...
}

同樣只看重點。首先會調(diào)用performRestart方法药磺,這個方法內(nèi)部會判斷是否需要執(zhí)行onRestart方法和onStart方法告组,如果是從別的activity返回這里是肯定要執(zhí)行的。然后使用Instrumentation來回調(diào)Activity的onResume方法癌佩。當(dāng)onResume回調(diào)完成后木缝,會再調(diào)用onPostResume()方法。

那么到這里關(guān)于handleResumeActivity的方法就講完了围辙,為什么在onResume甚至onPostResume方法被回調(diào)的時候界面尚未顯示我碟,也有了更加深刻的認(rèn)識。具體的代碼邏輯非常多姚建,而關(guān)于生命周期的代碼我只挑了重點來講矫俺,其他的源碼,感興趣的讀者可以自行去查閱源碼掸冤。筆者這里更多的是充當(dāng)一個拋磚引玉的效果厘托。要從源碼中學(xué)習(xí)到知識,就必須自己手動去閱讀源碼稿湿,跟著文章看完事實上收獲是不大的铅匹。

從系統(tǒng)設(shè)計看Activity與其生命周期

在筆者認(rèn)為,每一個知識饺藤,都是在具體的場景下為了解決具體的問題包斑,通過權(quán)衡各種條件設(shè)計出來的考杉。在學(xué)習(xí)了每一個知識之后,筆者總是喜歡反過來舰始,思考一下這一塊知識的底層設(shè)計思想是什么,他是需要解決什么問題咽袜,權(quán)衡了什么條件丸卷。通過不斷思考來從一個更高的角度來看待每一個知識點。

要理解生命周期的設(shè)計询刹,首先需要理解Activity本身谜嫉。想一下,如果沒有Activity凹联,那么我們該如何編寫程序沐兰?有沒有忽然反應(yīng)到,沒有了activity蔽挠,我們的程序竟無處下手住闯?因為這涉及到Activity的一個最大的作用:Activity 類是 Android 應(yīng)用的關(guān)鍵組件,而 Activity 的啟動和組合方式則是該平臺應(yīng)用模型的基本組成部分澳淑。

相信很多讀者都寫過c語言比原、java或者其他語言的課程設(shè)計,我們的程序入口就是main函數(shù)杠巡。從main函數(shù)開始量窘,根據(jù)用戶的輸入來進(jìn)入不同的功能模塊,如更改信息模塊氢拥、查閱模塊等等蚌铜。以功能模塊為基本組成部分的應(yīng)用模型是我們最初的程序設(shè)計模型。而android程序嫩海,我們會說這個程序有幾個界面冬殃。我們更關(guān)注的是界面之間的跳轉(zhuǎn),而不是功能模塊之間的跳轉(zhuǎn)出革。我們在設(shè)計程序的時候造壮,我們會說這個界面有什么功能,那個界面有什么功能骂束,多個界面之間如何協(xié)調(diào)耳璧。對于用戶來說,他們感知的也是一個個獨立的界面展箱。當(dāng)我們通過通訊軟件調(diào)用郵箱app的發(fā)送郵件界面時旨枯,我們喜歡看到的只是發(fā)送郵件的界面,而不需要看到收件箱混驰、登錄注冊等界面攀隔。以功能模塊為應(yīng)用模型的設(shè)計從一個主功能模塊入口皂贩,然后通過用戶的輸入去調(diào)用不同的功能模塊。與其類似昆汹,android程序也有一個主界面明刷,通過這個主界面,接受用戶的操作去調(diào)用其他的界面满粗。組成android程序的辈末,是一個個的界面,而每一個界面映皆,對應(yīng)一個Activity挤聘。因此,Activity是android平臺應(yīng)用模型的基本組成成分捅彻。

功能模塊的應(yīng)用模型從main方法進(jìn)入主功能模塊组去,而android程序從ActivityThread的main方法開始,接收AMS的調(diào)度啟動“LaunchActivity”步淹,也就是我們在AndroidManifest中配置為main的activity从隆,當(dāng)應(yīng)用啟動的時候,就會首先打開這個activity缭裆。那么第一個界面被打開广料,其他的界面就根據(jù)用戶的操作來依次跳轉(zhuǎn)了。

那如何做到每個界面之間彼此解耦幼驶、各自的顯示不發(fā)生混亂艾杏、界面之間的跳轉(zhuǎn)有條不紊等等?這些工作盅藻,官方都幫我們做好了购桑。Activity就是在這個設(shè)計思想下開發(fā)出來的。當(dāng)我們在Activity上開發(fā)的時候氏淑,就已經(jīng)沿用了他的這種設(shè)計思想勃蜘。當(dāng)我們開發(fā)一個app的時候,最開始要考慮的假残,是界面如何設(shè)計缭贡。設(shè)計好界面之后,就是考慮如何開發(fā)每個界面了辉懒。那我們?nèi)绾巫远x好每一個界面阳惹?如何根據(jù)我們的需求去設(shè)計每個界面的功能?Activity并沒有main方法眶俩,我們的代碼該寫在哪里被執(zhí)行莹汤?答案就是:生命周期回調(diào)方法

到這里颠印,你應(yīng)該可以理解為什么啟動activity并不是一句new就可以解決的吧纲岭?Activity承擔(dān)的責(zé)任非常多抹竹,需要初始化的邏輯也非常多。當(dāng)Activity被啟動止潮,他會根據(jù)自身的啟動情況窃判,來回調(diào)不同的生命周期方法。其中喇闸,承擔(dān)初始化整個界面已經(jīng)各個功能組件的初始化任務(wù)的就是onCreate方法兢孝。他有點類似于我們功能模塊的入口函數(shù),在這里我們通過setContentView來設(shè)計我們界面的布局仅偎,通過setOnClickListenner來給每個view設(shè)置監(jiān)聽等等。在MVVM設(shè)計模式中雳殊,還需要初始化viewModel橘沥、綁定數(shù)據(jù)等等。這是生命周期的第一個非常重要的意義所在夯秃。

而當(dāng)界面的顯示座咆、退出,我們需要為之申請或者釋放資源仓洼。如我上文舉的相機(jī)例子介陶,我在微信的掃一掃申請了相機(jī)權(quán)限,如果進(jìn)入后臺的時候沒有釋放資源色建,那么打開系統(tǒng)相機(jī)就無法使用了哺呜,資源被占領(lǐng)了。因此箕戳,生命周期的另一個重要的作用就是:做好資源的申請與釋放某残,避免內(nèi)存泄露

其他生命周期的作用陵吸,如界面數(shù)據(jù)恢復(fù)玻墅、界面切換邏輯處理等等就不再贅述了,前面已經(jīng)都有涉及到壮虫。

這一部分的重點就是理解android應(yīng)用程序是以Activity為基本組成部分的應(yīng)用模型這個點澳厢。當(dāng)界面的啟動以及不同界面之間進(jìn)行切換的時候,也就可以更加感知生命周期的作用了囚似。

最后

關(guān)于Activity生命周期的內(nèi)容剩拢,在一篇文章講完整是不可能的。當(dāng)對他研究地越深饶唤,涉及到內(nèi)容就會越多裸扶。每個知識點就是像是瓜藤架上的一個瓜,如果單純摘瓜搬素,那就是一個瓜呵晨;如果抓著藤蔓往外拔魏保,那么整個架子都會被扯出來。這篇文章也當(dāng)是拋磚引玉摸屠,在講生命周期相關(guān)的知識講完之后谓罗,提供給讀者一個思考的思路。

希望文章對你有幫助季二。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末檩咱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子胯舷,更是在濱河造成了極大的恐慌刻蚯,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,423評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桑嘶,死亡現(xiàn)場離奇詭異炊汹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)逃顶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評論 2 385
  • 文/潘曉璐 我一進(jìn)店門讨便,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人以政,你說我怎么就攤上這事霸褒。” “怎么了盈蛮?”我有些...
    開封第一講書人閱讀 157,019評論 0 348
  • 文/不壞的土叔 我叫張陵废菱,是天一觀的道長。 經(jīng)常有香客問我抖誉,道長昙啄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,443評論 1 283
  • 正文 為了忘掉前任寸五,我火速辦了婚禮梳凛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘梳杏。我一直安慰自己韧拒,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,535評論 6 385
  • 文/花漫 我一把揭開白布十性。 她就那樣靜靜地躺著叛溢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪劲适。 梳的紋絲不亂的頭發(fā)上楷掉,一...
    開封第一講書人閱讀 49,798評論 1 290
  • 那天,我揣著相機(jī)與錄音霞势,去河邊找鬼烹植。 笑死斑鸦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的草雕。 我是一名探鬼主播巷屿,決...
    沈念sama閱讀 38,941評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼墩虹!你這毒婦竟也來了嘱巾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,704評論 0 266
  • 序言:老撾萬榮一對情侶失蹤诫钓,失蹤者是張志新(化名)和其女友劉穎旬昭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體菌湃,經(jīng)...
    沈念sama閱讀 44,152評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡问拘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,494評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了慢味。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,629評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡墅冷,死狀恐怖纯路,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情寞忿,我是刑警寧澤驰唬,帶...
    沈念sama閱讀 34,295評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站腔彰,受9級特大地震影響叫编,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜霹抛,卻給世界環(huán)境...
    茶點故事閱讀 39,901評論 3 313
  • 文/蒙蒙 一搓逾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧杯拐,春花似錦霞篡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至顶滩,卻和暖如春余掖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背礁鲁。 一陣腳步聲響...
    開封第一講書人閱讀 31,978評論 1 266
  • 我被黑心中介騙來泰國打工盐欺, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留赁豆,地道東北人。 一個月前我還...
    沈念sama閱讀 46,333評論 2 360
  • 正文 我出身青樓找田,卻偏偏與公主長得像歌憨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子墩衙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,499評論 2 348

推薦閱讀更多精彩內(nèi)容