Jetpack系列(二蹦狂、架構(gòu))

一磨总、數(shù)據(jù)綁定庫(kù)

數(shù)據(jù)綁定庫(kù)是一種支持庫(kù)嗦明,可以使用聲明格式將布局中的界面組件綁定在應(yīng)用的數(shù)據(jù)源。
布局通常使用該框架方法的代碼在Activity中定義蚪燕;
例如:在一下代碼調(diào)用findViewByid()來(lái)查找TextView空間并將其綁定到viewModel變量的userNama屬性:

對(duì)TextView賦值

//普通方式
findViewById<TextView>(R.id.sample_text).apply {
        text = viewModel.userName
    }

//數(shù)據(jù)綁定方式
<TextView
        android:text="@{viewmodel.userName}" />
1.1布局和綁定表達(dá)式
<layout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto">
        <data>
            <variable
                name="viewmodel"
                type="com.myapp.data.ViewModel" />
        </data>
        <ConstraintLayout... /> <!-- UI layout's root element -->
    </layout>
1.2使用可觀察的數(shù)據(jù)對(duì)象

數(shù)據(jù)綁定庫(kù)提供了觀察數(shù)據(jù)更改情況的類(lèi)和方法娶牌,不必操心底層數(shù)據(jù)源發(fā)生改變時(shí)刷新界面奔浅。你可以將變量或者屬性設(shè)為可觀察
可觀察字段:ObservableBoolean、ObservableByte诗良、ObservableChar汹桦、ObservableShort、ObservableInt鉴裹、ObservableLong舞骆、ObservableFloat、ObservableDouble径荔、ObservableParcelable
可觀察的集合:ObservableArrayMap督禽、ObservableArrayList
可觀察的對(duì)象:
實(shí)現(xiàn)了Observable接口的類(lèi)允許注冊(cè)一個(gè)監(jiān)聽(tīng)器,當(dāng)可觀察對(duì)象的屬性更改時(shí)通知這個(gè)監(jiān)聽(tīng)器猖凛。
DataBinding庫(kù)提供了BaseObservable類(lèi),它實(shí)現(xiàn)了監(jiān)聽(tīng)器注冊(cè)機(jī)制赂蠢。繼承了BaseObservable的數(shù)據(jù)類(lèi)負(fù)責(zé)通知屬性何時(shí)更改绪穆。

private static class User extends BaseObservable {
    private String firstName;
    private String lastName;

    @Bindable
    public String getFirstName() {
        return this.firstName;
    }

    @Bindable
    public String getLastName() {
        return this.lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }
}
1.3生成的綁定類(lèi)

數(shù)據(jù)綁定卡可以生成用于訪問(wèn)布局變量和View視圖的Binding類(lèi),DataBing庫(kù)會(huì)為每個(gè)布局文件生成一個(gè)binding類(lèi)辨泳,生成一個(gè)binding類(lèi)將布局中的View與變量連接起來(lái);
該類(lèi)的名稱(chēng)基于布局文件的名稱(chēng)玖院,將布局名稱(chēng)裝換為pascal格式并向其添加Binding后綴菠红,例如activity_main.layout相應(yīng)生成MainActivityBinding類(lèi)。
有時(shí)候預(yù)先不知道綁定類(lèi)型难菌,在這種情況下试溯,可以使用DataBindingUtil類(lèi)創(chuàng)建綁定;

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
//方式一
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
//方式二
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
//方式三(最常用)
T createDataBinding = DataBindingUtil.setContentView(this, layoutId)
}

1.4綁定適配器

每個(gè)布局表達(dá)式都會(huì)與對(duì)應(yīng)的適配器綁定,要求必須進(jìn)行框架調(diào)用來(lái)設(shè)置對(duì)應(yīng)的屬性監(jiān)聽(tīng)器郊酒;例如setText()設(shè)置文本屬性遇绞,或者setOnClickListener()設(shè)置點(diǎn)擊事件監(jiān)聽(tīng)屬性。
自定義適配器燎窘,例如
舉例一:設(shè)置View隱藏顯示

   @BindingAdapter("app:goneUnless")
    public static void goneUnless(View view, Boolean visible) {
        view.visibility = visible ? View.VISIBLE : View.GONE;
    }

