Android EventBus3.0源碼分析

在我們開(kāi)發(fā)過(guò)程中进鸠,相信應(yīng)該有很多人使用過(guò)EventBus 3.0结榄,這個(gè)確實(shí)方便了我們,少些了很多代碼,這是個(gè)優(yōu)秀的庫(kù)郊酒,我們接下來(lái)進(jìn)行對(duì)他剖析。
我們使用EventBus 3.0的過(guò)程:

EventBus.getDefault().register()
EventBus.getDefault().post()
EventBus.getDefault().unregister()

我們先看看是怎么初始化的

 /** Convenience singleton for apps using a process-wide EventBus instance. */
    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

 /**
     * Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a
     * central bus, consider {@link #getDefault()}.
     */
    public EventBus() {
        this(DEFAULT_BUILDER);
    }
  
  EventBus(EventBusBuilder builder) {
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
        logSubscriberExceptions = builder.logSubscriberExceptions;
        logNoSubscriberMessages = builder.logNoSubscriberMessages;
        sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
        sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
        throwSubscriberException = builder.throwSubscriberException;
        eventInheritance = builder.eventInheritance;
        executorService = builder.executorService;
    }

一個(gè)單例模式,接著點(diǎn)你會(huì)發(fā)現(xiàn)其實(shí)是利用的Builder模式档冬,而框架內(nèi)部幫我們寫了一個(gè)EventBusBuilder

private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

 EventBusBuilder() {

  }

EventBusBuilder初始化的時(shí)候除了對(duì)成員變量的一些初始化外瞧柔,其他的并沒(méi)有做什么操作。
接下來(lái)我們進(jìn)入register函數(shù)

/**
     * Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they
     * are no longer interested in receiving events.
     * <p/>
     * Subscribers have event handling methods that must be annotated by {@link Subscribe}.
     * The {@link Subscribe} annotation also allows configuration like {@link
     * ThreadMode} and priority.
     */
    public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        //根據(jù)subscriberClass 獲取訂閱的方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
             //遍歷訂閱
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

從這個(gè)函數(shù)我們可以很清晰的看到邏輯宋彼,首先根據(jù)subscriber(Activity,Fragment)得到相應(yīng)的訂閱方法弄砍,然后在遍歷訂閱。這里代碼就不點(diǎn)進(jìn)去看了输涕,主要說(shuō)下框架的實(shí)現(xiàn)思路音婶。

采用了緩存加反射的方式,主要的參數(shù)和類為下:
METHOD_CACHE:一個(gè)key為訂閱者class莱坎,value為需要訂閱實(shí)現(xiàn)的方法的一個(gè)ConcurrentHashMap衣式,這個(gè)HashMap是為了保證線程并發(fā)安全。我們獲取的訂閱方法就會(huì)緩存到這里面檐什。
FindState:顧名思義碴卧,這是一個(gè)檢測(cè)狀態(tài)的類,里面的參數(shù)為

 final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
        final Map<Class, Object> anyMethodByEventType = new HashMap<>();
        final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
        final StringBuilder methodKeyBuilder = new StringBuilder(128);

        Class<?> subscriberClass;
        Class<?> clazz;
        boolean skipSuperClasses;
        SubscriberInfo subscriberInfo;

里面比較重要的幾個(gè)參數(shù):
SubscriberMethod: 當(dāng)前訂閱對(duì)象(Activity,Fragment)的訂閱方法實(shí)體乃正。

