Dagger2-從入門到精通(下)

前言

繼上篇文章,這篇我們來學習Dagger2的高級用法。如果沒看上篇文章的話最好先看下上篇文章再來學習本章穆役,因為本章是接續(xù)上篇文章來寫的,直接來看的話可能效果不是很好梳凛,Dagger2入門-基本使用(上)耿币。


注解

  • @Qualifier: 要作用是用來區(qū)分不同對象實例
    @Named 其實是@Qualifier的一種實現
  • Scope
  • Subcomponent
  • Lazy與Provider

@Qualifier

上面也提到了,作用是用來區(qū)分不同對象的實例的韧拒。平時我們可能會對一個類創(chuàng)建不同的構造方法以滿足不同的需求淹接,假設現在現在ApiSevice有2個構造方法十性,根據不同情況調用不同方法。這時就要用到@Named標簽(@Named是@Qualifier的一種實現).

public class ApiService {

    public ApiService(Context context) {

    }

    public ApiService(String url) {
        Log.d("TAG", "ApiService: " + url);
    }

    public void register() {
        Log.d("TAG", "ApiService: ");
    }

}

可以看到ApiService有2個不同的構造方法塑悼,并且參數不同劲适。下面我們就要在UserModule分別創(chuàng)建這兩個對象的實例。

@Module
public class UserModule {

    Context context;

    public UserModule(Context context) {
        this.context = context;
    }

    @Provides
    @Named("dev")
    public ApiService provideApiServiceDev(String url) {
        ApiService apiService = new ApiService(url);
        Log.d("TAG", "provideApiServiceDev: " + apiService);
        return apiService;
    }

    @Provides
    @Named("release")
    public ApiService provideApiServiceRelease() {
        ApiService apiService = new ApiService(context);
        Log.d("TAG", "provideApiServiceRelease: " + apiService);

        return apiService;
    }

    @Provides
    public Context provideContext() {
        return context;
    }

    @Provides
    public String providerUrl() {
        return "www.baidu.com";
    }


    @Provides
    public UserManager provideUserManager(ApiService apiService, UserStroe userStroe) {
        return new UserManager(userStroe, apiService);
    }
}

可以看到我們?yōu)锳piService分別提供了2個構造方法但參數不同厢蒜。然后分別用@Named("dev")和@Named("release")注解霞势,表明這是2個不同的構造方法。

(提問:這里為什么我們可以直接引用參數參數中的context和url呢斑鸦?因為我們提供了providerUrl()和provideContext()所以可以直接使用)

那么我們看下MainActivity中如何調用的:

    @Named("dev")
    @Inject
    ApiService mApiService;

    @Named("release")
    @Inject
    ApiService mApiService1;
    private boolean is_Dev = true;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerUserComponet.builder().userModule(new UserModule(this)).build().inject(this);
        Log.d("TAG", "mApiService= " + mApiService);
        Log.d("TAG", "mApiService1= " + mApiService1);
        if (is_Dev) {
            mApiService.register();
        } else {
            mApiService1.register();
        }
    }
}

可以看到我們在modle中用@Named區(qū)分支示,在使用是只需在 @Inject時添加上@Named注解就會創(chuàng)建對應注解的實例,然后我們用一個is_Dev標簽鄙才,表明不同情況使用不同的對象颂鸿。我們來看下打印結果:

 D/TAG: ApiService: www.baidu.com
 D/TAG: provideApiServiceDev: .ApiService@4a7c44c8
 D/TAG: provideApiServiceRelease: .ApiService@4a7c477c
 D/TAG: mApiService= .ApiService@4a7c44c8
 D/TAG: mApiService1= .ApiService@4a7c477c
注意:我們在Moudle用了@Named標簽,在調用時也需要加上@Named標簽攒庵,如果在調用處不使用@Named注解就需要在Moudle中創(chuàng)建對應沒有用@Named注解的實例方法

通過字符串標記一個對象嘴纺,容易導致前后不匹配,所以除了使用這種方法浓冒,我們還可以通過自定義注解的方式栽渴。
那么如何實現自定義注解@Qualifier呢?很簡單稳懒,@Named就是@Qualifier的一種實現闲擦,我看他是怎么實現的,我們就照葫蘆畫瓢被:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}
  • @Qualifier :注明是Qualifier(關鍵詞)

  • @Documented :標記在文檔

  • @Retention(RUNTIME) :運行時級別
    可以看到@Qualifier決定它關鍵性的作用是用來區(qū)分不同對象的场梆。里面的String類型就是我們之前填寫的("dev")和("release")用來區(qū)分的墅冷,那么我也照著寫我們的自定義的標簽:

