Android架構組件

本文就“谷歌開發(fā)者”最近推出的Android 架構組件做簡單介紹和使用娇昙,如果需要更好的理解架構組件的原理象泵,可以到官網《Guide to App Architecture》船殉。

(一)Lifecycle

Lifecycle組件的引入

一項新的技術的提出肯定是為了解決痛點問題,如果使用過MVP模式的話咽斧,有個問題:Presenter感知Activity或者Fragment的生命周期堪置?你可能會這樣做,Presenter中定義多個和Activity或者Fragment相應的生命周期方法张惹,然后在Activity或者Fragment中調用Presenter中定義的方法舀锨。以下暫且用MVP模式場景舉例:

public class MainPresenter implements IPresenter {

    public MainPresenter(Context context){
    }

    public void onCreate() {
    }

    public void onStart() {
    }

    ...

    public void onDestroy() {
    }
}

???然后,Activity中的生命周期回調主動調用Presenter的public函數宛逗,如下:

public class MainActivity extends AppCompatActivity {
    private IPresenter mPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mPresenter = new MainPresenter(this);
        mPresenter.onCreate();
    }

    @Override
    protected void onStart() {
        super.onStart();
        mPresenter.onStart();
    }

   ...
   
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.onDestroy();
    }
}

???上面簡單的例子很明顯暴露出代碼冗余的短板坎匿,實際開發(fā)中業(yè)務如果稍加復雜,這個問題體現的就更加明顯雷激,對于追求代碼結構完美的程序員最不能接受替蔬。因此,Lifecycle組件就誕生了屎暇。

Lifecycle組件的簡單使用

public interface IPresenter extends DefaultLifecycleObserver{
 //....
}
public class MainActivity extends AppCompatActivity {
    private IPresenter mPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mPresenter = new MainPresenter(this);
        mPresenter.onCreate();
        //將mPresenter加入宿主生命周期觀察者隊列
        getLifecycle().addObserver(mPresenter);
    }

    @Override
    protected void onStart() {
        super.onStart();
    }

   ...
   
    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

??作為觀察者Presenter即可選擇性的重寫相關生命周期回調

public class MainPresenter implements IPresenter {

    public MainPresenter(Context context){

    }

   @Override
    public void onCreate(LifecycleOwner owner) {
    }
    
    @Override
    public void onStart(LifecycleOwner owner) {
    }

    ...
    @Override
    public void onDestroy(LifecycleOwner owner) {
    }
}

???所謂沒有對比就沒有傷害承桥,關于Lifecycle組件原理簡單的由下面的圖來詮釋:

高清無碼圖
高清無碼圖

???我們以V4包中的Fragment(AppCompatActivity類似)為例,看下Fragment和LifecycleOwner根悼、LifecycleObserver凶异、Lifecycle之間的類關系圖。

  • Lifecycle組件成員Lifecycle被定義成了抽象類挤巡,LifecycleOwner剩彬、LifecycleObserver被定義成了接口;

  • Fragment實現了LifecycleOwner接口矿卑,該只有一個返回Lifecycle對象的方法getLifecyle()喉恋;

  • Fragment中getLifecycle()方法返回的是繼承了抽象類Lifecycle的LifecycleRegistry。

  • LifecycleRegistry中定義嵌套類ObserverWithState母廷,該類持有GenericLifecycleObserver對象轻黑,而GenericLifecycleObserver是繼承了LifecycleObserver的接口。

?
??最后琴昆,建議明白Lifecycle組件的框架和結構后氓鄙,親自實踐慢慢體會。


(二)LiveData

LiveData組件的引入

從LiveData具有的特點椎咧,我們就能聯想到它能夠解決我們遇到的什么問題玖详。LiveData具有以下優(yōu)點:

  • 能夠保證數據和UI統(tǒng)一
  • 減少內存泄漏
  • 當Activity停止時不會引起崩潰
  • 不需要額外的手動處理來響應生命周期的變化
  • 組件和數據相關的內容能實時更新
  • 針對configuration change(比如語言把介、屏幕方向變化)時勤讽,不需要額外的處理來保存數據
  • 資源共享