public class SubscriberMethod {
    //方法
    final Method method;
//模式
    final ThreadMode threadMode;
//類型住册, 就是參數(shù)的類型   參數(shù)只能為1個(gè)
    final Class<?> eventType;
    final int priority;
    final boolean sticky;
    /** Used for efficient comparison */
    String methodString;

subscriberMethods:用來(lái)存儲(chǔ)當(dāng)前訂閱對(duì)象(Activity,Fragment)內(nèi)的訂閱方法SubscriberMethod
anyMethodByEventType: key為參數(shù)類型(訂閱類型),value為方法的一個(gè)緩存瓮具。
subscriberClassByMethodKey:key訂閱方法名+">"+參數(shù)類型name,value為訂閱方法的class荧飞。

獲取訂閱方法的時(shí)候先判斷緩存里面是否存在凡人,不存在就獲取FindState實(shí)例,根據(jù)反射獲取注解Subscribe(Activity,Fragment)的方法叹阔,然后對(duì)FindState實(shí)例進(jìn)行操作(anyMethodByEventTypesubscriberClassByMethodKey得到相應(yīng)的值或添加到緩存)划栓,獲取到的訂閱方法集合其實(shí)就是FindState實(shí)例里面的subscriberMethods,最后在把獲取的訂閱方法集合加入緩存。
得到了訂閱方法集合接下來(lái)就遍歷subscriberMethods

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private final Map<Object, List<Class<?>>> typesBySubscriber;
 private final Map<Class<?>, Object> stickyEvents;


 // Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

        int size = subscriptions.size();
        for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }

        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

        if (subscriberMethod.sticky) {
            if (eventInheritance) {
                // Existing sticky events of all subclasses of eventType have to be considered.
                // Note: Iterating over all events may be inefficient with lots of sticky events,
                // thus data structure should be changed to allow a more efficient lookup
                // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        Object stickyEvent = entry.getValue();
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

訂閱的時(shí)候条获,首先根據(jù)訂閱者(Activity Fragment)SubscriberMethod得到一個(gè)Subscription對(duì)象忠荞,這個(gè)類就是一個(gè)真正執(zhí)行信息傳遞的訂閱對(duì)象。

/*
 * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.greenrobot.eventbus;

final class Subscription {
    final Object subscriber;
    final SubscriberMethod subscriberMethod;
    /**
     * Becomes false as soon as {@link EventBus#unregister(Object)} is called, which is checked by queued event delivery
     * {@link EventBus#invokeSubscriber(PendingPost)} to prevent race conditions.
     */
    volatile boolean active;

    Subscription(Object subscriber, SubscriberMethod subscriberMethod) {
        this.subscriber = subscriber;
        this.subscriberMethod = subscriberMethod;
        active = true;
    }

    @Override
    public boolean equals(Object other) {
        if (other instanceof Subscription) {
            Subscription otherSubscription = (Subscription) other;
            return subscriber == otherSubscription.subscriber
                    && subscriberMethod.equals(otherSubscription.subscriberMethod);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return subscriber.hashCode() + subscriberMethod.methodString.hashCode();
    }
}

接著把這個(gè)Subscription對(duì)象添加CopyOnWriteArrayListCopyOnWriteArrayList是一個(gè)適合用在讀多帅掘,寫少并發(fā)應(yīng)用中委煤,它是一個(gè)線程安全的集合)然后將這個(gè)CopyOnWriteArrayList添加到subscriptionsByEventType里面,這個(gè)subscriptionsByEventType是一個(gè)key為訂閱方法的類型(方法函數(shù)的類型)修档,value為一個(gè)存放SubscriptionCopyOnWriteArrayListHashMap碧绞。接下來(lái)把訂閱方法的類型(參數(shù)類型)eventType添加到一個(gè)ArrayList里面,在將這個(gè)ArrayList添加到typesBySubscriber吱窝,typesBySubscriber是一個(gè)key為訂閱者對(duì)象(Fragment讥邻,Activity),valueArrayList<Class<?>>HashMap院峡,最后在判斷是否是sticky,如果是的就將遍歷一個(gè)stickyEventsHashMap兴使,然后根據(jù)key(訂閱方法類型)發(fā)出消息。

我們看看unregister

 /** Unregisters the given subscriber from all event classes. */
    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 {
            Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

 /** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
    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--;
                }
            }
        }
    }

很簡(jiǎn)單照激,先根據(jù)訂閱者(Activity Fragment)獲取到typesBySubscriber里面的訂閱方法類型集合(List<Class<?>> subscribedTypes),然后遍歷這個(gè)集合发魄,根據(jù)這個(gè)訂閱方法類型得到subscriptionsByEventType里面的訂閱對(duì)象集合List<Subscription> subscriptions,在遍歷這個(gè)subscriptions集合,判斷subscription.subscriber == subscriber然后移除俩垃,最后在移除typesBySubscriber里面的這個(gè)訂閱對(duì)象(Fragment ,Activity )励幼。

接下來(lái)我們看看post,最終會(huì)走到這個(gè)函數(shù)

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case BACKGROUND:
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }


/**
     * Invokes the subscriber if the subscriptions is still active. Skipping subscriptions prevents race conditions
     * between {@link #unregister(Object)} and event delivery. Otherwise the event might be delivered after the
     * subscriber unregistered. This is particularly important for main thread delivery and registrations bound to the
     * live cycle of an Activity or Fragment.
     */
    void invokeSubscriber(PendingPost pendingPost) {
        Object event = pendingPost.event;
        Subscription subscription = pendingPost.subscription;
        PendingPost.releasePendingPost(pendingPost);
        if (subscription.active) {
            invokeSubscriber(subscription, event);
        }
    }

    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);
        }
    }

