EventBus源碼分析(二):編譯庫(kù)源碼解析

前言#

本來(lái)想把EventBus的使用和編譯庫(kù)的分析一起說(shuō)萨驶,但是覺(jué)得篇幅有點(diǎn)太大了,編譯庫(kù)的東西雖然不多也不復(fù)雜艇肴,但是還是有很多能學(xué)習(xí)到的東西腔呜。

在上一篇已經(jīng)建議大家對(duì)apt 和注解的使用有了一定的了解之后再來(lái),否則你可能看的一臉懵逼再悼,如果你還接觸他們核畴,可以先看一下我之前寫(xiě)的博客:

注解(Annotation)的基本了解

正文#

使用apt編譯工具,主要是實(shí)現(xiàn)process(Set<? extends TypeElement> annotations, RoundEnvironment env)方法冲九,這里完成了生成類和使用注解的所有工作:

@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        Messager messager = processingEnv.getMessager();
        try {
            // 通過(guò)命令行得到了EventBus的索引值谤草,這個(gè)值通過(guò)apt工具類配置,用來(lái)找到程序的包名,也是文件要生成的位置
            String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
            // 檢查是否配置了參數(shù)
            if (index == null) {
                messager.printMessage(Diagnostic.Kind.ERROR, "No option " + OPTION_EVENT_BUS_INDEX +
                        " passed to annotation processor");
                return false;
            }
            //
            verbose = Boolean.parseBoolean(processingEnv.getOptions().get(OPTION_VERBOSE));
            // 此處通過(guò)字符串的截取得到了包名
            int lastPeriod = index.lastIndexOf('.');
            String indexPackage = lastPeriod != -1 ? index.substring(0, lastPeriod) : null;

            round++;
            // 接下來(lái)進(jìn)行了一些檢查丑孩,這個(gè)就不用看了
            if (verbose) {
                messager.printMessage(Diagnostic.Kind.NOTE, "Processing round " + round + ", new annotations: " +
                        !annotations.isEmpty() + ", processingOver: " + env.processingOver());
            }
            if (env.processingOver()) {
                if (!annotations.isEmpty()) {
                    messager.printMessage(Diagnostic.Kind.ERROR,
                            "Unexpected processing state: annotations still available after processing over");
                    return false;
                }
            }
            if (annotations.isEmpty()) {
                return false;
            }

            if (writerRoundDone) {
                messager.printMessage(Diagnostic.Kind.ERROR,
                        "Unexpected processing state: annotations still available after writing.");
            }
            // 這里才是重點(diǎn)冀宴,開(kāi)始收集Subscriber注解
            collectSubscribers(annotations, env, messager);
            // 開(kāi)始檢查某些注解是否要忽略
            checkForSubscribersToSkip(messager, indexPackage);
            // 檢查是否被注解的方法的集合 是空的
            if (!methodsByClass.isEmpty()) {
                createInfoIndexFile(index);
            } else {
                messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
            }
            writerRoundDone = true;
        } catch (RuntimeException e) {
            // IntelliJ does not handle exceptions nicely, so log and print a message
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected error in EventBusAnnotationProcessor: " + e);
        }
        return true;
    }

這就是整個(gè)編譯實(shí)現(xiàn)的過(guò)程,看著代碼量很多温学,但是實(shí)際上我們關(guān)注的點(diǎn)只有幾個(gè)略贮,首先這個(gè)eventBusIndex參數(shù)是從哪來(lái)的呢?

String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);這個(gè)參數(shù)是在要運(yùn)行的工程中配置的枫浙,你可以打開(kāi)EventBusPerformance的build.gradle文件:
apt { arguments { eventBusIndex "org.greenrobot.eventbusperf.MyEventBusIndex" } }

我們重點(diǎn)要看的只有三個(gè)方法:

// 這里才是重點(diǎn),開(kāi)始收集Subscriber注解
collectSubscribers(annotations, env, messager);
// 開(kāi)始檢查某些注解是否要忽略
checkForSubscribersToSkip(messager, indexPackage);
// 檢查是否被注解的方法的集合 是空的
if (!methodsByClass.isEmpty()) {
      // 開(kāi)始創(chuàng)建文件
      createInfoIndexFile(index);
}

首先通過(guò)collectSubscribers(annotations, env, messager);收集所有的注解:

