ARouter 1.5.0

ARouter

幫助Android App進(jìn)行組件化改造的框架亩钟,支持模塊間路由驹针、通信逞刷、解耦

支持功能:

  • 支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn)纠俭,并自動(dòng)注入?yún)?shù)到目標(biāo)頁面中
  • 支持多模塊工程使用
  • 支持添加多個(gè)攔截器冰木,自定義攔截順序
  • 支持依賴注入穷劈,可單獨(dú)作為依賴注入框架使用
  • 支持InstantRun
  • 支持MultiDex(Google方案)
  • 映射關(guān)系按組分類、多級(jí)管理踊沸,按需初始化
  • 用戶指定全局降級(jí)與局部降級(jí)策略
  • 頁面歇终、攔截器、服務(wù)等組件均自動(dòng)注冊(cè)到框架
  • 多種方式配置跳轉(zhuǎn)動(dòng)畫
  • 獲取Fragment
  • 完全支持Kotlin以及混編
  • 第三方App加固(使用arouter-register實(shí)現(xiàn)自動(dòng)注冊(cè))
  • 生成路由文檔
  • IDE插件便捷關(guān)聯(lián)路徑和目標(biāo)類

應(yīng)用

  • 從外部URL映射到內(nèi)部頁面逼龟,參數(shù)傳遞和解析
  • 跨模塊頁面跳轉(zhuǎn)评凝,模塊解耦
  • 攔截跳轉(zhuǎn)過程,處理登陸腺律、埋點(diǎn)
  • 跨模塊API調(diào)用奕短,控制反轉(zhuǎn)來組件解耦

依賴(在app、子module等需要路由跳轉(zhuǎn)的模塊添加):

plugins {
    // module如下疾渣,如果是app(id 'com.android.application')
    id 'com.android.library'
   // kotlin 擴(kuò)展
    id 'kotlin-android'
    id 'kotlin-android-extensions'
    id 'kotlin-kapt'
}

// kotlin模塊(和javamodule模塊不一樣注意)
kapt {
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}

dependencies{
implementation 'com.alibaba:arouter-api:1.5.0'
kapt 'com.alibaba:arouter-compiler:1.2.2'
}

初始化:在Application中進(jìn)行初始化

class BaseApplication :Application(){
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG){
            ARouter.openDebug()
            ARouter.openLog()
        }
        ARouter.init(this)
    }
}

一個(gè)包含多個(gè)module項(xiàng)目篡诽,名為user的module中存在一個(gè)UserHomeActivity,路由路徑:/account/userHome榴捡。從其他module跳轉(zhuǎn)該頁面杈女,指定path來跳轉(zhuǎn)。

// 主app里的
@Route(path = "/account/userHome")
class UserHomeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_home)
    }
}

// module里的
@Route(path = "/library1/test")
class Library1Activity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_library1)

        tvTest.setOnClickListener{
            // 跨模塊跳轉(zhuǎn)
            ARouter.getInstance().build("/account/userHome").navigation()
        }
    }
}

編譯階段生成輔助代碼來實(shí)現(xiàn)path跳轉(zhuǎn)吊圾。需要拿到Activity的Class對(duì)象才行达椰。編譯階段,ARouter根據(jù)我們?cè)O(shè)定的路由跳轉(zhuǎn)規(guī)則來自動(dòng)生成映射文件项乒,包含path和ActivityClass之間的對(duì)應(yīng)關(guān)系啰劲。
UserHomeActivity編譯階段自動(dòng)生成輔助java文件ARouter$$Group$$account類中就將path和ActivityClass作為鍵值對(duì)保存到了Map中,ARouter依靠此進(jìn)行跳轉(zhuǎn)檀何。

public class ARouter$$Group$$account implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
  }
}

