Android LiveData

LiveData Overview

* 使用 LiveData 的優(yōu)點

* 使用 LiveData 對象
  * 創(chuàng)建 LiveData 對象
  * 觀察 LiveData 對象
  * 更新 LiveData 對象
  * LiveData 與 Room 一起使用

* 擴展 LiveData

* 轉(zhuǎn)換 LiveData
  * 創(chuàng)建新的轉(zhuǎn)換

* 合并多個 LiveData 源

* 其他資源

LiveData 是一個可觀察的數(shù)據(jù)持有者類础爬。與常規(guī) observable 不同漏益,LiveData 是生命周期感知的憎乙,這意味著它尊重其他應(yīng)用程序組件的生命周期拟杉,例如 activity笑旺,fragment 或 service。此感知確保 LiveData 僅更新處于活動狀態(tài)的應(yīng)用組件觀察者范咨。

注意:要將 LiveData 組件導(dǎo)入 Android 項目故觅,請參閱 Adding Components to your Project

如果 Observer 類表示的觀察者生命周期處于 STARTEDRESUMED 狀態(tài)渠啊,則 LiveData 會將其視為活動狀態(tài)输吏。LiveData 僅通知處于活動狀態(tài)的觀察者更新信息。非活動狀態(tài)的觀察者不會收到有關(guān)數(shù)據(jù)更改的通知替蛉。

你可以注冊與實現(xiàn)了 LifecycleOwner 接口的對象配對的觀察者贯溅。此關(guān)系允許在相應(yīng) Lifecycle 對象的狀態(tài)更改為 DESTROYED 時刪除觀察者拄氯。這對于 activity 和 fragment 特別有用,因為它們可以安全地觀察 LiveData 對象而不用擔(dān)心泄漏 - activity 和 fragment 在其生命周期被銷毀時立即取消訂閱它浅。

一译柏、使用 LiveData 的優(yōu)點

使用 LiveData 具有以下優(yōu)勢:

確保你的 UI 符合你的數(shù)據(jù)狀態(tài)

LiveData 遵循觀察者模式。生命周期狀態(tài)更改時姐霍,LiveData 會通知 Observer 對象鄙麦。你的觀察者可以在每次數(shù)據(jù)更改時更新 UI。

沒有內(nèi)存泄漏

觀察者綁定到 Lifecycle 對象镊折,并在其相關(guān)生命周期被銷毀后自行清理胯府。

不會因為 activity 停止而發(fā)生崩潰

如果觀察者的生命周期處于非活動狀態(tài)(例如,activity 在后臺堆棧中)恨胚,則它不會接收任何 LiveData 事件骂因。

不再需要手動處理生命周期

UI 組件只是觀察相關(guān)數(shù)據(jù),不會停止或恢復(fù)觀察赃泡。LiveData 自動管理所有這些寒波,因為它在觀察時意識到相關(guān)的生命周期狀態(tài)變化。

始終保持最新數(shù)據(jù)

如果生命周期變?yōu)榉腔顒訝顟B(tài)升熊,它將在再次變?yōu)榛顒訝顟B(tài)時接收最新數(shù)據(jù)俄烁。例如,后臺 activity 在返回前臺后立即接收最新數(shù)據(jù)级野。

適當(dāng)?shù)呐渲酶?/h6>

如果由于配置更改(例如設(shè)備旋轉(zhuǎn))而重新創(chuàng)建 activity 或 fragment猴娩,則會立即接收最新的可用數(shù)據(jù)。

共享資源

你可以使用單例模式擴展 LiveData 對象以包裝系統(tǒng)服務(wù)勺阐,以便可以在應(yīng)用程序中共享它們卷中。LiveData 對象連接到系統(tǒng)服務(wù)一次,然后任何需要該資源的觀察者只需觀察 LiveData 對象渊抽。

二蟆豫、使用 LiveData 對象

