ARouter 源碼淺析

簡介

Android平臺中對頁面将硝、服務(wù)提供路由功能的中間件蛉腌,我的目標(biāo)是 —— 簡單且夠用恰响。具體的使用可以參考:https://github.com/alibaba/ARouter拓颓。如果對內(nèi)部的詳細(xì)原理感興趣可以參考:http://www.reibang.com/p/3c4f4e3e621f讲坎。下文分析的代碼來源于ARouter官方Demo借跪,閱讀下文之前建議先下載一份源碼運(yùn)行并對照源碼進(jìn)行比對政己,因為下文中我會用官方demo的源碼截圖。

APT

APT技術(shù)可以讓我們通過自定義注解動態(tài)生成編譯后的class代碼掏愁,具體的使用我在這里就不詳細(xì)說了歇由,感興趣的可以參考我以前寫的:編寫最基本的APT Demo
我這里直接來看下ARouter說涉及到的幾個注解果港,以及編譯之后動態(tài)生成的代碼沦泌。

annotation

image.png

其中Param已被Autowired代替

Route:用于標(biāo)記我們需要跳轉(zhuǎn)的四大組件(可以通過Intent跳轉(zhuǎn)的,因為其實ARouter 內(nèi)部最后也是通過Intent來進(jìn)行跳轉(zhuǎn))辛掠、service(此處的sevice是類似于后臺的服務(wù)谢谦,需要繼承IProvider)。
Interceptor:主要的作用是通過AOP技術(shù)(面向切面編程)在我們進(jìn)行頁面跳轉(zhuǎn)之前可以進(jìn)行一系列的攔截操作
Autowired:主要的作用是通過IOC技術(shù)(依賴注入)獲取頁面跳轉(zhuǎn)的參數(shù)萝衩。

注解解析

image.png

可以看到對應(yīng)了上面的三個注解回挽。這里具體的代碼我就不分析了,感興趣的可以直接去看源碼(雖然不算很難但是比較繁瑣猩谊,一定要耐心)千劈,當(dāng)我們?nèi)志幾g以后會動態(tài)生成以下代碼


image.png

ARouter$$Root$$app:因為Arouter采取的是懶加載技術(shù),所以我們需要對router進(jìn)行分組牌捷,這里的Root內(nèi)部就是通過Map以組名為key存儲了每組router的信息信息墙牌。
ARouter$$Group$$xxx:我們按不同的router類型進(jìn)行分組袁梗,內(nèi)部通過Map以router path存儲了具體router的信息
ARouter$$Interceptors$$app:其中app是我們的module名(通過查看源碼可知),內(nèi)部
以priority優(yōu)先級為key存儲了具體的Interceptors的class信息憔古。
ARouter$$Providers$$app:其中app是我們的module名(通過查看源碼可知)遮怜,內(nèi)部以類的完整限定名為key保存了service的信息,結(jié)構(gòu)同ARouter$$Group$$xxx一致鸿市,只是用于不同的功能锯梁。

關(guān)于ARouter的APT分支就到這了,下面來看下ARouter的初始化焰情。

init

這正式分析初始化之前我們先了解幾個類

ARouter:_ARouter的代理類陌凳,這里采用了代理模式,其實ARouter對外只開放了這一個api内舟,所有的操作基本上都是通過ARouter來完成了合敦。
_ARouter:ARouter所代理得到類,功能的具體執(zhí)行者验游。
LogisticsCenter:物流調(diào)度中心充岛,對路由信息進(jìn)行解析和加工。
Warehouse:倉庫耕蝉,存儲了所有具體的router崔梗、interceptors、service等信息垒在,內(nèi)部是一系列的Map蒜魄。

ARouter的初始化流程:ARouter#init ──》_ARouter#init ──》LogisticsCenter#init ──》Warehouse#Map#put


image.png
          // 獲得指定包名下的所有類名
            List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

            for (String className : classFileNames) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    // This one of root elements, load root. 通過反射找到Arouter$$Root$$xx 加載根路由集合
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    // Load interceptorMeta 通過反射找到Arouter$$Interceptors$$xx 加載攔截器集合
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    // Load providerIndex 通過反射找到Arouter$$Providers$$xx 加載服務(wù)集合 此處的service對應(yīng)后臺的概念 不是四大組件的service
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }

