十沿猜、EventBus 源碼隨想

EventBus 源碼隨想

首先網(wǎng)上已經(jīng)有不少優(yōu)秀的EventBus的源碼分析文章啼肩,這篇只是為了記錄自己的理解祈坠,畢竟自己親自寫出來才能理解的更深赦拘,所以如有不對的地方躺同,還望諒解丸逸。

參考
http://www.reibang.com/p/f057c460c77e
http://p.codekk.com/blogs/detail/54cfab086c4761e5001b2538
https://kymjs.com/code/2015/12/16/01/

0. 幾個問題

EventBus 的使用過程無非就是 注冊车海、post侍芝、響應(yīng)事件函數(shù)埋同。
那么我們得弄清楚這幾個問題

1咧栗、怎樣進(jìn)行注冊的 致板?
2咏窿、post 的事件是 什么集嵌,即 post 里的參數(shù)到底代表著什么怜珍?
3凤粗、post 時嫌拣,訂閱者是怎樣收到響應(yīng)的亭罪,是怎么通知到所有與事件相關(guān)的訂閱者的?

1. 注冊

// 把當(dāng)前類注冊為訂閱者(Subscriber)
EventBus.getDefault().register(this);

// 解除當(dāng)前類的注冊
EventBus.getDefault().unregister(this);

代碼很簡單情组,就一行院崇,那么我們來看看到底是怎樣注冊的底瓣。在注冊的時候究竟做了什么捐凭。在整個 EventBus 的使用過程中茁肠,除了注冊大部分就是 post 和 事件響應(yīng)函數(shù)了垦梆,所以我們猜測在注冊的時候托猩,應(yīng)該會把事件和訂閱者綁定起來京腥。那么我們帶著這個疑惑去看這段代碼绞旅。

1.1 獲取 EventBus 對象

首先 Event.getDefault() 看到這個應(yīng)該能想到是個 單例模式因悲。

    static volatile EventBus defaultInstance;

    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

果不其然讯检,一個雙重校驗鎖的單例模式人灼。

    private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

    public EventBus() {
        this(DEFAULT_BUILDER);
    }

    EventBus(EventBusBuilder builder) {
        // 省略
    }

可以看到上面構(gòu)造函數(shù)傳入了一個 builder投放,很明顯 建造者模式。 至此拜姿,我們可以獲取到了一個單例的 EventBus 的對象了蕊肥,另外關(guān)于 builder 的更詳細(xì)的內(nèi)容可自己看源碼。

1.2 register

獲取到對象后裸准,就調(diào)用 register 方法了狼速,里面?zhèn)魅肓?this 參數(shù)。

    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

我們可以看到 register 方法的參數(shù)名是 subscriber惊完,說明我們傳進(jìn)去的 this 就是訂閱者對象处硬。此時我們清楚了 當(dāng)前類就是訂閱者荷辕。
這個方法里面做了什么呢疮方,我們來看一看骡显。首先獲取到傳進(jìn)去的 this (即當(dāng)前類)的 Class 對象疆栏,然后調(diào)用 subscriberMethodFinder.findSubscriberMethods(subscriberClass); 我們根據(jù)名字可以猜想,這應(yīng)該是去找到當(dāng)前訂閱者的所有 事件響應(yīng)函數(shù)(即帶有 @Subscribe 注解的方法)惫谤。 找到所有事件響應(yīng)函數(shù)后壁顶,就調(diào)用 subscribe, 那這里我們就可以猜想是把 當(dāng)前類對象 與這些 事件響應(yīng)函數(shù) 關(guān)聯(lián)起來溜歪。下面驗證我們的猜想若专。

1.2.1 findSubscriberMethods

首先我們得找到當(dāng)前類的所有 合法的事件響應(yīng)函數(shù)

    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }

這里面我們直接找到 findUsingReflection 方法, 根據(jù)方法名也知道這是什么意思了蝴猪。 繼續(xù)跟進(jìn)

    private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findUsingReflectionInSingleClass(findState);
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

這里的 FindState 用于做 事件響應(yīng)函數(shù) 的校驗和保存。繼續(xù)跟進(jìn) findUsingReflectionInSingleClass

    private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                    String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                    throw new EventBusException("@Subscribe method " + methodName +
                            "must have exactly 1 parameter but has " + parameterTypes.length);
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }

這里如果知道反射的應(yīng)該都看得懂,沒什么復(fù)雜的地方爬早。 從這里的實(shí)現(xiàn)我們可以知道饶米,這個 eventType 其實(shí)是第一個參數(shù)類型, 并且保證只能有一個參數(shù)。 這樣我們的事件其實(shí)是以 事件響應(yīng)函數(shù)的 參數(shù)類型 為基準(zhǔn)的,可以看到如果參數(shù)的數(shù)量不為 1 软吐,會拋出異常使兔。