請按照以下步驟使用 LiveData 對象:

  1. 創(chuàng)建 LiveData 實例以保存特定類型的數(shù)據(jù)。這通常在 ViewModel 類中完成懒闷。

  2. 創(chuàng)建一個 Observer 對象十减,該對象定義 onChanged() 方法,該方法控制 LiveData 對象數(shù)據(jù)更改時觸發(fā)的邏輯愤估。通常在 UI 控制器中創(chuàng)建一個 Observer 對象帮辟,例如 activity 或 fragment。

  3. 使用 observe() 方法將 Observer 對象關(guān)聯(lián)到 LiveData 對象玩焰。observe() 方法需要 LifecycleOwner 對象由驹。該方法使 Observer 對象訂閱 LiveData 對象,以便通知它更改昔园。通常將 Observer 對象關(guān)聯(lián)到 UI 控制器中蔓榄,例如 activity 或 fragment并炮。

注意:你可以使用 observeForever(Observer) 方法注冊沒有關(guān)聯(lián)任何一個 LifecycleOwner 對象的觀察者。在這種情況下甥郑,觀察者被認為始終處于活動狀態(tài)逃魄,因此始終會收到有關(guān)修改的通知。你可以刪除這些觀察者通過調(diào)用 removeObserver(Observer) 方法澜搅。

更新存儲在 LiveData 對象中的值時伍俘,只要關(guān)聯(lián)的 LifecycleOwner 處于活動狀態(tài),它就會觸發(fā)所有已注冊的觀察者勉躺。

LiveData 允許 UI 控制器觀察者訂閱更新养篓。當(dāng) LiveData 對象保存的數(shù)據(jù)發(fā)生更改時,UI 會自動響應(yīng)更新赂蕴。

2.1 創(chuàng)建 LiveData 對象

LiveData 是一個包裝器,可以與任何數(shù)據(jù)一起使用舶胀,包括實現(xiàn)集合的對象概说,例如 List。LiveData 對象通常存儲在 ViewModel 對象中嚣伐,并通過 getter 方法訪問糖赔,如以下示例所示:

public class NameViewModel extends ViewModel {

// Create a LiveData with a String
private MutableLiveData<String> currentName;

    public MutableLiveData<String> getCurrentName() {
        if (currentName == null) {
            currentName = new MutableLiveData<String>();
        }
        return currentName;
    }

// Rest of the ViewModel...
}

最初的時候并沒有設(shè)置 LiveData 對象中的數(shù)據(jù)。

注意:確保在 ViewModel 對象中存儲用于更新 UI 的 LiveData 對象轩端,而不是在 activity 或 fragment 中放典,原因如下:

  • 避免臃腫的 activity 和 fragment。UI 控制器負責(zé)顯示數(shù)據(jù)但不保持?jǐn)?shù)據(jù)狀態(tài)基茵。

  • 將 LiveData 實例與特定 activity 或 fragment 實例分離奋构,并允許 LiveData 對象在配置更改后繼續(xù)存活。

你可以在 ViewModel 指南中閱讀有關(guān) ViewModel 類的優(yōu)點和用法的更多信息拱层。

2.2 觀察 LiveData 對象

在大多數(shù)情況下弥臼,app 組件的 onCreate() 方法是開始觀察 LiveData 對象的正確位置,原因如下:

  • 確保系統(tǒng)不會從 activity 或 fragment 的 onResume() 方法進行冗余調(diào)用根灯。

  • 確保 activity 或 fragment 在其變?yōu)榛顒訝顟B(tài)時立即顯示數(shù)據(jù)径缅。只要應(yīng)用程序組件處于 STARTED 狀態(tài),它就會從它正在觀察的 LiveData 對象中接收最新值烙肺。只有在設(shè)置了要觀察的 LiveData 對象時才會出現(xiàn)這種情況纳猪。

通常,LiveData 僅在數(shù)據(jù)更改時才提供更新桃笙,并且要求觀察者處于活動狀態(tài)氏堤。此行為的一個例外是觀察者從非活動狀態(tài)更改為活動狀態(tài)時也會收到更新。此外搏明,如果觀察者第二次從非活動狀態(tài)更改為活動狀態(tài)丽猬,則只有在自上次活動狀態(tài)以來該值發(fā)生更改時才會收到更新宿饱。

以下示例代碼說明了如何開始觀察 LiveData 對象:

public class NameActivity extends AppCompatActivity {

    private NameViewModel model;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Other code to setup the activity...