舉例二:設(shè)置ImageView加載圖片

//代碼中
@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
  Picasso.with(view.getContext()).load(url).error(error).into(view);
}
//布局中調(diào)用
<ImageView 
        app:imageUrl="@{venue.imageUrl}" 
        app:error="@{@drawable/venueError}" />
1.5將布局視圖綁定到架構(gòu)組件

AndoridX庫(kù)中包含了架構(gòu)組件摹闽,DataBinding可以與架構(gòu)組件無(wú)縫協(xié)作,進(jìn)一步簡(jiǎn)化UI的開(kāi)發(fā)褐健;

1.5.1使用LiveData通知UI有關(guān)數(shù)據(jù)更新

與上面提到的Observable觀察者對(duì)象不同付鹿,LiveData對(duì)象知道訂閱數(shù)據(jù)更改的觀察者的生命周期蚜迅。
在使用LiveData對(duì)象前舵匾,需要指定生命周期所有者來(lái)定義LiveData對(duì)象的范圍,下面以Activity作為生命周期所有者為例:

class ViewModelActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Inflate view and obtain an instance of the binding class.
        UserBinding binding = DataBindingUtil.setContentView(this, R.layout.user);

        // Specify the current activity as the lifecycle owner.
        binding.setLifecycleOwner(this);
    }
}

我們可以使用ViewModel組件將數(shù)據(jù)綁定到布局谁不,在ViewModel組件中坐梯,我們可以使用LiveData對(duì)象來(lái)轉(zhuǎn)換數(shù)據(jù)或合并多個(gè)數(shù)據(jù)源;

class ScheduleViewModel extends ViewModel {
    LiveData username;

    public ScheduleViewModel() {
        String result = Repository.userName;
        userName = Transformations.map(result, result -> result.value);
}
1.5.2使用ViewModel管理與UI相關(guān)數(shù)據(jù)

使用ViewModel庫(kù)可以將UI邏輯移出布局并放入易于測(cè)試的組件中刹帕,確保View在需要從數(shù)據(jù)源綁定和解綁吵血;

class ViewModelActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Obtain the ViewModel component.
        UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class);

        // Inflate view and obtain an instance of the binding class.
        UserBinding binding = DataBindingUtil.setContentView(this, R.layout.user);

        // Assign the component to a property in the binding class.
        binding.viewmodel = userModel;
    }
}

二馏段、Lifecycle+LiveData+ViewModel庫(kù)

Lifecycle:生命周期感知組件可執(zhí)行操作來(lái)響應(yīng)另一個(gè)組件(Activity或Fragment)的生命周期狀態(tài)的變化。
LiveData:在底層數(shù)據(jù)庫(kù)更改時(shí)通知視圖践瓷;
ViewModel:以注重生命周期的方式管理界面相關(guān)數(shù)據(jù)院喜;
Lifecycle是一個(gè)類(lèi),用于存儲(chǔ)有個(gè)組件(Activity晕翠、Fragment)的生命周期狀態(tài)信息喷舀,并允許其他對(duì)象觀察此狀態(tài);

2.1通過(guò)注解監(jiān)控組件生命周期

你可以桐楠格Lifeceycle類(lèi)的addObserver()方法并專(zhuān)遞觀察者的實(shí)例來(lái)添加觀察者淋肾;

    public class MyObserver implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        public void connectListener() {
            ...
        }

@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        public void disconnectListener() {
            ...
        }
    }
    myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

Lifecycles+LiveData+ViewModel 在項(xiàng)目中的組合使用代碼案例 https://codelabs.developers.google.com/codelabs/android-lifecycles/index.html?index=..%2F..%2Findex#0

三硫麻、Navigation導(dǎo)航