@Qualifier
@Retention(RUNTIME)
public @interface Dev {
   
}

@Qualifier
@Retention(RUNTIME)
public @interface Release {

}

我們創(chuàng)建了2個自定義注解,當然你全賦值過來也是沒有問題的或油,這里我去掉了不必要的部分寞忿。因為本身我們就不想用字符串區(qū)分了,所以我把字符串參數去掉了顶岸。


  @Provides
    @Release
    public ApiService provideApiServiceDev(String url) {
        ApiService apiService = new ApiService(url);
        Log.d("TAG", "provideApiServiceDev: " + apiService);
        return apiService;
    }

    @Provides
    @Dev
    public ApiService provideApiServiceRelease() {
        ApiService apiService = new ApiService(context);
        Log.d("TAG", "provideApiServiceRelease: " + apiService);
        return apiService;
    }  
    @Dev
    @Inject
    ApiService mApiService;

    @Release
    @Inject
    ApiService mApiService1;

用法和上面是結果是一樣的腔彰。


@Singleton

我們先看下他的里面是什么樣子的:

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
友情提示:我剛學習的時候就總搞不懂總以為@Scope,@Singleton辖佣,@Qualifier霹抛,@Named是4個不同作用的操作符,其實他就是兩兩一對的卷谈,@Named是@Qualifier具體實現杯拐,@Singleton是@Scope的具體實現;@Scope和@Qualifier相當于不同作用注解的關鍵字

他就是用@Scope注解修飾的注解。這樣我們就知道了藕施。那么首先我們來看下,如果不用單例什么樣的:

    @Inject
    ApiService mApiService1;
    
    @Inject
    ApiService mApiService2;
    private boolean is_Dev = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerUserComponet.builder().userModule(new UserModule(this)).build().inject(this);

        Log.d("TAG", "mApiService1= " + mApiService1);
        Log.d("TAG", "mApiService2= " + mApiService2);
    }
}

結果:

D/TAG: mApiService1= .ApiService@4a7c7904
D/TAG: mApiService2= .ApiService@4a7c7aa8

可以看到確實創(chuàng)建了2個實例凸郑,那么如何使用單例注解:
首先在Module中將創(chuàng)建實例的方法加上@Singleton

@Singleton
@Provides
public ApiService provideApiService() {
     ApiService apiService = new ApiService(context);
     Log.d("TAG", "provideApiService: " + apiService);
     return apiService;
    }    

然后在@Component中也加上@Singleton:

@Singleton
@Component(modules = {UserModule.class})
public interface UserComponet {

    void inject(MainActivity activity);
}

@Singleton在使用時調用處正常書寫:

@Inject
ApiService mApiService1;

@Inject
ApiService mApiService2;

打印結果:

provideApiService: com.example.ggxiaozhi.dagger2.ApiService@4a7c5200   
mApiService1= com.example.ggxiaozhi.dagger2.ApiService@4a7c5200
mApiService2= com.example.ggxiaozhi.dagger2.ApiService@4a7c5200 

可以看到只調用了一次創(chuàng)建對象的方法裳食。2個對象其實是一個實例。

注意:
  • module 的 provide 方法使用了 scope 芙沥,那么 component 就必須使用同一個注解
  • @Singleton 的生命周期依附于component诲祸,同一個module被不同的@Component依賴結果也不一樣
  • @Singleton分為Activity級別單例生命周期和全局的生命周期單例
    這里第一點注意我們通過上面的事例比較容易理解,那么第二點是什么意思呢而昨?這句話的意思在于@Singleton 的生命周期依附于component救氯。那么實際測試下。我們在創(chuàng)建一個LoginAcyivity歌憨,然后MainActivity創(chuàng)建對象后直接跳轉LoginAcyivity着憨。并創(chuàng)建LogingConponent如下:
@Singleton
@Component(modules = UserModule.class)
public interface LoginComponent {
    void inject(LoginActivity activity);
}

LogingConponent也依賴UserModule.class。然后在LoginAcyivity中創(chuàng)建ApiService如下:

public class LoginActivity extends AppCompatActivity {