        // Get the ViewModel.
        model = ViewModelProviders.of(this).get(NameViewModel.class);


        // Create the observer which updates the UI.
        final Observer<String> nameObserver = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String newName) {
                // Update the UI, in this case, a TextView.
                nameTextView.setText(newName);
            }
        };

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        model.getCurrentName().observe(this, nameObserver);
    }
}

在使用 nameObserver 作為參數(shù)傳遞調(diào)用 observe() 之后,立即調(diào)用 onChanged()脚祟,提供存儲在 mCurrentName 中的最新值谬以。如果 LiveData 對象未在 mCurrentName 中設(shè)置值,則不會調(diào)用 onChanged()由桌。

2.3 更新 LiveData 對象

LiveData 沒有公開的方法來更新存儲的數(shù)據(jù)为黎。MutableLiveData 類公開 setValue(T)postValue(T) 方法,如果需要編輯存儲在 LiveData 對象中的值行您,則必須使用這些方法铭乾。通常在 ViewModel 中使用 MutableLiveData,然后 ViewModel 僅向觀察者公開不可變的 LiveData 對象娃循。

設(shè)置觀察者關(guān)系后炕檩,可以更新 LiveData 對象的值,如以下示例所示捌斧,當(dāng)用戶點擊按鈕時觸發(fā)所有觀察者:

button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        String anotherName = "John Doe";
        model.getCurrentName().setValue(anotherName);
    }
});

在示例中調(diào)用 setValue(T) 會導(dǎo)致觀察者使用值 John Doe 調(diào)用其 onChanged() 方法笛质。該示例顯示按下按鈕,調(diào)用 setValue()postValue() 來更新 mName捞蚂,更新的原因可以有多種妇押,包括響應(yīng)網(wǎng)絡(luò)請求或數(shù)據(jù)庫加載完成;在所有情況下姓迅,對 setValue()postValue() 的調(diào)用都會觸發(fā)觀察者并更新 UI敲霍。

注意:必須調(diào)用 setValue(T) 方法才能從主線程更新 LiveData 對象。如果代碼在工作線程中執(zhí)行丁存,則可以使用 postValue(T) 方法來更新 LiveData 對象肩杈。

2.4 LiveData 與 Room 一起使用

Room 持久性庫支持可觀察的查詢,這些查詢返回 LiveData 對象解寝》嫣瘢可觀察查詢作為數(shù)據(jù)庫訪問對象 (DAO) 的一部分寫入。

在更新數(shù)據(jù)庫時编丘,Room 會生成更新 LiveData 對象所需的所有代碼与学。生成的代碼在需要時在后臺線程上異步運行查詢。此模式對于使 UI 中顯示的數(shù)據(jù)與存儲在數(shù)據(jù)庫中的數(shù)據(jù)保持同步非常有用嘉抓。你可以在 Room 持久性庫指南中閱讀有關(guān) Room 和 DAO 的更多信息索守。

三、擴展 LiveData

如果觀察者的生命周期處于 STARTEDRESUMED 狀態(tài)抑片,LiveData 會將觀察者視為處于活動狀態(tài)卵佛。以下示例代碼說明了如何擴展 LiveData 類:

public class StockLiveData extends LiveData<BigDecimal> {
    private StockManager stockManager;

    private SimplePriceListener listener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    public StockLiveData(String symbol) {
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}

此示例中價格監(jiān)聽器的實現(xiàn)包括以下重要方法:

  • 當(dāng) LiveData 對象具有活動的觀察者時,將調(diào)用 onActive() 方法。這意味著你需要從此方法開始觀察股票價格更新截汪。

  • 當(dāng) LiveData 對象沒有任何活動的觀察者時疾牲,將調(diào)用 onInactive() 方法。由于沒有觀察者正在觀察衙解,因此沒有理由保持與 StockManager 服務(wù)的連接阳柔。

  • setValue(T) 方法更新 LiveData 實例的值,并通知任何處于活動狀態(tài)的觀察者有關(guān)更改的信息蚓峦。

你可以如下所示使用 StockLiveData 類:

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(this, price -> {
            // Update the UI.
        });
    }
}