其中第三步和第四步的代碼會根據(jù)指定報名查找下面所有的類信息,然后根據(jù)類得到全限定名進(jìn)行功能分組场躯,并把信息保存在Warehouse的Map中谈为。到此Arouter的初始化過程就完成。

Router的跳轉(zhuǎn)和參數(shù)注入

ARouter.getInstance().build("/test/activity1")
                        .withString("name", "老王")
                        .withInt("age", 18)
                        .withBoolean("boy", true)
                        .withLong("high", 180)
                        .withString("url", "https://a.b.c")
                        .withParcelable("pac", testParcelable)
                        .withObject("obj", testObj)
                        .navigation();

public class Test1Activity extends AppCompatActivity {

    @Autowired
    String name;

    @Autowired
    int age;

    @Autowired(name = "boy")
    boolean girl;

    @Autowired
    TestParcelable pac;

    @Autowired
    TestObj obj;

    private long high;

    @Autowired
    String url;

    @Autowired
    HelloService helloService;

}

其中Test1Activity 通過APT動態(tài)生成的代碼如下:

atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("pac", 9); put("obj", 10); put("name", 8); put("boy", 0); put("age", 3); put("url", 8); }}, -1, -2147483648));

所有的跳轉(zhuǎn)參數(shù)都保存在map中踢关,其中key是一一對應(yīng)伞鲫,而value是參數(shù)類型,對題對照如下:

public enum TypeKind {
    // Base type
    BOOLEAN,
    BYTE,
    SHORT,
    INT,
    LONG,
    CHAR,
    FLOAT,
    DOUBLE,

    // Other type
    STRING,
    PARCELABLE,
    OBJECT;
}

下面我們來看具體的跳轉(zhuǎn)流程耘成。

build

ARouter.getInstance().build("/test/activity1")升溫我們說過ARouter是_ARouter的代理的類榔昔,所有的api最終都會進(jìn)入到真正的執(zhí)行類_ARouter驹闰。
_ARouter#build

protected Postcard build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            //查找是否存在重定向服務(wù)
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return build(path, extractGroup(path));
        }
    }

這里我們先來看下PathReplaceService這個類

public interface PathReplaceService extends IProvider {

    /**
     * For normal path.
     *
     * @param path raw path
     */
    String forString(String path);

    /**
     * For uri type.
     *
     * @param uri raw uri
     */
    Uri forUri(Uri uri);
}

PathReplaceService我稱它為重定向服務(wù)(具體怎么使用請參考官網(wǎng)文檔)瘪菌,它繼承IProvider,那IProvider是什么嘹朗,其實他是用來實現(xiàn)service的师妙,所以PathReplaceService就相當(dāng)于我們自己的自定義服務(wù),唯一的區(qū)別是屹培,自定義服務(wù)需要我們顯示去調(diào)用默穴,跟調(diào)用router一樣怔檩,但是PathReplaceService不需要顯示調(diào)用,他是作用于所有服務(wù)和路由的蓄诽,而且不管你實現(xiàn)了幾個PathReplaceService薛训,最終全局都只會保存在APT時掃描到的最后一個服務(wù)。為什么這么說仑氛,請看下面的代碼:

public class ARouter$$Providers$$app implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    //第一個重定向服務(wù)
    providers.put("com.alibaba.android.arouter.facade.service.PathReplaceService", RouteMeta.build(RouteType.PROVIDER, PathReplaceServiceImpl.class, "/redirect/r1", "redirect", null, -1, -2147483648));
    //第二個重定向服務(wù)
    providers.put("com.alibaba.android.arouter.facade.service.PathReplaceService", RouteMeta.build(RouteType.PROVIDER, PathReplaceServiceImpl2.class, "/redirect/r2", "redirect", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648));
  }
}

因為第一個和第二個重定向服務(wù)的key是一樣都是PathReplaceService的類全限定名乙埃,所以第一個服務(wù)會被覆蓋掉。好了關(guān)于PathReplaceService我們就說到這锯岖,他是我們一個重定向服務(wù)介袜,作用域所有的跳轉(zhuǎn),而且全局只有一個出吹。
我們繼續(xù)往下分析遇伞,如果單例沒有實現(xiàn)自定義重定向服務(wù)的時候,PathReplaceService pService == null捶牢,所以會直接調(diào)用兩個參數(shù)的重載的build方法鸠珠。