    @Inject
    ApiService mApiService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        DaggerLoginComponent.builder().userModule(new UserModule(this)).build().inject(this);
        Log.d("TAG", "LoginActivity-->mApiService : "+mApiService);
    }
}

運行結果:

 D/TAG: provideApiService: com.example.ggxiaozhi.dagger2.ApiService@4a7c5994
 D/TAG: MainActivity-->mApiService1= com.example.ggxiaozhi.dagger2.ApiService@4a7c5994
 D/TAG: MainActivity-->mApiService2= com.example.ggxiaozhi.dagger2.ApiService@4a7c5994
 D/TAG: provideApiService: com.example.ggxiaozhi.dagger2.ApiService@4a7d54f8
 D/TAG: LoginActivity-->mApiService : com.example.ggxiaozhi.dagger2.ApiService@4a7d54f8

可以看你到LoginComponent和UserComponent都依賴UserMoudle务嫡,并且創(chuàng)建ApiService已經加了@Singleton注解但是在MainActivity中是單例但是在LoginActivity又創(chuàng)建了不同的ApiService的實例甲抖,這就是上面提到的因為LoginComponent和UserComponent為兩個不同的@Component,@Singleton的生命周期依附于component心铃,同一個module provide singleton ,不同component 也是不一樣准谚。所以會看到這樣的結果。如果我們修改下代碼呢去扣?如下:

@Singleton
@Component(modules = {UserModule.class})
public interface UserComponet {

    void inject(MainActivity activity);
    void inject(LoginActivity activity);
}

然后在LoginActivity中也引用UserComponent而不去引用LogingComponent呢柱衔?

public class LoginActivity extends AppCompatActivity {

    @Inject
    ApiService mApiService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        UserComponet userComponet = DaggerUserComponet.builder().userModule(new UserModule(this)).build();
        userComponet.inject(this);
        Log.d("TAG", "LoginActivity-->mApiService : "+mApiService);
        Log.d("TAG", "LoginActivity-->UserComponet : "+userComponet);
    }
}

這也也是不行的。為什么?看打印結果:

  D/TAG: provideApiService: com.example.ggxiaozhi.dagger2.ApiService@4a7c454c
 D/TAG: MainActivity-->mApiService1= com.example.ggxiaozhi.dagger2.ApiService@4a7c454c
 D/TAG: MainActivity-->mApiService2= com.example.ggxiaozhi.dagger2.ApiService@4a7c454c
 D/TAG: MainActivity-->UserComponet= com.example.ggxiaozhi.dagger2.DaggerUserComponet@4a7c382c
 D/TAG: provideApiService: com.example.ggxiaozhi.dagger2.ApiService@4a7d3ccc
 D/TAG: LoginActivity-->mApiService : com.example.ggxiaozhi.dagger2.ApiService@4a7d3ccc
 D/TAG: LoginActivity-->UserComponet : com.example.ggxiaozhi.dagger2.DaggerUserComponet@4a7d3c9c

可以看到愉棱,在UserComponet在LoginActivity和MainActivity中會創(chuàng)建2個不同的實例唆铐,當然會創(chuàng)建2個不同的mApiService了。如果想實現全局單例就要用到自定義@Scope注解奔滑。


自定義@Scope注解

上面是屬于Activity生命周期單例或链。下面我們就創(chuàng)建全局生命周期單例。

1. 創(chuàng)建全局AppModule:
@Module
public class AppMoudle {
    private MyApplication context;

    public AppMoudle(MyApplication context) {
        this.context = context;
    }

    @Singleton
    @Provides
    public ApiService provideApiService() {
        ApiService apiService = new ApiService(context);
        Log.d("TAG", "provideApiService: " + apiService);
        return apiService;
    }
}
2. 創(chuàng)建全局AppComponent:
@Singleton
@Component(modules = AppMoudle.class)
public interface AppComponent {

    /**
     * 全局單例档押。所以不用Inject Activity
     *
     * @return 向下返回ApiService實例
     */
    ApiService getApiService();
}
3. 在MyApplication實例化AppComponent:

單例的依托于他所在的Component中澳盐,所以需要在Application中進行實例化。

public class MyApplication extends Application {

    private AppComponent mAppComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        mAppComponent = DaggerAppComponent.builder().appMoudle(new AppMoudle(this)).build();
    }

    public AppComponent getAppComponent() {
        return mAppComponent;
    }
}