支持用戶(hù)導(dǎo)航、進(jìn)入樊卓、退出應(yīng)用中不同內(nèi)容片斷的交互拿愧,導(dǎo)航組件主要由三部分組成:

  • 導(dǎo)航圖:在一個(gè)集中位置包含所有導(dǎo)航相關(guān)信息XML資源,應(yīng)用內(nèi)單個(gè)內(nèi)容區(qū)域以及用戶(hù)可以通過(guò)應(yīng)用獲取的可能路勁碌尔;
  • NavHost:顯示導(dǎo)航圖中目標(biāo)的空白容器浇辜,導(dǎo)航組件包含一個(gè)默認(rèn)的NavHost實(shí)現(xiàn)(NavHostFragment),可顯示Fragment目標(biāo)唾戚;
  • NavController:在NavHost中管理應(yīng)用導(dǎo)航的對(duì)象柳洋,當(dāng)用戶(hù)在整個(gè)應(yīng)用中移動(dòng)時(shí),NavController會(huì)安排NavHost中的目標(biāo)內(nèi)容交換叹坦;
優(yōu)勢(shì):
  • 處理Fragment事務(wù)熊镣;
  • ViewModel支持,在范圍內(nèi)先訂導(dǎo)航圖募书,在圖表目標(biāo)之間共享與界面相關(guān)的數(shù)據(jù)绪囱;
  • 實(shí)現(xiàn)處理深層鏈接;
  • 默認(rèn)情況下莹捡,正確處理往返操作鬼吵;

官方代碼實(shí)現(xiàn)流程:https://codelabs.developers.google.com/codelabs/android-navigation/index.html?index=..%2F..%2Findex#0

四、Paging分頁(yè)組件

分頁(yè)庫(kù)支持當(dāng)加載大數(shù)據(jù)時(shí)道盏,一次加載或顯示一小塊數(shù)據(jù)而柑,按需載入部分?jǐn)?shù)據(jù)減少網(wǎng)絡(luò)帶寬和系統(tǒng)資源使用量;減少內(nèi)存資源的消耗
PagedList類(lèi)荷逞,用于加載應(yīng)用數(shù)據(jù)塊或頁(yè)面媒咳,隨著所需數(shù)據(jù)的增加,系統(tǒng)會(huì)將分頁(yè)到現(xiàn)有的PagedList對(duì)象中种远,如果任何已加載的數(shù)據(jù)發(fā)生改變涩澡,會(huì)沖LiveData或RXJava2的對(duì)象向可觀察數(shù)據(jù)存儲(chǔ)發(fā)出一個(gè)新的PagedList實(shí)例。隨著PagedList對(duì)象的生成坠敷,應(yīng)用界面會(huì)呈現(xiàn)其內(nèi)容妙同,同時(shí)還會(huì)考慮界面控件的生命周期射富;
下面使用PagedList與LiveData存儲(chǔ)加載和顯示數(shù)據(jù):

    public class ConcertViewModel extends ViewModel {
        private ConcertDao concertDao;
        public final LiveData<PagedList<Concert>> concertList;

        // Creates a PagedList object with 50 items per page.
        public ConcertViewModel(ConcertDao concertDao) {
            this.concertDao = concertDao;
            concertList = new LivePagedListBuilder<>(
                    concertDao.concertsByDate(), 50).build();
        }
    }

每個(gè)PageList實(shí)例對(duì)象都對(duì)應(yīng)DataSource對(duì)象加載應(yīng)用數(shù)據(jù)的最新快照。數(shù)據(jù)從后端或者數(shù)據(jù)庫(kù)流向PagedList對(duì)象粥帚;