protected Postcard build(String path, String group) {
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return new Postcard(path, group);
        }
    }

這里有調(diào)用了一遍ARouter.getInstance().navigation(PathReplaceService.class),感覺沒必要啊秋麸,但是不管肯定還是一樣的返回空跳芳。所以最終ARouter.getInstance().build()會返回一個Postcard(個包含跳轉(zhuǎn)信息的容器,包含跳轉(zhuǎn)參數(shù)和目標(biāo)類的信息)竹勉。下面進(jìn)入真正的跳轉(zhuǎn)飞盆。其實真正的跳轉(zhuǎn)位于_ARouter#navigation。

_ARouter#navigation

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        try {
            //完善跳轉(zhuǎn)信息
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
           ……………………
            return null;
        }

        if (null != callback) {
            callback.onFound(postcard);
        }
        //不是綠色通道所以會進(jìn)入默認(rèn)interceptorService.doInterceptions
        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }

                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 *
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }

InterceptorService是我們在初始化完成以后ARouter為我們自動注冊的攔截器服務(wù)次乓,因為我們并沒有為我們得到路由匹配相應(yīng)的攔截器吓歇,所以應(yīng)該會進(jìn)入onContinue方法,經(jīng)過斷點(diǎn)調(diào)試確實和我們想的一樣票腰,可是onContinue是個回調(diào)函數(shù)城看,它又具體是在哪被調(diào)用的呢?我們經(jīng)過查找發(fā)現(xiàn)是在InterceptorServiceImpl中

InterceptorServiceImpl#doInterceptions

LogisticsCenter.executor.execute(new Runnable() {
                @Override
                public void run() {
                    CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                    try {
                        _excute(0, interceptorCounter, postcard);
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                        if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                            callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                        } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                            callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
                        } else {
                            callback.onContinue(postcard);
                        }
                    } catch (Exception e) {
                        callback.onInterrupt(e);
                    }
                }
            });

這里開了一個線程用來執(zhí)行攔截器或者普通的跳轉(zhuǎn)所以調(diào)用了callback.onContinue杏慰,接下來就進(jìn)入到我們真正的跳轉(zhuǎn)執(zhí)行的地方了测柠。

_ARouter#_navigation

private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = null == context ? mContext : context;

        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // Set flags.
                int flags = postcard.getFlags();
                if (-1 != flags) {
                    intent.setFlags(flags);
                } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // 因為上文我們是在子線程中檢查是否有匹配的攔截器,所以我們要在這里切換到UI線程執(zhí)行具體的跳轉(zhuǎn)
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (requestCode > 0) {  // Need start for result
                            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                        } else {
                            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                        }

                        if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                        }

                        if (null != callback) { // Navigation over.
                            callback.onArrival(postcard);
                        }
                    }
                });

                break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                Class fragmentMeta = postcard.getDestination();
                try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }

                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null;
    }

這里的代碼比較簡單缘滥,就是調(diào)用了Android原生的Intent進(jìn)行跳轉(zhuǎn)轰胁,然后根據(jù)不同的狀態(tài),調(diào)用一些回調(diào)函數(shù)朝扼。到此關(guān)于ARouter的跳轉(zhuǎn)到這里就結(jié)束了赃阀,下面我們來看下目標(biāo)對象的參數(shù)是如何獲取的。

Autowired

這里在分析參數(shù)獲取之前我們先廢話2句擎颖,在看到Autowired注解的時候榛斯,是不是感覺似曾相識观游,沒錯這里的原理跟ButterKnife是一毛一樣的,我強(qiáng)烈懷疑Arouter作者是參考ButterKnife代碼寫的驮俗,所以當(dāng)我們分析完Autowired的時候懂缕,其實就相當(dāng)于把ButterKnife也給分析了,哈哈王凑,正式一舉兩得啊提佣。還有,這種開發(fā)思想其實在后臺開發(fā)中非常普遍荤崇,比如大名鼎鼎的Spring就是這種IOC(控制反轉(zhuǎn))思想的最佳代表拌屏。好了,下面進(jìn)入正題术荤。

Autowired注解處理器