這類自動(dòng)生成的文件包名路徑都是com.alibaba.android.arouter.routes蝇裤,類名前綴特定規(guī)則。雖然ARouter$$Group$$account類實(shí)現(xiàn)了對(duì)應(yīng)關(guān)系保存到Map中频鉴,loadInto方法還是需要有ARouter運(yùn)行時(shí)來調(diào)用栓辜,ARouter就需要拿到ARouter$$Group$$account這個(gè)類才行,ARouter通過掃描com.alibaba.android.arouter.routes這個(gè)包名路徑來獲取所有輔助文件垛孔。

1藕甩、程序員自己維護(hù)特定path和特定目標(biāo)類之間對(duì)應(yīng)關(guān)系,ARouter只要求開發(fā)者使用包含path的@Route 注解修飾目標(biāo)類
2周荐、ARouter在編譯階段通過注解處理器自動(dòng)生成path和特定的目標(biāo)類之間的對(duì)應(yīng)關(guān)系狭莱,path作為key僵娃,將目標(biāo)類Class對(duì)象為value之一存到Map中
3、在運(yùn)行階段腋妙,通過path來發(fā)起請(qǐng)求默怨,ARouter根據(jù)path從map中取值,拿到目標(biāo)類

ARouter類使用單例模式辉阶,暴露外部調(diào)用的API先壕。實(shí)現(xiàn)邏輯交給_ARouter來完成。
_ARouter類包私有權(quán)限谆甜,使用了單例垃僚,通過init(Application)-->LogisticsCenter.init(mContext, executor)

LogisticsCenter實(shí)現(xiàn)掃描特定包名路徑拿到所有自動(dòng)生成的輔助文件的邏輯。在進(jìn)行初始化的時(shí)候规辱,加載到當(dāng)前項(xiàng)目一共包含所有g(shù)roup谆棺,以及每個(gè)group對(duì)應(yīng)的路由信息表。
1罕袋、如果開啟debug模式或通過本地SP緩存判斷出app的版本前后發(fā)生變化改淑,需要重新獲取路由信息,否則從使用之前緩存到SP中的數(shù)據(jù)浴讯。
2朵夏、獲取全局路由信息是耗時(shí)操作,所以ARouter通過將全局路由信息緩存到SP中來實(shí)現(xiàn)榆纽。開發(fā)階段可能隨時(shí)添加路由表仰猖,每次發(fā)布新的版本正常都會(huì)加大應(yīng)用版本號(hào),所以ARouter只開啟了debug模式或者版本號(hào)發(fā)生變化的時(shí)候才會(huì)重新獲取路由信息
3奈籽、獲取路由信息包含了com.alibaba.android.arouter.routes包下自動(dòng)生成的輔助文件的全路徑饥侵,判斷路徑名前綴字符串,知道該類什么類型文件衣屏,通過反射構(gòu)建不同類型對(duì)象躏升,調(diào)用對(duì)象的方法將路由信息存到Warehouse的Map中。

UserHomeActivity狼忱,其path為/account/userHome,ARouter默認(rèn)會(huì)將path的第一個(gè)單詞account作為group膨疏,而UserHomeActivity放到名為app(module名字)的module中

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("account", ARouter$$Group$$account.class);
  }
}

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
  }
}

LogisticsCenter的init文件名前綴ARouter$$Root$$定位到ARouter$$Root$$app這個(gè)類,然后通過反射構(gòu)建出該對(duì)象钻弄,通過調(diào)用loadInto的頁面時(shí)成肘,反射調(diào)用ARouter$$Group$$account的loadInto方法,按需加載斧蜕,等到需要的時(shí)候再獲取詳細(xì)的路由對(duì)應(yīng)信息。

    ARouter.getInstance().build(RoutePath.USER_HOME).navigation()

build()通過ARouter中轉(zhuǎn)調(diào)用_ARouter的build()返回Postcard對(duì)象砚偶,用于傳入跳轉(zhuǎn)配置參數(shù)批销,例如:mBundle洒闸、開啟綠色通道greenChannel、跳轉(zhuǎn)動(dòng)畫optionsCompat等