observe() 方法將 fragment(LifecycleOwner 的一個實例)作為第一個參數(shù)傳遞舌剂。這樣做表示此觀察者綁定到與所有者關(guān)聯(lián)的 Lifecycle 對象,這意味著:

  • 如果 Lifecycle 對象未處于活動狀態(tài)暑椰,則即使值發(fā)生更改霍转,也不會調(diào)用觀察者。

  • 銷毀 Lifecycle 對象后一汽,會自動刪除觀察者避消。

LiveData 對象具有生命周期感知這一事實意味著你可以在多個 activity,fragment 和 service 之間共享它們召夹。為了簡化示例岩喷,你可以將 LiveData 類實現(xiàn)為單例,如下所示:

public class StockLiveData extends LiveData<BigDecimal> {
    private static StockLiveData sInstance;
    private StockManager stockManager;

    private SimplePriceListener listener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    @MainThread
    public static StockLiveData get(String symbol) {
        if (sInstance == null) {
            sInstance = new StockLiveData(symbol);
        }
        return sInstance;
    }

    private StockLiveData(String symbol) {
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}

你可以在 fragment 中使用它戳鹅,如下所示:

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        StockLiveData.get(symbol).observe(this, price -> {
            // Update the UI.
        });
    }
}

多個 fragment 和 activity 可以觀察 MyPriceListener 實例。LiveData 僅在一個或多個 fragment 和 activity 可見且處于活動狀態(tài)時才連接到系統(tǒng)服務(wù)昏兆。

四枫虏、轉(zhuǎn)換 LiveData

你可能希望在將其分配給觀察者之前更改存儲在 LiveData 對象中的值,或者你可能需要根據(jù)另一個實例返回另一個 LiveData 實例的值爬虱。Lifecycle 包提供 Transformations 類隶债,其中包括支持這些場景的幫助方法。

對存儲在 LiveData 對象中的值應(yīng)用函數(shù)跑筝,并往后傳遞結(jié)果死讹。

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});

map() 類似,將函數(shù)應(yīng)用于存儲在 LiveData 對象中的值曲梗,并將結(jié)果解包并向下分發(fā)赞警。傳遞給 switchMap() 的函數(shù)必須返回一個 LiveData 對象,如以下示例所示:

private LiveData<User> getUser(String id) {
  ...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

你可以使用轉(zhuǎn)換方法在觀察者的生命周期中傳遞信息虏两。除非觀察者正在觀察返回的 LiveData 對象愧旦,否則不會執(zhí)行轉(zhuǎn)換。由于轉(zhuǎn)換是延遲執(zhí)行的定罢,因此生命周期相關(guān)的行為會被隱式傳遞下去笤虫,而不需要額外的顯式調(diào)用或依賴項。

如果你認為在 ViewModel 對象中需要 Lifecycle 對象,則轉(zhuǎn)換可能是更好的解決方案琼蚯。例如酬凳,假設(shè)你有一個接受地址的 UI 組件并返回該地址的郵政編碼。你可以為此組件實現(xiàn)樸素的 ViewModel(不推薦)遭庶,如以下示例代碼所示:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    public MyViewModel(PostalCodeRepository repository) {
       this.repository = repository;
    }

    private LiveData<String> getPostalCode(String address) {
       // DON'T DO THIS
       return repository.getPostCode(address);
    }
}

UI 組件需要從先前的 LiveData 對象取消注冊宁仔,并在每次調(diào)用 getPostalCode() 時注冊到新實例。此外罚拟,如果重新創(chuàng)建 UI 組件台诗,它將觸發(fā)對 repository.getPostCode() 方法的另一次調(diào)用,而不是使用先前調(diào)用的結(jié)果赐俗。

相反拉队,你可以將郵政編碼查找實現(xiàn)為地址輸入的轉(zhuǎn)換,如以下示例所示:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    private final MutableLiveData<String> addressInput = new MutableLiveData();
    public final LiveData<String> postalCode =
            Transformations.switchMap(addressInput, (address) -> {
                return repository.getPostCode(address);
             });

  public MyViewModel(PostalCodeRepository repository) {
      this.repository = repository
  }

  private void setInput(String address) {
      addressInput.setValue(address);
  }
}