當(dāng)我們在編譯過程中倚喂,系統(tǒng)會是掃描有Autowired注解的成員變量類,然后生成自動生成以下代碼:

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

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);;
    Test1Activity substitute = (Test1Activity)target;
    substitute.name = substitute.getIntent().getStringExtra("name");
    substitute.age = substitute.getIntent().getIntExtra("age", 0);
    substitute.girl = substitute.getIntent().getBooleanExtra("boy", false);
    substitute.pac = substitute.getIntent().getParcelableExtra("pac");
    if (null != serializationService) {
      substitute.obj = serializationService.json2Object(substitute.getIntent().getStringExtra("obj"), TestObj.class);
    } else {
      Log.e("ARouter::", "You want automatic inject the field 'obj' in class 'Test1Activity' , then you should implement 'SerializationService' to support object auto inject!");
    }
    substitute.url = substitute.getIntent().getStringExtra("url");
    substitute.helloService = ARouter.getInstance().navigation(HelloService.class);
  }
}

這里的代碼很簡單瓣戚,應(yīng)該能直接看懂端圈,我們先來看他的父類ISyringe,他其實相當(dāng)于一個模板類子库,為了便于編程ARouter內(nèi)核提供了許多的模板類舱权,存儲在如下路徑中:


image.png

那么為什么要提供模板類呢?簡單了來說仑嗅,當(dāng)我們在變成過程中宴倍,由于框架作者并不知道哪些具體類被標(biāo)注了注解,所以要動態(tài)獲取對象仓技,只能通過反射動態(tài)來獲取實例鸵贬,然后調(diào)用接口的方法來執(zhí)行具體的操作,這就是多態(tài)的概念脖捻,如下代碼所示:


image.png

這里插一句阔逼,反射是會影響性能的,所以一般我們在編程中除非萬不得已地沮,否則盡量不要采用反射嗜浮,但是這里是activity初始化的時候反射,本來就會進(jìn)行大量耗時的操作摩疑,哪怕有一點(diǎn)點(diǎn)的性能損耗也是可以接受的危融。還記得Arouter的初始化嗎?官網(wǎng)上有一句原話是這么說的:
image.png

大家有沒有想過未荒,為什么要盡可能早的初始化专挪,我想除了要掃描大量的對象并保存到全局的map集合中以外,跟初始化的時候用到反射也有關(guān)系吧片排,畢竟還是有性能損耗的寨腔。如下所示


image.png

image.png

總結(jié)

好了,到這我們已經(jīng)把頁面跳轉(zhuǎn)和參數(shù)綁定都分析完了率寡,剩下的重定向迫卢,攔截器,降級等很多其他功能冶共,其實都是在跳轉(zhuǎn)的過程中插入的一些攔截操作而已乾蛤,我相信只要大家只要耐下心來看代碼都是可以看明白的。
請參考ARouter 源碼淺析第二篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捅僵,一起剝皮案震驚了整個濱河市家卖,隨后出現(xiàn)的幾起案子氯庆,更是在濱河造成了極大的恐慌弟孟,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毅待,死亡現(xiàn)場離奇詭異馒闷,居然都是意外死亡酪捡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門纳账,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逛薇,“玉大人,你說我怎么就攤上這事疏虫∮婪#” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵卧秘,是天一觀的道長尤蛮。 經(jīng)常有香客問我,道長斯议,這世上最難降的妖魔是什么产捞? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮哼御,結(jié)果婚禮上坯临,老公的妹妹穿的比我還像新娘。我一直安慰自己恋昼,他們只是感情好看靠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著液肌,像睡著了一般挟炬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天谤祖,我揣著相機(jī)與錄音婿滓,去河邊找鬼。 笑死粥喜,一個胖子當(dāng)著我的面吹牛凸主,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播额湘,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼卿吐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锋华?” 一聲冷哼從身側(cè)響起嗡官,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎毯焕,沒想到半個月后衍腥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芥丧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年紧阔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片续担。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡擅耽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出物遇,到底是詐尸還是另有隱情乖仇,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布询兴,位于F島的核電站乃沙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏诗舰。R本人自食惡果不足惜警儒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望眶根。 院中可真熱鬧蜀铲,春花似錦、人聲如沸属百。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽族扰。三九已至厌丑,卻和暖如春定欧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怒竿。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工砍鸠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人愧口。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓睦番,卻偏偏與公主長得像类茂,于是被迫代替她去往敵國和親耍属。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355