/**
     * 收集所有的注解
     * */
    private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {
        // 遍歷所有的注解
        for (TypeElement annotation : annotations) {
            // 獲取使用注解的所有元素
            Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation);
            // 遍歷所有元素
            for (Element element : elements) {
                // 判斷這個(gè)被注解的元素是否是一個(gè)方法
                if (element instanceof ExecutableElement) {
                    ExecutableElement method = (ExecutableElement) element;
                    // 對(duì)這個(gè)方法進(jìn)行必要的檢查古拴,不允許static/ 必須是public / 只能有一個(gè)參數(shù)
                    if (checkHasNoErrors(method, messager)) {
                        // 找到這個(gè)方法的類
                        TypeElement classElement = (TypeElement) method.getEnclosingElement();
                        // 把類個(gè)方法保存起來(lái)
                        methodsByClass.putElement(classElement, method);
                    }
                } else {
                    messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
                }
            }
        }
    }

上面的注釋已經(jīng)寫(xiě)的非常詳細(xì)了箩帚,找到所有被注解的方法,然后會(huì)這些方法進(jìn)行檢查黄痪,都做了哪些限制呢紧帕?

/**
     * 對(duì)方法進(jìn)行檢查
     * */
    private boolean checkHasNoErrors(ExecutableElement element, Messager messager) {
        // 不允許是static靜態(tài)方法
        if (element.getModifiers().contains(Modifier.STATIC)) {
            messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must not be static", element);
            return false;
        }
        // 只能是public
        if (!element.getModifiers().contains(Modifier.PUBLIC)) {
            messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must be public", element);
            return false;
        }

        // 只能含有一個(gè)方法參數(shù)
        List<? extends VariableElement> parameters = ((ExecutableElement) element).getParameters();
        if (parameters.size() != 1) {
            messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must have exactly 1 parameter", element);
            return false;
        }
        return true;
    }

看來(lái)EventBus對(duì)于方法的限制還是很嚴(yán)格的,不能是static桅打,只能是public是嗜,而且還只能由一個(gè)參數(shù)。

注解的收集工作就到此結(jié)束挺尾,然后對(duì)這個(gè)集合再次篩選鹅搪,檢查某些注解是否要忽略,進(jìn)入到checkForSubscribersToSkip(messager, indexPackage):

private void checkForSubscribersToSkip(Messager messager, String myPackage) {
        // 遍歷剛才獲得類和其對(duì)應(yīng)的注解的集合
        for (TypeElement skipCandidate : methodsByClass.keySet()) {
            // 得到類
            TypeElement subscriberClass = skipCandidate;
            // 開(kāi)始循環(huán)判斷判斷遭铺,一直找到最頂端的父類
            while (subscriberClass != null) {
                // 檢查類是否可見(jiàn)
                if (!isVisible(myPackage, subscriberClass)) {
                    // 如果類是不可見(jiàn)的丽柿,把他保存到不可見(jiàn)類的集合中
                    boolean added = classesToSkip.add(skipCandidate);
                    // 打印出錯(cuò)誤日志
                    if (added) {
                        String msg;
                        if (subscriberClass.equals(skipCandidate)) {
                            msg = "Falling back to reflection because class is not public";
                        } else {
                            msg = "Falling back to reflection because " + skipCandidate +
                                    " has a non-public super class";
                        }
                        messager.printMessage(Diagnostic.Kind.NOTE, msg, subscriberClass);
                    }
                    break;
                }
                // 獲取這個(gè)類中被注解的方法
                List<ExecutableElement> methods = methodsByClass.get(subscriberClass);
                // 判空
                if (methods != null) {
                    // 遍歷方法
                    for (ExecutableElement method : methods) {
                        String skipReason = null;
                        // 得到第一個(gè)參數(shù)
                        VariableElement param = method.getParameters().get(0);
                        // 得到參數(shù)的類型
                        TypeMirror typeMirror = getParamTypeMirror(param, messager);
                        // 如果參數(shù)不是類或者是接口,不會(huì)處理
                        if (!(typeMirror instanceof DeclaredType) ||
                                !(((DeclaredType) typeMirror).asElement() instanceof TypeElement)) {
                            skipReason = "event type cannot be processed";
                        }
                        if (skipReason == null) {
                            // 獲取這個(gè)元素的類名
                            TypeElement eventTypeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
                            // 判斷類名是否可見(jiàn)魂挂,否則也不處理
                            if (!isVisible(myPackage, eventTypeElement)) {
                                skipReason = "event type is not public";
                            }
                        }
                        // 如果經(jīng)過(guò)上面的檢查甫题,這個(gè)注解要被忽略
                        if (skipReason != null) {
                            // 添加到被忽略的結(jié)合中,并且出書(shū)錯(cuò)誤日志
                            boolean added = classesToSkip.add(skipCandidate);
                            if (added) {
                                String msg = "Falling back to reflection because " + skipReason;
                                if (!subscriberClass.equals(skipCandidate)) {
                                    msg += " (found in super class for " + skipCandidate + ")";
                                }
                                messager.printMessage(Diagnostic.Kind.NOTE, msg, param);
                            }
                            break;
                        }
                    }
                }
                // 找到自己的父類涂召,再次循環(huán)
                subscriberClass = getSuperclass(subscriberClass);
            }
        }
    }