在這種情況下阻逮,postalCode 字段被定義為 addressInput 的轉(zhuǎn)換粱快。只要你的應(yīng)用程序具有與 postalCode 字段關(guān)聯(lián)的處于活動狀態(tài)的觀察者,只要 addressInput 更改叔扼,就會重新計算并檢索字段的值事哭。

此機制允許較低級別的應(yīng)用程序創(chuàng)建按需延遲計算的 LiveData 對象。ViewModel 對象可以輕松獲取對 LiveData 對象的引用瓜富,然后在它們之上定義轉(zhuǎn)換規(guī)則鳍咱。

4.1 創(chuàng)建新的轉(zhuǎn)換

有十幾種不同的特定轉(zhuǎn)換可能對你的應(yīng)用有用,但默認情況下不提供与柑。 要實現(xiàn)自己的轉(zhuǎn)換谤辜,可以使用 MediatorLiveData 類,該類偵聽其他 LiveData 對象并處理它們發(fā)出的事件价捧。MediatorLiveData 正確地將其狀態(tài)傳遞到源 LiveData 對象丑念。 要了解有關(guān)此模式的更多信息,請參閱 Transformations 類的參考文檔结蟋。

五脯倚、合并多個 LiveData 源

MediatorLiveDataLiveData 的子類,允許你合并多個 LiveData 源嵌屎。只要任何原始 LiveData 源對象發(fā)生更改推正,就會觸發(fā) MediatorLiveData 對象的觀察者。

例如宝惰,如果你的 UI 中的 LiveData 對象舔稀,可以從本地數(shù)據(jù)庫或網(wǎng)絡(luò)進行更新,那么你可以添加以下來源的 MediatorLiveData 對象:

  • 與存儲在數(shù)據(jù)庫中的數(shù)據(jù)關(guān)聯(lián)的 LiveData 對象掌测。

  • 與從網(wǎng)絡(luò)訪問的數(shù)據(jù)關(guān)聯(lián)的 LiveData 對象内贮。

你的 activity 只需要觀察 MediatorLiveData 對象以從兩個源接收更新产园。有關(guān)詳細示例,請參閱 Guide to App Architecture 中的 Addendum: exposing network status夜郁。

六什燕、其他資源

LiveData 用于 Sunflower 演示應(yīng)用程序。

另請參閱架構(gòu)組件 BasicSample竞端。

有關(guān)將 LiveData 與 Snackbar 消息屎即,導(dǎo)航事件和其他事件一起使用的其他信息,請閱讀此文章事富。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末技俐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子统台,更是在濱河造成了極大的恐慌雕擂,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贱勃,死亡現(xiàn)場離奇詭異井赌,居然都是意外死亡,警方通過查閱死者的電腦和手機贵扰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門仇穗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人戚绕,你說我怎么就攤上這事纹坐。” “怎么了舞丛?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵耘子,是天一觀的道長。 經(jīng)常有香客問我瓷马,道長拴还,這世上最難降的妖魔是什么跨晴? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任欧聘,我火速辦了婚禮,結(jié)果婚禮上端盆,老公的妹妹穿的比我還像新娘怀骤。我一直安慰自己,他們只是感情好焕妙,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布蒋伦。 她就那樣靜靜地躺著,像睡著了一般焚鹊。 火紅的嫁衣襯著肌膚如雪痕届。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音研叫,去河邊找鬼锤窑。 笑死,一個胖子當(dāng)著我的面吹牛嚷炉,可吹牛的內(nèi)容都是我干的渊啰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼申屹,長吁一口氣:“原來是場噩夢啊……” “哼绘证!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起哗讥,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤嚷那,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后忌栅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體车酣,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年索绪,在試婚紗的時候發(fā)現(xiàn)自己被綠了湖员。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡瑞驱,死狀恐怖娘摔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情唤反,我是刑警寧澤凳寺,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站彤侍,受9級特大地震影響肠缨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盏阶,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一晒奕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧名斟,春花似錦脑慧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至岩梳,卻和暖如春囊骤,著一層夾襖步出監(jiān)牢的瞬間晃择,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人臊岸。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像激况,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子膘魄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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