最后再通過 findUsingReflection 方法的 getMethodsAndRelease 返回一個 List<SubscriberMethod>
至此泽艘, register 方法的 List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); 這一行代碼就走完了然低,我們拿到了該訂閱類的所有 事件響應(yīng)函數(shù) 枫笛, 即 SubscriberMethod 的集合无畔。

1.2.2 subscribe

接著繼續(xù) register 往下走,可以看到會循環(huán) List<SubscriberMethod> 這個集合薄风,取出每個 SubscriberMethod(事件響應(yīng)函數(shù))撇他,然后調(diào)用 subscribe,我們看看這個方法到底做了什么。
這里先給出結(jié)果,這個方法就是我們注冊的關(guān)鍵了,它完成了我們每個 事件響應(yīng)函數(shù)的注冊過程榛臼。

首先這個方法會涉及兩個變量路呜,我一開始特別懵抵屿,完全不知道這兩個變量啥意思,不過圖一畫出來就立馬清晰了尿扯。
這兩個變量是 subscriptionsByEventType 和 typesBySubscriber 矩屁”僮冢基本上理解這兩個變量是什么意思出牧,整個注冊流程就通了。

首先我們定義兩個類和方法士袄,如下所示,這個得認(rèn)真理清楚这敬。

public class A {
    @Subscribe
    public void testA1(Event1 event1) {
        //do something
    }

    @Subscribe
    public void testA2(Event2 event2) {
        //do something
    }
}

public class B {
    @Subscribe
    public void testB1(Event1 event1) {
        //do something
    }

    @Subscribe
    public void testB2(Event2 event2) {
        //do something
    }
}

可以看到這兩個類分別有著兩個事件響應(yīng)函數(shù),事件類型有兩種孕锄,Event1 和 Event2
subscriptionsByEventType 這個變量是 Map<Class<?>, CopyOnWriteArrayList<Subscription>> 類型抡砂,它的圖示如下:

subscriptionsByEventType.png

typesBySubscriber 這個變量是 Map<Object, List<Class<?>>> 類都,它的圖示如下:

typesBySubscriber.png

至于其中的 SubscriberMethod 對象绞呈,在 findUsingReflectionInSingleClass 方法中可以看到是怎樣構(gòu)造出來的志鹃。這個對象也是非常重要的秘血,里面封裝了 方法信息 method、線程模式 threadMode评甜、參數(shù)類型 eventType、優(yōu)先級 priority韧骗、sticky 布爾值 等信息蚂会。

相信這兩個圖可以幫助你很好的理解接下來的 subscribe 流程酵镜。關(guān)于 subscribe 的源碼靠粪,以及流程我就不分析了,自己看源碼對著上面兩個圖絕對能理解毫蚓。 不能理解的話庇配,還有 參考 內(nèi)的文章,這里一定得自己去弄懂绍些。當(dāng)然并不復(fù)雜,所以耐心點(diǎn)耀鸦。

當(dāng) subscribe 走完后柬批,我們的 EventBus 擁有了什么?
它擁有了 事件類型(例 Event1) 所對應(yīng)的 Subscription 信息列表袖订,什么意思氮帐,就是這個 事件 在 哪些類的哪些方法存在,有了這些信息洛姑,EventBus 就能很方便的將一個 事件 傳遞給所有 訂閱了這個事件的 方法了上沐。

那么 typesBySubscriber 有什么用呢,在 unregister 時楞艾,它可以發(fā)揮它的作用参咙。那么趁熱打鐵龄广,看看 unregister 是如何工作的。噢蕴侧,對了择同,放上注冊的流程圖,是取自 codekk 的流程圖净宵,相信再看這個圖敲才,你會非常清晰。

register-flow-chart.png

2 unregister 解除注冊