4.自定義@Scope:
@Scope
@Retention(RUNTIME)
public @interface PerActivity {
}
5. 讓其他Component依賴:
@PerActivity
@Component(modules = {UserModule.class},dependencies = AppComponent.class)
public interface UserComponet {

    void inject(MainActivity activity);
}
@PerActivity
@Component(modules = UserModule.class,dependencies = AppComponent.class)
public interface LoginComponent {
    void inject(LoginActivity activity);
}
6. 調用

MainActivity:

DaggerUserComponet.builder().
                appComponent(((MyApplication)getApplicationContext()).getAppComponent()).
                build().inject(this);

LoginActivity:

DaggerLoginComponent.builder().
                 appComponent(((MyApplication)getApplicationContext()).getAppComponent()).
                 build().inject(this);
打印結果:
 D/TAG: provideApiService: com.example.ggxiaozhi.dagger2.ApiService@4a7c3e8c
 D/TAG: MainActivity-->mApiService1= com.example.ggxiaozhi.dagger2.ApiService@4a7c3e8c
 D/TAG: MainActivity-->mApiService2= com.example.ggxiaozhi.dagger2.ApiService@4a7c3e8c
 D/TAG: LoginActivity-->mApiService= com.example.ggxiaozhi.dagger2.ApiService@4a7c3e8c

可以看到這次全局都是用的一個單例了令宿。

注意:
  • 可以看到第4步我們自定義@Scope注解PerActivity叼耙,因為component的dependencies與component自身的scope不能相同,即組件之間的scope不同粒没。所以我們自己定義筛婉。
  • Singleton的組件不能依賴其他scope的組件,只能其他scope的組件依賴Singleton的組件 如下:
    AppComponent已經用@Singleton修飾就不能再去依賴(dependencies=XXX.class)別的Component。
    clipboard.png
  • 但是其他scope的組件 可以依賴其他組件:


    clipboardd.png

@Subcomponent

作用有些類似Component中的dependencies作用爽撒。特點:

  1. Subcomponent同時具備兩種不同生命周期的scope, SubComponent具備了父Component擁有的Scope入蛆,也具備了自己的Scope。
  2. SubComponent的Scope范圍小于父Component

我們用代碼使用體會下:
FComponent

//第一步
@Module
public class FModule {

    @Singleton
    @Provides
    public User provideUser() {
        return new User();
    }
}
//第二步
@Singleton
@Component(modules = FModule.class)
public interface FComponent {
  //需要將SubComponent 追加到被依賴的Component中
    CComponent addCComponent();
}

//第三步
public class MyApplication extends Application {

    private AppComponent mAppComponent;

    private FComponent mFComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        mAppComponent = DaggerAppComponent.builder().appMoudle(new AppMoudle(this)).build();
        mFComponent = DaggerFComponent.builder().build();
    }

    public AppComponent getAppComponent() {
        return mAppComponent;
    }

    public FComponent getFComponent() {
        return mFComponent;
    }
}

CComponent:

@Module
public class CModule {

    @PerActivity
    @Provides
    public UserStroe provideUser(User user) {
        return new UserStroe(user);
    }
}
 
@PerActivity
@Subcomponent(modules = CModule.class)
public interface CComponent {
    void Inject(Main2Activity activity);
}  


調用:

public class Main2Activity extends AppCompatActivity {
    private static final String TAG = "Main2Activity";
    @Inject
    UserStroe mUserStroe;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        ((MyApplication)getApplicationContext()).getFComponent().getCComponent().Inject(this);
        Log.d(TAG, "onCreate: "+mUserStroe);
    }
}

首先我們先創(chuàng)建FComponent硕勿,他屬于App級別的哨毁。我們在MyApplication創(chuàng)建它。FComponent中調用者提供CComponent源武。然后有CComponent扼褪。這和我們之前使用有些不同。之前我們都是通過Avtivity級別創(chuàng)建粱栖,然后填入App級別的參數话浇。這個使用正好相反。優(yōu)勢:

不需要在被依賴的Component顯示提供依賴
不需要使用更多的DaggerXXXXComponent對象來創(chuàng)建依賴闹究,僅需要在被依賴Component中增加 XXXComponent addXXComponent(XXXModule) 方法

這個如果不太理解也沒有關系幔崖,會使用就行。


懶加載Lazy和強制重新加載Provider