Postcard的navigation()方法會(huì)調(diào)用_ARouter完成Activity跳轉(zhuǎn)均芽。
navigation方法的重點(diǎn)在于LogisticsCenter.completion(postcard)丘逸。按需加載反射調(diào)用ARouterGroupaccount 的 loadInto 方法。completion方法就獲取詳細(xì)路由信息掀宋。通過postcard攜帶的path和group信息從Warehouse取值深纲,如果不是null信息保存到postcard中,為null則拋出NoRouteFoundException

跳轉(zhuǎn)到Activity并注入?yún)?shù)

            // 帶參數(shù)跳轉(zhuǎn)
            ARouter.getInstance()
                .build("/account/userHome")
                .withLong("userId",13)
                .withString("userName","小明")
                .navigation()

// 參數(shù)接收
@Route(path = "/account/userHome")
class UserHomeActivity : AppCompatActivity() {
    private val TAG = "UserHome-"
    @Autowired(name = "userId")
    @JvmField
    var userId:Long = 0

    @Autowired(name = "userName")
    @JvmField
    var userName = ""
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_home)
        // 需要注冊(cè)劲妙,否則無法接收到參數(shù)傳遞
        ARouter.getInstance().inject(this)
        Log.d(TAG, "onCreate: userId:$userId userName:$userName")
    }
}

通過@Autowired注解修飾變量湃鹊。聲明name參數(shù),傳遞鍵值對(duì)的key對(duì)應(yīng)镣奋。通過ARouter.getInstance().inject(this)后币呵,完成參數(shù)賦值。
生產(chǎn)輔助代碼:

public class UserHomeActivity$$ARouter$$Autowired implements ISyringe {
  private SerializationService serializationService;

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    UserHomeActivity substitute = (UserHomeActivity)target;
    substitute.userId = substitute.getIntent().getLongExtra("userId", substitute.userId);
    substitute.userName = substitute.getIntent().getStringExtra("userName");
  }
}

攜帶的參數(shù)放到Intent侨颈,inject方法實(shí)現(xiàn)了從Intent取值向變量賦值余赢,要求變量是public的,kotlin需要同時(shí)向變量加上@JvmField注解的原因哈垢。參數(shù)注解:ARouter.getInstance().inject(this)
ARouter通過控制反轉(zhuǎn)拿到AutowiredService對(duì)應(yīng)實(shí)現(xiàn)類AutowiredServiceImpl的對(duì)象妻柒,調(diào)用autowire方法完成參數(shù)注入。
由于參數(shù)注入輔助類的類名具有固定的包名和類名耘分,目標(biāo)類類名+$$ARouter$$Autowired, 所以AutowiredServiceImpl傳入的instance參數(shù)和反射過來生成的對(duì)象举塔,最終調(diào)用inject方法完成參數(shù)注入。

控制反轉(zhuǎn)

跳轉(zhuǎn)Activity并自動(dòng)注入?yún)?shù)屬于依賴注入的一種陶贼,ARouter同時(shí)也支持控制反轉(zhuǎn):通過接口獲取其實(shí)現(xiàn)類實(shí)例
存在一個(gè)ISayHelloService接口啤贩,需要拿到其實(shí)現(xiàn)類實(shí)例,但是不希望使用的時(shí)候和特定實(shí)現(xiàn)類SayHelloService綁定在一起造成耦合拜秧,使用ARouter控制反轉(zhuǎn)功能痹屹,要求ISayHelloService接口繼承了IProvider接口才行
注意:Route(path = "/account/sayhello")在實(shí)現(xiàn)的接口和調(diào)用的類path要一致,才能實(shí)現(xiàn)反轉(zhuǎn)控制枉氮。

// 接口
interface ISayHelloService :IProvider {
    fun sayHello()
}