LiveData組件的簡單使用

LiveData常見的幾種使用方式:

  • 使用LiveData對象
  • 繼承LiveData類

方式一:使用LiveData對象

主要有一下步驟:

  1. 創(chuàng)建保存特定數據類型的LiveData實例;
  2. 創(chuàng)建Observer對象拗踢,作為參數傳入LiveData.observe(...)方法添加觀察者;
  3. 更新LiveData對象存儲的數據;

創(chuàng)建LiveData實例

???Android文檔中建議LiveData配合ViewModel使用更佳犯建,其實炸宵,你也可以不使用ViewModel券膀,但是一定要做到LiveData中保存的數據和組件分離,原因前面我們已經提到過了驯遇。下面是在ViewModel中創(chuàng)建LiveData實例的例子芹彬,至于ViewModel后面會做詳細介紹:

public class NameViewModel extends ViewModel{
    // Create a LiveData with a String
    private MutableLiveData<String> mCurrentName;
    // Create a LiveData with a String list
    private MutableLiveData<List<String>> mNameListData;

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

    public MutableLiveData<List<String>> getNameList(){
        if (mNameListData == null) {
            mNameListData = new MutableLiveData<>();
        }
        return mNameListData;
    }
}

???在NameViewModel中創(chuàng)建了兩個MutableLiveData(MutableLiveData是LiveData的子類)實例,分別存儲當前姓名叉庐、姓名列表舒帮;兩個實例通過NameViewModel中的Getter方法得到。

創(chuàng)建Observer對象陡叠,添加觀察者

public class LiveDataFragment extends Fragment {
    private static final String TAG = "LiveDataFragment";
    private NameViewModel mNameViewModel;
    @BindView(R.id.tv_name)
    TextView mTvName;

    public static LiveDataFragment getInstance(){
        return new LiveDataFragment();
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mNameViewModel = ViewModelProviders.of(this).get(NameViewModel.class);
        mNameViewModel.getCurrentName().observe(this,(String name) -> {
            mTvName.setText(name);
            Log.d(TAG, "currentName: " + name);
        }); // 訂閱LiveData中當前Name數據變化
        mNameViewModel.getNameList().observe(this, (List<String> nameList) -> {
            for (String item : nameList) {
                Log.d(TAG, "name: " + item);
            }
        }); // 訂閱LiveData中Name列表數據變化
    }


    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.layout_livedata, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

}

???在onCreate()方法中通過LiveData.observe()方法添加觀察者玩郊,當數據變化時會通過回調方法通知觀察者,在Observer中更新當前姓名和打印姓名列表枉阵。

更新LiveData中的數據

???在上面我們已經訂閱了LiveData數據變化译红,現在我們看下如果LiveData數據變化時,上面的Observer中是否會受到更新的通知兴溜。我們在LiveDataFragment中增加兩個按鈕來改變LiveData中的數據侦厚。

@OnClick({R.id.btn_change_name, R.id.btn_update_list})
void onClicked(View view){
    switch (view.getId()){
        case R.id.btn_change_name:
            mNameViewModel.getCurrentName().setValue("Uzi");
            break;
        case R.id.btn_update_list:
            List<String> nameList = new ArrayList<>();
            for (int i = 0; i < 10; i++){
                nameList.add("Mlxg<" + i + ">");
            }
            mNameViewModel.getNameList().setValue(nameList);
            break;
    }
}

???代碼很簡單,在兩個按鈕的點擊事件中通過LiveData.setValue()方法來改變LiveData中保存的數據昵慌。當點擊這兩個按鈕的時候假夺,我們會發(fā)現在onCreate()方法中會收相應到數據改變的回調。

方式二:繼承LiveData類

????除了直接使用LiveData對象外斋攀,還可以通過集成LiveData類來定義適合特定需求的LiveData已卷。下面舉個繼承LiveData類的例子, 來驗證下LiveData的其中一個優(yōu)點——資源共享淳蔼。