這個比較簡單渣淤,我就列舉一個簡單的例子岖瑰。

public class Container{
    @Inject Lazy<User> lazyUser; //注入Lazy元素
    @Inject Provider<User> providerUser; //注入Provider元素
    public void init(){
        DaggerComponent.create().inject(this);
        User user1=lazyUser.get();  
//在這時才創(chuàng)建user1,以后每次調用get會得到同一個user1對象

        User user2=providerUser.get(); 
//在這時創(chuàng)建user2,以后每次調用get會再強制調用Module的Provides方法一次砂代,
//根據Provides方法具體實現的不同蹋订,可能返回跟user2是同一個對象,也可能不是刻伊。
    }
}

注意事項(重要)分析

  1. componet 的 inject 方法接收父類型參數露戒,而調用時傳入的是子類型對象則無法注入
  2. component關聯的modules中不能有重復的provide
  3. module 的 provide 方法使用了 scope ,那么 component 就必須使用同一個注解
  4. module 的 provide 方法沒有使用 scope 捶箱,那么 component 和 module 是否加注解都無關緊要智什,可以通過編譯
  5. component的dependencies與component自身的scope不能相同,即組件之間的scope不同
  6. Singleton的組件不能依賴其他scope的組件丁屎,只能其他scope的組件依賴Singleton的組件
  7. 沒有scope的component不能依賴有scope的component
  8. 一個component不能同時有多個scope(Subcomponent除外)
  9. @Singleton 的生命周期依附于component荠锭,同一個module provide singleton ,不同component 也是不一樣
  10. Component注入的Activity 在其他Component中不能再去注入
  11. dagger2是跟著生命周期的綁定Activity(Fragment)onDestory 對象也會銷毀
  12. 創(chuàng)建實例的方法和引用實例都不能用private修飾
剛開始使用一定總會遇到很多錯誤,遇到錯誤不要著急晨川。如果注意事項中的錯誤沒有犯的話一定會減少很多錯誤证九。

結語

終于寫完了。本來不我自己就不喜歡長文章共虑。不知不覺寫的有點多愧怜。對我這種小白,看源碼寫博客妈拌。真的很費心拥坛,學過的技術忘的很快,很多東西理解不透徹,想把博客寫好寫深還是很有難度的猜惋。不過如果你看到了這篇文章丸氛,希望有錯誤很問題請留言一起探討。Dagger2也是在我用MVP構建項目時候使用的著摔,可能學習的不是很深入缓窜。不過相信把這兩篇文章的代碼敲一邊。平常的使用一定沒有問題的梨撞。最后希望大家一起加油1⑾础O愎蕖卧波!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市庇茫,隨后出現的幾起案子港粱,更是在濱河造成了極大的恐慌,老刑警劉巖旦签,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件查坪,死亡現場離奇詭異,居然都是意外死亡宁炫,警方通過查閱死者的電腦和手機偿曙,發(fā)現死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來羔巢,“玉大人望忆,你說我怎么就攤上這事「透眩” “怎么了启摄?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長幽钢。 經常有香客問我歉备,道長,這世上最難降的妖魔是什么匪燕? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任蕾羊,我火速辦了婚禮,結果婚禮上帽驯,老公的妹妹穿的比我還像新娘肚豺。我一直安慰自己,他們只是感情好界拦,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布吸申。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪截碴。 梳的紋絲不亂的頭發(fā)上梳侨,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音日丹,去河邊找鬼走哺。 笑死,一個胖子當著我的面吹牛哲虾,可吹牛的內容都是我干的丙躏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼束凑,長吁一口氣:“原來是場噩夢啊……” “哼晒旅!你這毒婦竟也來了?” 一聲冷哼從身側響起汪诉,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤废恋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后扒寄,有當地人在樹林里發(fā)現了一具尸體鱼鼓,經...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年该编,在試婚紗的時候發(fā)現自己被綠了迄本。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡课竣,死狀恐怖嘉赎,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情稠氮,我是刑警寧澤曹阔,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站隔披,受9級特大地震影響赃份,放射性物質發(fā)生泄漏。R本人自食惡果不足惜奢米,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一抓韩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鬓长,春花似錦谒拴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炭序。三九已至,卻和暖如春苍日,著一層夾襖步出監(jiān)牢的瞬間惭聂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工相恃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辜纲,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓拦耐,卻偏偏與公主長得像耕腾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子杀糯,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容