// 具體實(shí)現(xiàn)
@Route(path = "/account/sayhello")
class SayHelloService :ISayHelloService{
    private val TAG = "SayHello-"
    override fun sayHello() {
        Log.d(TAG, "sayHello: ")
    }

    override fun init(context: Context?) {
        Log.d(TAG, "init: ")
    }
}

// 具體使用
@Route(path = "/account/sayhello")
class MainActivity : AppCompatActivity() {
    private val TAG = "Main-網(wǎng)絡(luò)測(cè)試"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        textNet.setOnClickListener {
        ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()
        }
    }
}

在使用的時(shí)候直接傳遞ISayHelloService的Class對(duì)象即可志衍,ARouter會(huì)將SayHelloService以單例形式返回,無需開發(fā)者手動(dòng)構(gòu)建SayHelloService對(duì)象聊替,從而達(dá)到解耦楼肪。

ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()

和實(shí)現(xiàn)Activity跳轉(zhuǎn)一樣,ARouter自動(dòng)生成幾個(gè)文件惹悄,包含路由表映射關(guān)系春叫。

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/account/sayhello", RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhello", "account", null, -1, -2147483648));
  }
}

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Providers$$app implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("com.george.learnretrofit.test.ISayHelloService", RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhello", "account", null, -1, -2147483648));
  }
}

LogisticsCenter實(shí)現(xiàn)掃描特定包名路徑拿到所有自動(dòng)生成的輔助文件邏輯。最終Warehouse中就會(huì)在初始化的時(shí)候拿到數(shù)據(jù)
Warehouse.groupsIndex:

  • account--->class com.alibaba.android.arouter.routes.ARouter$$Group$$account
    Warehouse.providersIndex:
    com.george.learnretrofit.test.ISayHelloService--->RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhello", "account", null, -1, -2147483648)

LogisticsCenter.completion(postcard)獲取對(duì)象實(shí)例時(shí)候同時(shí)將實(shí)例緩存起來,以后復(fù)用暂殖。

攔截器

可以通過攔截器來判斷用戶是否處于登陸狀態(tài)价匠,還未登陸的話,攔截請(qǐng)求呛每,打開登陸界面踩窖。
可以添加多個(gè)攔截器,每個(gè)攔截器設(shè)置不同優(yōu)先級(jí)

@Interceptor(priority = 100,name="啥也不做的攔截器")
class NothingInterceptor :IInterceptor{
    private val TAG = "Nothing-1-"
    override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
        // 不攔截晨横,任其跳轉(zhuǎn)
        Log.d(TAG, "process: ")
        callback?.onContinue(postcard)
    }

    override fun init(context: Context?) {

        Log.d(TAG, "init: ")
    }
}

@Interceptor(priority = 200, name = "登陸攔截器")
class LoginInterceptor: IInterceptor{
    private val TAG = "Nothing-2-"
    override fun process(postcard: Postcard?, callback: InterceptorCallback?) {

        if (postcard?.path == "/account/userHome"){
            // 攔截
            Log.d(TAG, "process: 攔截-到主頁")
            callback?.onInterrupt(null)
            ARouter.getInstance().build("/account/sayhello").navigation()
        }else{
            Log.d(TAG, "process: 不攔截")
            // 不攔截洋腮,任其跳轉(zhuǎn)
            callback?.onContinue(postcard)
        }
    }

    override fun init(context: Context?) {
        Log.d(TAG, "init: ")
    }

}