public class MyLiveData extends LiveData<Integer> {
    private static final String TAG = "MyLiveData";
    private static MyLiveData sData;
    private WeakReference<Context> mContextWeakReference;

    public static MyLiveData getInstance(Context context){
        if (sData == null){
            sData = new MyLiveData(context);
        }
        return sData;
    }

    private MyLiveData(Context context){
        mContextWeakReference = new WeakReference<>(context);
    }

    @Override
    protected void onActive() {
        super.onActive();
        registerReceiver();
    }

    @Override
    protected void onInactive() {
        super.onInactive();
        unregisterReceiver();
    }

    private void registerReceiver() {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
        mContextWeakReference.get().registerReceiver(mReceiver, intentFilter);
    }

    private void unregisterReceiver() {
        mContextWeakReference.get().unregisterReceiver(mReceiver);
    }


    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Log.d(TAG, "action = " + action);
            if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
                int wifiRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
                int wifiLevel = WifiManager.calculateSignalLevel(wifiRssi, 4);
                sData.setValue(wifiLevel);
            }
        }
    };
}

???MyLiveData是個繼承了LiveData的單例類侧蘸,在onActive()和onInactive()方法中分別注冊和反注冊Wifi信號強度的廣播。然后在廣播接收器中更新MyLiveData對象鹉梨。在使用的時候就可以通過MyLiveData.getInstance()方法讳癌,然后通過調用observe()方法來添加觀察者對象,訂閱Wifi信息強度變化存皂。

  • onActive(),此方法是當處于激活狀態(tài)的observer個數從0到1時晌坤,該方法會被調用。
  • onInactive() ,此方法是當處于激活狀態(tài)的observer個數從1變?yōu)?時旦袋,該方法會被調用骤菠。

???關于LiveData的原理,從以下圖理解:

類關系圖
類關系圖

????LiveData的類關系圖相對比較簡單疤孕,從上面的類圖我們就能看到商乎。和LiveData組件相關的類和接口有:LiveData類、Observer接口祭阀、GenericLifecycleObserver接口鹉戚。LiveData類是個抽象類鲜戒,但是它沒有抽象方法,抽象類有個特點是:不能在抽象類中實例化自己抹凳。為什么LiveData會被定義成abstract而又沒有抽象方法呢遏餐,這個…我也不知道,看了下LiveData的提交記錄赢底,是在將hasObservers()替換getObserverCount()方法時將LiveData改成了abstract境输,在此之前它是被定義為public,可以翻墻的可以看下這里的修改記錄颖系。

  • MediatorLiveData繼承自MutableLiveData嗅剖,MutableLiveData繼承自LiveData。MediatorLiveData可以看成是多個LiveData的代理嘁扼,當將多個LiveData添加到MediatorLiveData信粮,任何一個LiveData數據發(fā)生變化時,MediatorLiveData都會收到通知趁啸。
  • LiveData有個內部類LifecycleBoundObserver强缘,它實現了GenericLifecycleObserver,而GenericLifecycleObserver繼承了LifecycleObserver接口不傅。在這里可以回顧下Lifecycle組件相關的內容旅掂。當組件(Fragment、Activity)生命周期變化時會通過onStateChanged()方法回調過來访娶。
  • Observer接口就是觀察者商虐,其中定義了LiveData數據變化的回調方法onChanged()。

?
當然崖疤,LiveData還有更多的使用方法秘车,這里僅做大致的理解并簡單使用。


(三)ViewModel

ViewModel組件的引入

ViewModel劫哼,它是負責準備及管理UI組件(Fragment/Activity)相關的數據類叮趴,也就是說ViewModel是用來管理UI相關的數據的。同時ViewModel還可以用來負責UI組件間的通信权烧。既然ViewModel是用來管理和UI組件有關的數據的眯亦,而LiveData又是這些數據的持有類,所以在使用LiveData的時候般码,就自然想到要使用ViewModel了妻率。另外,ViewModel還可以用于UI組件間的通信侈询。