使用 LiveData 觀察分頁(yè)數(shù)據(jù)+Room持久性庫(kù):

    @Dao
    public interface ConcertDao {
        // The Integer type parameter tells Room to use a PositionalDataSource
        // object, with position-based loading under the hood.
        @Query("SELECT * FROM concerts ORDER BY date DESC")
        DataSource.Factory<Integer, Concert> concertsByDate();
    }

    public class ConcertViewModel extends ViewModel {
        private ConcertDao concertDao;
        public final LiveData<PagedList<Concert>> concertList;

        public ConcertViewModel(ConcertDao concertDao) {
            this.concertDao = concertDao;
            concertList = new LivePagedListBuilder<>(
                concertDao.concertsByDate(), /* page size */ 50).build();
        }
    }

    public class ConcertActivity extends AppCompatActivity {
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ConcertViewModel viewModel =
                    new ViewModelProvider(this).get(ConcertViewModel.class);
            RecyclerView recyclerView = findViewById(R.id.concert_list);
            ConcertAdapter adapter = new ConcertAdapter();
            viewModel.concertList.observe(this, adapter::submitList);
            recyclerView.setAdapter(adapter);
        }
    }

    public class ConcertAdapter
            extends PagedListAdapter<Concert, ConcertViewHolder> {
        protected ConcertAdapter() {
            super(DIFF_CALLBACK);
        }

        @Override
        public void onBindViewHolder(@NonNull ConcertViewHolder holder,
                int position) {
            Concert concert = getItem(position);
            if (concert != null) {
                holder.bindTo(concert);
            } else {
                // Null defines a placeholder item - PagedListAdapter automatically
                // invalidates this row when the actual object is loaded from the
                // database.
                holder.clear();
            }
        }

        private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK =
                new DiffUtil.ItemCallback<Concert>() {
            // Concert details may have changed if reloaded from the database,
            // but ID is fixed.
            @Override
            public boolean areItemsTheSame(Concert oldConcert, Concert newConcert) {
                return oldConcert.getId() == newConcert.getId();
            }

            @Override
            public boolean areContentsTheSame(Concert oldConcert,
                    Concert newConcert) {
                return oldConcert.equals(newConcert);
            }
        };
    }

官方代碼流程:https://codelabs.developers.google.com/codelabs/android-paging/index.html?index=..%2F..%2Findex#0

五胰耗、Room持久性庫(kù)

基于SQLite基礎(chǔ)上提供的一個(gè)抽象層,讓用戶(hù)利用SQLiter強(qiáng)大功能同事獲取更強(qiáng)健的數(shù)據(jù)庫(kù)訪問(wèn)機(jī)制芒涡。Room包含三個(gè)主要組件:

  • 數(shù)據(jù)庫(kù):數(shù)據(jù)庫(kù)持有者柴灯,用于保留持久關(guān)系型數(shù)據(jù)的底層連接;
  • Entity:數(shù)據(jù)庫(kù)中的表费尽;
  • DAO:訪問(wèn)數(shù)據(jù)庫(kù)的方法赠群;
    工作流程:使用Room數(shù)據(jù)庫(kù)來(lái)獲取與該數(shù)據(jù)庫(kù)關(guān)聯(lián)的數(shù)據(jù)訪問(wèn)對(duì)象DAO,然后,應(yīng)用使用每個(gè)DAO對(duì)象從數(shù)據(jù)庫(kù)中獲取實(shí)體旱幼,然后再將對(duì)這些實(shí)體的所有更改保存回?cái)?shù)據(jù)庫(kù)中查描,最后,應(yīng)用使用實(shí)體獲取和設(shè)置與數(shù)據(jù)庫(kù)中的表列相對(duì)應(yīng)的值柏卤;
    下面是代碼舉例:
//實(shí)體對(duì)象User
    @Entity
    public class User {
        @PrimaryKey
        public int uid;

        @ColumnInfo(name = "first_name")
        public String firstName;

        @ColumnInfo(name = "last_name")
        public String lastName;
    }
    
//對(duì)應(yīng)的DAO數(shù)據(jù)庫(kù)-----UesrDao
    @Dao
    public interface UserDao {
        @Query("SELECT * FROM user")
        List<User> getAll();

        @Query("SELECT * FROM user WHERE uid IN (:userIds)")
        List<User> loadAllByIds(int[] userIds);

        @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
               "last_name LIKE :last LIMIT 1")
        User findByName(String first, String last);

        @Insert
        void insertAll(User... users);

        @Delete
        void delete(User user);
    }