public synchronized void unregister(Object subscriber) {
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

第一行就用到了剛剛的 typesBySubscriber择葡, 這里將當(dāng)前訂閱者的 事件類型列表 取出來紧武,也就是拿到了當(dāng)前類的所有 事件。
然后判斷 事件列表 是否為空敏储,不為空則 循環(huán)事件列表阻星,依次調(diào)用 unsubscribeByEventType(subscriber, eventType); 最后調(diào)用 typesBySubscriber.remove(subscriber); 這個是把 當(dāng)前訂閱者 從 typesBySubscriber 中刪掉,這樣就完成了解除綁定的操作虹曙。當(dāng)然最重要的是 unsubscribeByEventType 這個方法迫横。

2.1 unsubscribeByEventType
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions != null) {
            int size = subscriptions.size();
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

這個根據(jù)上面的 subscriptionsByEventType 圖示,應(yīng)該不難理解酝碳。
首先從 subscriptionsByEventType 中拿到當(dāng)前 事件(eventType)的 Subscription 列表矾踱。 然后循環(huán)這個列表,從中找到傳進(jìn)來的 訂閱者(subscriber 即 當(dāng)前解除注冊的那個 類疏哗,找到后呛讲,從 Subscription 列表 中刪除即可。

3. post 發(fā)布事件

前面注冊和解除注冊兩個流程都走通了返奉,那么只剩下 post 了贝搁。關(guān)于 post 的流程,參考內(nèi)的文章都講的很好芽偏,礙于篇幅雷逆,就不重復(fù)闡述了,這里說說我剛看時污尉,不懂的幾個地方膀哲。

1、 postSingleEvent 方法會先去得到該事件類型的所有父類及接口類型被碗,然后循環(huán) 調(diào)用 postSingleEventForEventType 函數(shù)發(fā)布每個事件到每個訂閱者某宪? 這里說了這么多是什么意思。
2锐朴、 ThreadMode 對應(yīng)的四種狀態(tài)的 Poster兴喂。

針對第一個問題很好解釋,我們基于上面的 Event1 事件解釋,假設(shè)有一個 訂閱者訂閱了 Object 類型的事件衣迷,而 Event1 的父類是 Object畏鼓,那么我們 post Event1事件 時, 訂閱了 Event1 事件的訂閱者訂閱了 Object 事件的訂閱者 是不是都得接收到這個事件蘑险。所以我們需要處理父類及其接口類型的 post滴肿。

第二個問題,看了 post 的源碼應(yīng)該知道最后會進(jìn)入到 postToSubscription 這個方法佃迄,判斷 subscription.subscriberMethod.threadMode 然后調(diào)用 invokeSubscriber 完成 post流程泼差。
那么這個 threadMode 有四種狀態(tài),

3.1 POSTING

首先看下這個 POSTING 狀態(tài)呵俏,這是 默認(rèn)的 ThreadMode堆缘,表示在執(zhí)行 Post 操作的線程直接調(diào)用訂閱者的事件響應(yīng)方法,不論該線程是否為主線程(UI 線程)普碎,也就是說你是哪個線程就會在哪個線程執(zhí)行吼肥。 代碼中可以看到直接調(diào)用了 invokeSubscriber ,看看 invokeSubscriber 方法的源碼麻车。

void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e);
        }
    }

就是通過反射執(zhí)行了 method 缀皱, 這樣我們也就清楚了,事件函數(shù)得到了響應(yīng)动猬。
注意: 若 Post 線程為主線程啤斗,別忘了不能進(jìn)行耗時操作

3.2 MAIN

            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;

這個狀態(tài)我們可以看到,首先會判斷是否是 主線程赁咙, 如果是直接調(diào)用 invokeSubscriber 钮莲,如果不是會去調(diào)用 mainThreadPoster.enqueue 。 那么這個 mainThreadPoster 是什么彼水?

3.2.1 mainThreadPoster

這個 mainThreadPoster 會在構(gòu)造函數(shù)中進(jìn)行初始化崔拥,自己可以去源碼里看看, 可以知道最后就是 new 一個 HandlerPoster凤覆。
看看 HandlerPoster 的構(gòu)造函數(shù)

protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
        super(looper);
        this.eventBus = eventBus;
        this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
        queue = new PendingPostQueue();
    }

這個 looper 是主線程的 looper链瓦,也就是說最后發(fā)送的消息,會在主線程去處理盯桦。
然后會 new 一個 PendingPostQueue, 這個 PendingPostQueue 是一個 PendingPost 類型的隊列澡绩。
而 PendingPost 則是 訂閱者和事件信息實(shí)體類,并含有同一隊列中指向下一個對象的指針俺附。通過緩存存儲不用的對象,減少下次創(chuàng)建的性能消耗溪掀。
會看的很懵事镣,其實(shí)很好理解, PendingPostQueue就相當(dāng)于 MessageQueue, PendingPost 則相當(dāng)于 Message璃哟。當(dāng)然只是類比氛琢,在調(diào)用 enqueue 的時候,會發(fā)送一個空的 Message随闪,如下代碼:

public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