在發(fā)送消息的時(shí)候根據(jù)消息類型(參數(shù)類型)然后從上面的subscriptionsByEventType里面取出相應(yīng)的數(shù)據(jù)進(jìn)行封裝成一個(gè)PendingPost對(duì)象口柳,在根據(jù)反射invoke對(duì)應(yīng)的方法即可苹粟,如果是MAIN的話就通過(guò)Handler進(jìn)行線程轉(zhuǎn)換,如果是BACKGROUND并且是在主線程中調(diào)用或者是ASYNC將會(huì)通過(guò)線程池來(lái)進(jìn)行線程切換跃闹。

接下來(lái)看看postSticky

/**
     * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky
     * event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}.
     */
    public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        post(event);
    }

這個(gè)其實(shí)我們?cè)谧?cè)的時(shí)候已經(jīng)分析了嵌削,當(dāng)調(diào)用這個(gè)函數(shù)的時(shí)候,將會(huì)將這個(gè)消息加到stickyEvents里面辣卒,這個(gè)stickyEvents是一個(gè)ConcurrentHashMap,ConcurrentHashMap是一個(gè)應(yīng)用于高并發(fā)的鍵值對(duì)掷贾,接著調(diào)用post函數(shù)先把已經(jīng)注冊(cè)的觀察者的方法實(shí)現(xiàn)睛榄,接下來(lái)其他對(duì)象注冊(cè)的時(shí)候

 if (subscriberMethod.sticky) {
            if (eventInheritance) {
                // Existing sticky events of all subclasses of eventType have to be considered.
                // Note: Iterating over all events may be inefficient with lots of sticky events,
                // thus data structure should be changed to allow a more efficient lookup
                // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        Object stickyEvent = entry.getValue();
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }

在這里將會(huì)調(diào)用荣茫。
還有一個(gè)優(yōu)先級(jí)priority問(wèn)題,可以看到也是在注冊(cè)的時(shí)候

 int size = subscriptions.size();
        for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }

其實(shí)也是很簡(jiǎn)單的原理场靴。源碼分析得也差不多了啡莉,具體的需要大家自己去查看源碼港准。