這個(gè)方法里把剛才收集到的注解和對(duì)應(yīng)的類坠非,進(jìn)行遍歷,從子類到最頂端的父類果正,先檢查類是否是可見(jiàn)的炎码,再檢查參數(shù)的合法性,只允許是類或者是接口秋泳,這也解釋了為什么使用基本類型的方法無(wú)法接受到Event辅肾。

下面是判斷類的可見(jiàn)的方法:

/**
     * 判斷一個(gè)類是否可見(jiàn)
     * */
    private boolean isVisible(String myPackage, TypeElement typeElement) {
        // 獲取類的修飾符
        Set<Modifier> modifiers = typeElement.getModifiers();
        boolean visible;
        // 如果這個(gè)類是public的,返回true
        if (modifiers.contains(Modifier.PUBLIC)) {
            visible = true;
        }
        // 如果這個(gè)類是PRIVATE 或 PROTECTED 返回false
        else if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) {
            visible = false;
        }
        // 其他情況
        else {
            // 獲取完整的包名
            String subscriberPackage = getPackageElement(typeElement).getQualifiedName().toString();
            // 如果包名是空的轮锥,說(shuō)明類是在最外層矫钓,是可見(jiàn)的
            if (myPackage == null) {
                visible = subscriberPackage.length() == 0;
            } else {
                // 判斷包名是否和類的包名相同,同樣是可見(jiàn)的
                visible = myPackage.equals(subscriberPackage);
            }
        }
        return visible;
    }

從方法中總結(jié),只有三種情況類是可見(jiàn)的:

1新娜、類是public修飾赵辕。
2、類在最外層概龄,不在任何包內(nèi)还惠。
3、正好在編譯的文件的包下私杜。

2蚕键、3是在比較極端的情況下,我們平時(shí)注意使用注解的類都是public的就OK了衰粹。

然后就是最后的一步锣光,生成我們的源文件createInfoIndexFile(index):

/**
     * 開(kāi)始創(chuàng)建每個(gè)類的索引文件,也是生成的Java文件
     *
     *   @param index 文件生成的位置
     * */
    private void createInfoIndexFile(String index) {
        BufferedWriter writer = null;
        try {
            // 在指定的位置铝耻,創(chuàng)建一個(gè)Java源文件
            JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
            int period = index.lastIndexOf('.');
            // 截取包名
            String myPackage = period > 0 ? index.substring(0, period) : null;
            // 截取類名
            String clazz = index.substring(period + 1);
            // 創(chuàng)建字符輸出流
            writer = new BufferedWriter(sourceFile.openWriter());
            // 寫(xiě)入包名
            if (myPackage != null) {
                writer.write("package " + myPackage + ";\n\n");
            }
            // 寫(xiě)入要引入的包和類
            writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");
            writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");
            writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");
            writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");
            writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");
            writer.write("import java.util.HashMap;\n");
            writer.write("import java.util.Map;\n\n");
            writer.write("/** This class is generated by EventBus, do not edit. */\n");
            // 這里開(kāi)始定義類
            writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
            // 創(chuàng)建一個(gè)私有的不可變的靜態(tài)變量SUBSCRIBER_INDEX誊爹,類型是Map<Class<?>, SubscriberInfo>
            writer.write("    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
            // static 方法塊,用來(lái)初始化靜態(tài)變量
            writer.write("    static {\n");
            writer.write("        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");
            
            // 這里把所有的類和方法都寫(xiě)到文件里了
            writeIndexLines(writer, myPackage);
            writer.write("    }\n\n");
            // 定義putIndex方法瓢捉,剛才的writeIndexLines中就是使用了這個(gè)方法
            // 把我們的類名频丘,還有注解的方法名,還有定義的優(yōu)先級(jí)和線程信息都放入了集合中
            writer.write("    private static void putIndex(SubscriberInfo info) {\n");
            writer.write("        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");
            writer.write("    }\n\n");
            writer.write("    @Override\n");
            writer.write("    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");
            writer.write("        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");
            writer.write("        if (info != null) {\n");
            writer.write("            return info;\n");
            writer.write("        } else {\n");
            writer.write("            return null;\n");
            writer.write("        }\n");
            writer.write("    }\n");
            writer.write("}\n");
        } catch (IOException e) {
            throw new RuntimeException("Could not write source for " + index, e);
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    //Silent
                }
            }
        }
    }