可以看到我們把 pendingPost 放入到 PendingPostQueue阳似, 然后發(fā)送一個空的 Message, 我們理解 Handler 機(jī)制铐伴,所以去看 handleMessage 吧撮奏。

    @Override
    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            long started = SystemClock.uptimeMillis();
            while (true) {
                PendingPost pendingPost = queue.poll();
                if (pendingPost == null) {
                    synchronized (this) {
                        // Check again, this time in synchronized
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false;
                            return;
                        }
                    }
                }
                eventBus.invokeSubscriber(pendingPost);
                long timeInMethod = SystemClock.uptimeMillis() - started;
                if (timeInMethod >= maxMillisInsideHandleMessage) {
                    if (!sendMessage(obtainMessage())) {
                        throw new EventBusException("Could not send handler message");
                    }
                    rescheduled = true;
                    return;
                }
            }
        } finally {
            handlerActive = rescheduled;
        }
    }

代碼很長,但是你可以很清楚的看到這句代碼 eventBus.invokeSubscriber(pendingPost); 還記得 invokeSubscriber 方法嗎当宴,沒錯就是 通過反射執(zhí)行 method畜吊。 當(dāng)然這里重載了該 方法, 但是最終還是會走到 那個有兩個參數(shù)的 invokeSubscriber 方法户矢。

到這里玲献,我們的 MAIN 狀態(tài)的 post 流程也走完了。

3.3 BACKGROUND 和 ASYNC

這兩個狀態(tài)希望大家自己去看源碼了梯浪,并沒有什么復(fù)雜的捌年,無非就是 backgroundPoster 和 asyncPoster 對線程處理的不同,這兩個 Poster 內(nèi)部同樣有 PendingPostQueue 挂洛。

private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();

這是 AsyncPoster 和 BackgroundPoster 內(nèi)部的線程池 在 EventBusBuilder 中的定義礼预。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市抹锄,隨后出現(xiàn)的幾起案子逆瑞,更是在濱河造成了極大的恐慌,老刑警劉巖伙单,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件获高,死亡現(xiàn)場離奇詭異,居然都是意外死亡吻育,警方通過查閱死者的電腦和手機(jī)念秧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來布疼,“玉大人摊趾,你說我怎么就攤上這事∮瘟剑” “怎么了砾层?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贱案。 經(jīng)常有香客問我肛炮,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任侨糟,我火速辦了婚禮碍扔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秕重。我一直安慰自己不同,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布溶耘。 她就那樣靜靜地躺著二拐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汰具。 梳的紋絲不亂的頭發(fā)上卓鹿,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機(jī)與錄音留荔,去河邊找鬼吟孙。 笑死,一個胖子當(dāng)著我的面吹牛聚蝶,可吹牛的內(nèi)容都是我干的杰妓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼碘勉,長吁一口氣:“原來是場噩夢啊……” “哼巷挥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起验靡,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤倍宾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后胜嗓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體高职,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年辞州,在試婚紗的時候發(fā)現(xiàn)自己被綠了怔锌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡变过,死狀恐怖埃元,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情媚狰,我是刑警寧澤岛杀,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站崭孤,受9級特大地震影響类嗤,放射性物質(zhì)發(fā)生泄漏衫生。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一土浸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彭羹,春花似錦黄伊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至毡惜,卻和暖如春拓轻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背经伙。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工扶叉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人帕膜。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓枣氧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親垮刹。 傳聞我的和親對象是個殘疾皇子达吞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 我每周會寫一篇源代碼分析的文章,以后也可能會有其他主題.如果你喜歡我寫的文章的話,歡迎關(guān)注我的新浪微博@達(dá)達(dá)達(dá)達(dá)s...
    SkyKai閱讀 24,927評論 23 184
  • 對于Android開發(fā)老司機(jī)來說肯定不會陌生,它是一個基于觀察者模式的事件發(fā)布/訂閱框架荒典,開發(fā)者可以通過極少的代碼...
    飛揚(yáng)小米閱讀 1,475評論 0 50
  • 我的理想國 我不清楚是不是每個人心中都有一個理想國酪劫,但是我心中的理想國漸漸的清晰起來,哲學(xué)家心中的理想國是一個真正...
    宛若清風(fēng)R閱讀 819評論 0 0
  • 樂慢瑜伽私教館 每天晚上寺董,在睡前花15分鐘抬抬腳覆糟,堅持二、三個月螃征,就可以改善體質(zhì)搪桂、精力倍增,對于生理不適盯滚、便秘踢械、胃...
    小小井同學(xué)閱讀 677評論 0 1
  • 《新年心語》 香陌野徑凝天地, 火樹銀花化春風(fēng)魄藕。 天高地厚微茫飛内列, 流光溢彩大潮涌。 紅旗招展向久遠(yuǎn)背率, 高歌猛進(jìn)新...
    曦微w行走在路上閱讀 371評論 2 10