總結(jié),EventBus的實(shí)現(xiàn)方式:反射 + 數(shù)據(jù)封裝 + 緩存 + 線程切換
通過(guò)查看EventBus的源碼收獲了什么咧欣?

  1. Builder模式封裝參數(shù)
  1. 根據(jù)用處不同進(jìn)行對(duì)不同的類封裝浅缸,比如:SubscriberMethodFinder用于訂閱方法的查找;FindState一個(gè)用SubscriberMethodFinder查找的輔助類,里面封裝了一些數(shù)據(jù);SubscriberMethod訂閱方法實(shí)體魄咕,就是通過(guò)注解的方法對(duì)應(yīng)的對(duì)象衩椒;Subscription一個(gè)訂閱對(duì)象。
  2. 緩存
  3. 線程切換使用方式
  4. 反射用法熟悉加強(qiáng)
  5. ThreadLocal是一個(gè)關(guān)于創(chuàng)建線程局部變量的類哮兰。
    通常情況下毛萌,我們創(chuàng)建的變量是可以被任何一個(gè)線程訪問(wèn)并修改的。而使用ThreadLocal創(chuàng)建的變量只能被當(dāng)前線程訪問(wèn)喝滞,其他線程則無(wú)法訪問(wèn)和修改阁将。
  6. CopyOnWriteArrayList:是一個(gè)適合用在讀多,寫少的并發(fā)應(yīng)用中右遭,它是一個(gè)線程安全的集合
  7. ConcurrentHashMap:是一個(gè)應(yīng)用于高并發(fā)的鍵值對(duì)

人生苦短,要學(xué)會(huì)享受做盅!但是我還沒(méi)到應(yīng)該安于享受的年齡!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窘哈,一起剝皮案震驚了整個(gè)濱河市吹榴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌滚婉,老刑警劉巖腊尚,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異满哪,居然都是意外死亡婿斥,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門哨鸭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)民宿,“玉大人,你說(shuō)我怎么就攤上這事像鸡』钣ィ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵只估,是天一觀的道長(zhǎng)志群。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蛔钙,這世上最難降的妖魔是什么锌云? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮吁脱,結(jié)果婚禮上桑涎,老公的妹妹穿的比我還像新娘彬向。我一直安慰自己,他們只是感情好攻冷,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布娃胆。 她就那樣靜靜地躺著,像睡著了一般等曼。 火紅的嫁衣襯著肌膚如雪里烦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天禁谦,我揣著相機(jī)與錄音招驴,去河邊找鬼。 笑死枷畏,一個(gè)胖子當(dāng)著我的面吹牛别厘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拥诡,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼触趴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了渴肉?” 一聲冷哼從身側(cè)響起冗懦,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仇祭,沒(méi)想到半個(gè)月后披蕉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乌奇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年没讲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片礁苗。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡爬凑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出试伙,到底是詐尸還是另有隱情嘁信,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布疏叨,位于F島的核電站潘靖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蚤蔓。R本人自食惡果不足惜卦溢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧既绕,春花似錦、人聲如沸涮坐。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)袱讹。三九已至疲扎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捷雕,已是汗流浹背椒丧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留救巷,地道東北人壶熏。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像浦译,于是被迫代替她去往敵國(guó)和親棒假。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,517評(píng)論 25 707
  • 原文鏈接:http://blog.csdn.net/u012810020/article/details/7005...
    tinyjoy閱讀 541評(píng)論 1 5
  • 項(xiàng)目到了一定階段會(huì)出現(xiàn)一種甜蜜的負(fù)擔(dān):業(yè)務(wù)的不斷發(fā)展與人員的流動(dòng)性越來(lái)越大精盅,代碼維護(hù)與測(cè)試回歸流程越來(lái)越繁瑣帽哑。這個(gè)...
    fdacc6a1e764閱讀 3,163評(píng)論 0 6
  • 徐老師,男叹俏,約50歲妻枕,既然稱呼其老師必定是位教師,現(xiàn)在某街道小學(xué)任教粘驰。徐太太屡谐,瘦瘦的,典型的中年婦女蝌数,說(shuō)起話來(lái)咬牙...
    臺(tái)州韓瑛閱讀 151評(píng)論 0 0