//AppDatabase
    @Database(entities = {User.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase {
        public abstract UserDao userDao();
    }

//使用一下代碼獲取已創(chuàng)建的數(shù)據(jù)庫(kù)的實(shí)例:
    AppDatabase db = Room.databaseBuilder(getApplicationContext(),
            AppDatabase.class, "database-name").build();

官方提供代碼:https://github.com/android/architecture-components-samples

六:WorkManager任務(wù)調(diào)度

用處:可以清楚調(diào)度冬三,即時(shí)在應(yīng)用退出或設(shè)備重啟時(shí),仍應(yīng)運(yùn)行的可延遲異步任務(wù)闷旧。

應(yīng)用場(chǎng)景:

  • 向后端服務(wù)器上傳日志和分析數(shù)據(jù)(應(yīng)用崩潰時(shí))长豁;
  • 定期將應(yīng)用數(shù)據(jù)與服務(wù)器同步

不適用場(chǎng)景:

  • 應(yīng)用進(jìn)程結(jié)束時(shí)能夠安全終止的運(yùn)行中后臺(tái)工作;
  • 需要立即執(zhí)行的任務(wù)忙灼;

代碼應(yīng)用:

6.1創(chuàng)建后臺(tái)任務(wù)

任務(wù)使用Worker類(lèi)定義,doWork()方法在WorkManager提供的后臺(tái)線(xiàn)程上同步運(yùn)行钝侠;
doWork()返回的Result會(huì)通知WorkManager任務(wù)该园;

  • 已完成:Result.success();
  • 已失敗:Result.failure();
  • 需要稍后重試:Result.retry();
//上次圖像的Worker
    public class UploadWorker extends Worker {

        public UploadWorker(
            @NonNull Context context,
            @NonNull WorkerParameters params) {
            super(context, params);
        }

        @Override
        public Result doWork() {
          // Do the work here--in this case, upload the images.

          uploadImages()

          // Indicate whether the task finished successfully with the Result
          return Result.success()
        }
    }

6.2可以配置運(yùn)行任務(wù)的方式和時(shí)間

Work定義是工作單元帅韧,WorkRequest定義工作運(yùn)行方式和時(shí)間里初。任務(wù)可以是一次性或者周期性的;
一次性使用OneTimeWorkRequest忽舟,周期性使用:PeriodicWorkRequest
示例:

    OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadWorker.class)
            .build()
``
#####6.3提交任務(wù)給系統(tǒng)
WorkManager.getInstance(myContext).enqueue(uploadWorkRequest);



?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末双妨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子叮阅,更是在濱河造成了極大的恐慌刁品,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浩姥,死亡現(xiàn)場(chǎng)離奇詭異挑随,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)勒叠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)兜挨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)膏孟,“玉大人,你說(shuō)我怎么就攤上這事拌汇∑馍#” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵噪舀,是天一觀的道長(zhǎng)幕垦。 經(jīng)常有香客問(wèn)我,道長(zhǎng)傅联,這世上最難降的妖魔是什么先改? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮蒸走,結(jié)果婚禮上仇奶,老公的妹妹穿的比我還像新娘。我一直安慰自己比驻,他們只是感情好该溯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著别惦,像睡著了一般狈茉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掸掸,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天氯庆,我揣著相機(jī)與錄音,去河邊找鬼扰付。 笑死堤撵,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的羽莺。 我是一名探鬼主播实昨,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盐固!你這毒婦竟也來(lái)了荒给?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤刁卜,失蹤者是張志新(化名)和其女友劉穎志电,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體长酗,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溪北,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片之拨。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茉继,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蚀乔,到底是詐尸還是另有隱情烁竭,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布吉挣,位于F島的核電站派撕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏睬魂。R本人自食惡果不足惜终吼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望氯哮。 院中可真熱鬧际跪,春花似錦、人聲如沸喉钢。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肠虽。三九已至幔戏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間税课,已是汗流浹背闲延。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伯复,地道東北人慨代。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像啸如,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子氮惯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349