priority越小優(yōu)先級(jí)越高(越先執(zhí)行)。
_ARouter的navigation方法手形,轉(zhuǎn)交給interceptorService啥供,判斷有沒有開啟綠色通道模式。InterceptorServiceImpl實(shí)現(xiàn)類叁幢,ARouter初始化過程中通過控制反轉(zhuǎn)拿到interceptorService實(shí)例滤灯。
1、第一次獲取InterceptorServiceImpl實(shí)例時(shí)候曼玩,其init方法會(huì)馬上被調(diào)用鳞骤,交由線程池來處理。通過反射生成每個(gè)攔截器對(duì)象黍判,并調(diào)用每個(gè)攔截器的init方法完成攔截器初始化豫尽,將每個(gè)攔截器對(duì)象都存到Warehouse.interceptors中。如果初始化完成顷帖,則喚醒等待的interceptorInitLock上的線程美旧。
2、doInterceptions方法被調(diào)用贬墩,如果第一個(gè)步驟未執(zhí)行完榴嗅,則通過checkInterceptorsInitStatus()方法等待第一次步驟完成。如果10s內(nèi)都未完成陶舞,則走失敗流程直接返回嗽测。
3、在線程池中遍歷攔截器列表肿孵,如果某個(gè)攔截器攔截請(qǐng)求則調(diào)用callback.onInterrupt方法通知內(nèi)部唠粥,否則調(diào)用callback.onContinue()方法繼續(xù)跳轉(zhuǎn)邏輯。

注解處理器

依靠注解器生成輔助文件停做,ARouter才能完成參數(shù)自動(dòng)注入功能晤愧。
APT(Annotation Processing Tool)注解處理器,用在編譯期掃描和處理注解蛉腌,通過注解生成Java文件官份。即注解作為橋梁只厘,預(yù)先設(shè)定好的代碼生成規(guī)則,自動(dòng)生成Java文件舅巷。例如:ButterKnife/Dragger2/EventBus等

Java API已經(jīng)提供了掃描源碼并解析注解的框架懈凹,通過繼承AbstractProcessor類來實(shí)現(xiàn)自己的注解解析邏輯。APT的原理就是在注解了某些代碼元素(如字段悄谐、函數(shù)、類等)后库北。在編譯時(shí)編輯器會(huì)檢查AbstractProcess的子類爬舰,并且自動(dòng)調(diào)用process()方法,然后將指定注解的所有代碼元素作為參數(shù)傳遞給該方法寒瓦,開發(fā)者根據(jù)注解元素在編譯期輸出Java代碼情屹。

ARouter源碼中和注解處理器相關(guān)的module

  • router-annotation。Java Module杂腰,包含了Autowired垃你、Interceptor這些注解及RouteMeta等JavaBean
  • arouter-compiler。Android Module喂很,包含了AbstractProcessor的實(shí)現(xiàn)類用于生產(chǎn)代碼
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惜颇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子少辣,更是在濱河造成了極大的恐慌凌摄,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漓帅,死亡現(xiàn)場離奇詭異锨亏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)忙干,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門器予,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捐迫,你說我怎么就攤上這事乾翔。” “怎么了弓乙?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵末融,是天一觀的道長。 經(jīng)常有香客問我暇韧,道長勾习,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任懈玻,我火速辦了婚禮巧婶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己艺栈,他們只是感情好英岭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著湿右,像睡著了一般诅妹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上毅人,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天吭狡,我揣著相機(jī)與錄音,去河邊找鬼丈莺。 笑死划煮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缔俄。 我是一名探鬼主播弛秋,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼俐载!你這毒婦竟也來了蟹略?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤瞎疼,失蹤者是張志新(化名)和其女友劉穎科乎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贼急,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茅茂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了太抓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片空闲。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖走敌,靈堂內(nèi)的尸體忽然破棺而出碴倾,到底是詐尸還是另有隱情,我是刑警寧澤掉丽,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布跌榔,位于F島的核電站,受9級(jí)特大地震影響捶障,放射性物質(zhì)發(fā)生泄漏僧须。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一项炼、第九天 我趴在偏房一處隱蔽的房頂上張望担平。 院中可真熱鬧示绊,春花似錦、人聲如沸暂论。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽取胎。三九已至展哭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闻蛀,已是汗流浹背摄杂。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留循榆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓墨坚,卻偏偏與公主長得像秧饮,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泽篮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354