ViewModel的基本使用

前面在講解LiveData時舌涨,我們已經使用了ViewModel糯耍,所以它的基本使用扔字,在這里就不在贅述了囊嘉。

ViewModel的生命周期

ViewModel的生命周期,在官方文檔中是用下面這張圖來描述ViewModel的生命周期革为。


ViewModel生命周期
ViewModel生命周期

???上圖是用Activity作為例子扭粱,左側表示Activity的生命周期狀態(tài),右側綠色部分表示ViewModel的生命周期范圍震檩。當屏幕旋轉的時候琢蛤,Activity會被recreate,Activity會經過幾個生命周期方法抛虏,但是這個時候ViewModel還是之前的對象博其,并沒有被重新創(chuàng)建,只有當Activity的finish()方法被調用時迂猴,ViewModel.onCleared()方法會被調用慕淡,對象才會被銷毀。這張圖很好的描述了是當Activity被recreate時沸毁,ViewModel的生命周期峰髓。

??? 另外,有個注意的地方:在ViewModel中不要持有Activity的引用息尺。為什么要注意這一點呢携兵?從上面的圖我們看到,當Activity被recreate時搂誉,ViewModel對象并沒有被銷毀徐紧,如果Model持有Activity的引用時就可能會導致內存泄漏。那如果要使用到Context對象怎么辦呢炭懊,那就使用ViewModel的子類AndroidViewModel吧浪汪。

ViewModel的組件間通信

下面看下ViewModel用于Fragment之間通信的例子:

public class CommunicateViewModel extends ViewModel {
    private MutableLiveData<String> mNameLiveData;

    public LiveData<String> getName(){
        if (mNameLiveData == null) {
            mNameLiveData = new MutableLiveData<>();
        }
        return mNameLiveData;
    }

    public void setName(String name){
        if (mNameLiveData != null) {
            mNameLiveData.setValue(name);
        }
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        mNameLiveData = null;
    }
}

?

public class FragmentOne extends Fragment {
    private CommunicateViewModel mCommunicateViewModel;

    public static FragmentOne getInstance(){
        return new FragmentOne();
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCommunicateViewModel = ViewModelProviders.of(getActivity()).get(CommunicateViewModel.class);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_one, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

    @OnClick(R.id.btn_set_name)
    void onViewClicked(View v){
        switch (v.getId()){
            case R.id.btn_set_name:
                mCommunicateViewModel.setName("Jane");
                break;
        }
    }
}

?

public class FragmentTwo extends Fragment {
    @BindView(R.id.tv_name)
    TextView mTvName;
    private CommunicateViewModel mCommunicateViewModel;

    public static FragmentTwo getInstance(){
        return new FragmentTwo();
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCommunicateViewModel =      ViewModelProviders.of(getActivity()).get(CommunicateViewModel.class);
        mCommunicateViewModel.getName().observe(this, name -> mTvName.setText(name));
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_two, container, false);
        ButterKnife.bind(this, view);
        return view;
    }

}

代碼很簡單,在FragmentOne中改變CommunicateViewModel中LiveData保存的數據凛虽,然后在FragmentTwo中會收到數據改變的通知死遭。但是這個前提兩個Fragment是共享的同一個LiveData數據對象。如何保證兩個Fragment里的ViewModel是同一個對象呢凯旋,ViewModelProviders.of(Activity/Fragment)中的對象一致呀潭,獲得的ViewModel就是同一個對象,源碼如下:

   /** 
     * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given Activity
     * is alive. More detailed explanation is in {@link ViewModel}.
     * <p>
     * It uses {@link ViewModelProvider.AndroidViewModelFactory} to instantiate new ViewModels.
     *
     * @param activity an activity, in whose scope ViewModels should be retained
     * @return a ViewModelProvider instance
     */
    @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        ViewModelProvider.AndroidViewModelFactory factory =
                ViewModelProvider.AndroidViewModelFactory.getInstance(
                        checkApplication(activity));
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }
   /**
     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
     * an activity), associated with this {@code ViewModelProvider}.
     * <p>
     * The created ViewModel is associated with the given scope and will be retained
     * as long as the scope is alive (e.g. if it is an activity, until it is
     * finished or process is killed).
     *
     * @param modelClass The class of the ViewModel to create an instance of it if it is not
     *                   present.
     * @param <T>        The type parameter for the ViewModel.
     * @return A ViewModel that is an instance of the given type {@code T}.
     */
    @NonNull
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    
    /**
     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
     * an activity), associated with this {@code ViewModelProvider}.
     * <p>
     * The created ViewModel is associated with the given scope and will be retained
     * as long as the scope is alive (e.g. if it is an activity, until it is
     * finished or process is killed).
     *
     * @param key        The key to use to identify the ViewModel.
     * @param modelClass The class of the ViewModel to create an instance of it if it is not
     *                   present.
     * @param <T>        The type parameter for the ViewModel.
     * @return A ViewModel that is an instance of the given type {@code T}.
     */
    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }

以下是原理圖:


ViewModel相關類圖
ViewModel相關類圖
  • ViewModelProviders是ViewModel工具類至非,該類提供了通過Fragment和Activity得到ViewModel的方法钠署,而具體實現又是有ViewModelProvider實現的。ViewModelProvider是實現ViewModel創(chuàng)建荒椭、獲取的工具類谐鼎。在ViewModelProvider中定義了一個創(chuàng)建ViewModel的接口類——Factory。ViewModelProvider中有個ViewModelStore對象趣惠,用于存儲ViewModel對象狸棍。
  • ViewModelStore是存儲ViewModel的類身害,具體實現是通過HashMap來保存ViewModle對象。
  • ViewModel是個抽象類草戈,里面只定義了一個onCleared()方法塌鸯,該方法在ViewModel不在被使用時調用。ViewModel有一個子類AndroidViewModel唐片,這個類是便于要在ViewModel中使用Context對象丙猬,因為我們前面提到是不能在ViewModel中持有Activity的引用。
  • ViewModelStores是ViewModelStore的工廠方法類费韭,它會關聯HolderFragment茧球,HolderFragment有個嵌套類——HolderFragmentManager。
    ?
    最后星持,Lifecycle袜腥,LiveData,ViewModel結合起來使用钉汗,優(yōu)化項目整體架構羹令。

?
?
?
?
?

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市损痰,隨后出現的幾起案子福侈,更是在濱河造成了極大的恐慌,老刑警劉巖卢未,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肪凛,死亡現場離奇詭異,居然都是意外死亡辽社,警方通過查閱死者的電腦和手機伟墙,發(fā)現死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滴铅,“玉大人戳葵,你說我怎么就攤上這事『撼祝” “怎么了拱烁?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長噩翠。 經常有香客問我戏自,道長,這世上最難降的妖魔是什么伤锚? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任擅笔,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘猛们。我一直安慰自己念脯,他們只是感情好,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布阅懦。 她就那樣靜靜地躺著,像睡著了一般徘铝。 火紅的嫁衣襯著肌膚如雪耳胎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天惕它,我揣著相機與錄音怕午,去河邊找鬼。 笑死淹魄,一個胖子當著我的面吹牛郁惜,可吹牛的內容都是我干的。 我是一名探鬼主播甲锡,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼兆蕉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了缤沦?” 一聲冷哼從身側響起虎韵,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缸废,沒想到半個月后包蓝,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡企量,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年测萎,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片届巩。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡硅瞧,死狀恐怖,靈堂內的尸體忽然破棺而出恕汇,到底是詐尸還是另有隱情零酪,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布拇勃,位于F島的核電站四苇,受9級特大地震影響,放射性物質發(fā)生泄漏方咆。R本人自食惡果不足惜月腋,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧榆骚,春花似錦片拍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至碉钠,卻和暖如春纲缓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喊废。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工祝高, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人污筷。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓工闺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瓣蛀。 傳聞我的和親對象是個殘疾皇子陆蟆,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容