在文件中引入了幾個(gè)文件泡态,都是在EventBus中定義的搂漠,大家都去看一看,這里就略過(guò)了某弦。我們已經(jīng)知道這個(gè)文件中状答,有一個(gè)靜態(tài)變量SUBSCRIBER_INDEX,并且有對(duì)應(yīng)的put和get方法刀崖,然后再去看writeIndexLines(writer, myPackage):

private void writeIndexLines(BufferedWriter writer, String myPackage) throws IOException {
        // 開(kāi)始遍歷被注解的方法集合
        for (TypeElement subscriberTypeElement : methodsByClass.keySet()) {
            // 這里做了一個(gè)檢查惊科,查看是否這個(gè)方法被忽略了
            if (classesToSkip.contains(subscriberTypeElement)) {
                continue;
            }
            // 得到類名
            String subscriberClass = getClassString(subscriberTypeElement, myPackage);
            // 檢查這個(gè)類是否可見(jiàn)
            if (isVisible(myPackage, subscriberTypeElement)) {
                // 把類和被注解的方法都放入集合里面去
                writeLine(writer, 2,
                        "putIndex(new SimpleSubscriberInfo(" + subscriberClass + ".class,",
                        "true,", "new SubscriberMethodInfo[] {");
                List<ExecutableElement> methods = methodsByClass.get(subscriberTypeElement);
                writeCreateSubscriberMethods(writer, methods, "new SubscriberMethodInfo", myPackage);
                writer.write("        }));\n\n");
            } else {
                writer.write("        // Subscriber not visible to index: " + subscriberClass + "\n");
            }
        }
    }

/**
     * 寫(xiě)入被注解的方法
     * */
    private void writeCreateSubscriberMethods(BufferedWriter writer, List<ExecutableElement> methods,
                                              String callPrefix, String myPackage) throws IOException {
        // 開(kāi)始遍歷方法
        for (ExecutableElement method : methods) {
            // 得到放的參數(shù)
            List<? extends VariableElement> parameters = method.getParameters();
            // 得到第一個(gè)參數(shù)的類型
            TypeMirror paramType = getParamTypeMirror(parameters.get(0), null);
            // 得到這個(gè)類型的元素
            TypeElement paramElement = (TypeElement) processingEnv.getTypeUtils().asElement(paramType);
            // 得到方法名
            String methodName = method.getSimpleName().toString();
            // 得到類名,并且拼接了.class
            String eventClass = getClassString(paramElement, myPackage) + ".class";
            //  得到注解對(duì)象
            Subscribe subscribe = method.getAnnotation(Subscribe.class);
            List<String> parts = new ArrayList<>();
            // 開(kāi)始把字符放入到list中
            // 這是方法名
            parts.add(callPrefix + "(\"" + methodName + "\",");
            String lineEnd = "),";
            // 判斷優(yōu)先級(jí)
            if (subscribe.priority() == 0 && !subscribe.sticky()) {
                // 加入類名
                if (subscribe.threadMode() == ThreadMode.POSTING) {
                    parts.add(eventClass + lineEnd);
                } else {
                    // 加入類名
                    parts.add(eventClass + ",");
                    // 加入線程名
                    parts.add("ThreadMode." + subscribe.threadMode().name() + lineEnd);
                }
            } else {
                // 加入類名
                parts.add(eventClass + ",");
                // 加入線程名
                parts.add("ThreadMode." + subscribe.threadMode().name() + ",");
                parts.add(subscribe.priority() + ",");
                parts.add(subscribe.sticky() + lineEnd);
            }
            writeLine(writer, 3, parts.toArray(new String[parts.size()]));

            if (verbose) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Indexed @Subscribe at " +
                        method.getEnclosingElement().getSimpleName() + "." + methodName +
                        "(" + paramElement.getSimpleName() + ")");
            }

        }
    }

這個(gè)方法代碼很多但是邏輯很簡(jiǎn)單亮钦,就是把剛才經(jīng)過(guò)層層篩選的注解個(gè)對(duì)應(yīng)的類封裝成SimpleSubscriberInfo對(duì)象馆截,通過(guò)剛才的put方法,都保存到了SUBSCRIBER_INDEX中蜂莉。

SimpleSubscriberInfo里面有類的信息蜡娶,還有被注解的方法的信息,上面使用代碼的形式定義Java源文件映穗,所以很亂窖张,我們知道了他的實(shí)現(xiàn)過(guò)程就可以了,我們可以在運(yùn)行的程序編譯后蚁滋,查看生成的文件的內(nèi)容:

這里寫(xiě)圖片描述
/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscriberClassEventBusAsync.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventAsync", TestEvent.class, ThreadMode.ASYNC),
        }));

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscribeClassEventBusBackground.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventBackgroundThread", TestEvent.class, ThreadMode.BACKGROUND),
        }));

        putIndex(new SimpleSubscriberInfo(TestRunnerActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventMainThread", TestFinishedEvent.class, ThreadMode.MAIN),
        }));

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscribeClassEventBusMain.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventMainThread", TestEvent.class, ThreadMode.MAIN),
        }));

        putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.SubscribeClassEventBusDefault.class,
                true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEvent", TestEvent.class),
        }));

    }

    private static void putIndex(SubscriberInfo info) {
        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
    }

    @Override
    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
        if (info != null) {
            return info;
        } else {
            return null;
        }
    }
}

ok宿接,這樣我們就知道了剛才的Java源文件生成的內(nèi)容赘淮,整個(gè)編譯庫(kù)就到此結(jié)束了。

總結(jié)#

編譯庫(kù)的作用主要是:把類和其中被注解的方法睦霎,封裝成一個(gè)SubscriberInfo信息保存起來(lái)梢卸,SubscriberInfo里面不僅有類的信息,還有被注解的方法的必要的設(shè)置副女,例如sticky蛤高,threadMode等等,這些信息都會(huì)為框架層的實(shí)現(xiàn)邏輯服務(wù)碑幅。

ok戴陡,編譯庫(kù)我們就已經(jīng)完美攻克了,接下來(lái)就是看看再Java框架層的實(shí)現(xiàn)邏輯了沟涨。

因?yàn)槠鶈?wèn)題恤批,我會(huì)漏掉一些方法的說(shuō)明,所以我把我的工程連接發(fā)給大家拷窜,方便大家學(xué)習(xí)使用:https://github.com/li504799868/EventBus-master

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末开皿,一起剝皮案震驚了整個(gè)濱河市涧黄,隨后出現(xiàn)的幾起案子篮昧,更是在濱河造成了極大的恐慌,老刑警劉巖笋妥,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懊昨,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡春宣,警方通過(guò)查閱死者的電腦和手機(jī)酵颁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)月帝,“玉大人躏惋,你說(shuō)我怎么就攤上這事∪赂ǎ” “怎么了簿姨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)簸搞。 經(jīng)常有香客問(wèn)我扁位,道長(zhǎng),這世上最難降的妖魔是什么趁俊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任域仇,我火速辦了婚禮,結(jié)果婚禮上寺擂,老公的妹妹穿的比我還像新娘暇务。我一直安慰自己泼掠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布般卑。 她就那樣靜靜地躺著武鲁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蝠检。 梳的紋絲不亂的頭發(fā)上沐鼠,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音叹谁,去河邊找鬼饲梭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛焰檩,可吹牛的內(nèi)容都是我干的憔涉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼析苫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼兜叨!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起衩侥,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤国旷,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后茫死,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體跪但,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年峦萎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屡久。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡爱榔,死狀恐怖被环,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情详幽,我是刑警寧澤筛欢,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站妒潭,受9級(jí)特大地震影響悴能,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜雳灾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一漠酿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谎亩,春花似錦炒嘲、人聲如沸宇姚。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)浑劳。三九已至,卻和暖如春夭拌,著一層夾襖步出監(jiān)牢的瞬間魔熏,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工鸽扁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蒜绽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓桶现,卻偏偏與公主長(zhǎng)得像躲雅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子骡和,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,117評(píng)論 25 707
  • 博文出處:EventBus源碼解析相赁,歡迎大家關(guān)注我的博客,謝謝慰于! 0001B 時(shí)近年末钮科,但是也沒(méi)閑著。最近正好在看...
    俞其榮閱讀 1,301評(píng)論 1 16
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理东囚,服務(wù)發(fā)現(xiàn)跺嗽,斷路器战授,智...
    卡卡羅2017閱讀 134,656評(píng)論 18 139
  • EventBus源碼分析(二) 在之前的一篇文章EventBus源碼分析(一)分析了EventBus關(guān)于注冊(cè)注銷以...
    蕉下孤客閱讀 1,662評(píng)論 0 10
  • 01 有時(shí)候我覺(jué)得活著特別累。累到不知道為什么活著楣导,累到什么都不想干废境,甚至不想活。 而其實(shí)我什么都沒(méi)干筒繁。 我并不是...
    拒絕_1eca閱讀 458評(píng)論 0 0