[Android] 注解處理

需求:有一組功能模塊昭抒,每個(gè)功能模塊負(fù)責(zé)處理一種具體功能且有一個(gè)唯一的標(biāo)識(shí)惭嚣;這些功能模塊隨項(xiàng)目迭代會(huì)有動(dòng)態(tài)的修改瞳脓、增加或者刪除饭望。

  • 如果是你會(huì)如何設(shè)計(jì)實(shí)現(xiàn)這個(gè)需求仗哨?
    我可能會(huì)這樣做:新建一個(gè)功能模塊管理類,管理類中預(yù)加載所有的功能模塊铅辞;提供一個(gè)方法厌漂,可以根據(jù)標(biāo)識(shí)獲取具體的功能模塊;然后就可以調(diào)用功能模塊的具體方法了斟珊。

  • 這樣做有什么問(wèn)題苇倡?
    每增加一個(gè)功能模塊你可能至少需要改兩個(gè)地方甚至更多:創(chuàng)建具體的功能模塊類;在功能模塊管理類中加入新增的功能模塊(此處可能要改一個(gè)地方以上)囤踩。

  • 有沒(méi)有更好的實(shí)現(xiàn)方式旨椒?
    答案當(dāng)然是有,比如只新建一個(gè)功能模塊類堵漱,其他工作自動(dòng)完成综慎。下面介紹如果通過(guò)編譯時(shí)注解的方式解決這個(gè)問(wèn)題。

0x01 從一個(gè)例子開(kāi)始

考慮到原理理解的難易怔锌,這里先給出解決方案:創(chuàng)建具體的功能模塊類寥粹,在這些類上添加自定義的注解变过,注解上標(biāo)識(shí)這個(gè)類可以處理哪些具體的功能。在程序編譯期根據(jù)這些注解自動(dòng)生成一個(gè)功能模塊管理類涝涤。使用時(shí)直接調(diào)用此功能管理類即可(此管理類和我們手動(dòng)創(chuàng)建的一樣)媚狰。若有新的功能模塊加入(或者移除),我們只需要?jiǎng)?chuàng)建(或者刪除)對(duì)應(yīng)功能模塊類即可阔拳,只改這一個(gè)地方崭孤。

下面以一個(gè)具體的例子說(shuō)明這個(gè)問(wèn)題。

有一個(gè)班級(jí)糊肠,包含若干個(gè)學(xué)生辨宠,每個(gè)學(xué)生有姓名和年齡,同時(shí)還有一個(gè)對(duì)應(yīng)的職責(zé)货裹,如班長(zhǎng)嗤形,語(yǔ)文課代表,數(shù)學(xué)課代表弧圆,體育課代表等赋兵。班長(zhǎng)負(fù)責(zé)管理班級(jí),課代表負(fù)責(zé)收作業(yè)等搔预。這個(gè)班級(jí)可能有同學(xué)會(huì)退學(xué)霹期,也可能有新的同學(xué)加入。老師通過(guò)一個(gè)管理類來(lái)管理這個(gè)班級(jí)的所有學(xué)生拯田。

創(chuàng)建項(xiàng)目

創(chuàng)建一個(gè)Android項(xiàng)目:DemoAnnotation历造,在DemoAnnotation中創(chuàng)建兩個(gè)Java Library: lib_annotationlib_compiler,然后分別配置其build.gradle船庇。

?  DemoAnnotation git:(master) ? tree -L 1
.
├── app
├── lib_annotation
└── lib_compiler

以下全部使用Java 8

DemoAnnotation

plugins {
    id 'com.android.application'
}
android {
    ...
    defaultConfig {
        applicationId "com.ttdevs.demo.annotation"
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                argument "debug", "true"
                argument "param1", "value1"
            }
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
dependencies {
    ...
    implementation project(path: ':lib_annotation')
    annotationProcessor project(path: ':lib_compiler')
}

引入兩個(gè)library吭产,注意一個(gè)是implementation,另一個(gè)是annotationProcessor溢十。

lib_annotation

plugins {
    id 'java-library'
}
java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

lib_compiler

plugins {
    id 'java-library'
}
java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
    implementation project(path: ':lib_annotation')
    implementation 'com.squareup:javapoet:1.13.0'
    implementation 'com.google.auto.service:auto-service-annotations:1.0'
    annotationProcessor 'com.google.auto.service:auto-service:1.0'
}

通過(guò)implementation 'com.google.auto.service:auto-service-annotations:1.0'引入@AutoService(Processor.class)垮刹。

lib_annotation中創(chuàng)建注解類

這里創(chuàng)建一個(gè)叫Student的注解,包含姓名张弛,年齡荒典,職責(zé),如下:

package com.ttdevs.demo.lib.annotation;
...
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Student {
    String name();
    int age() default 8;
    /**
     * Duty information, ClassMonitor, Chinese, Math, Sport, Art etc.
     *
     * @return
     */
    String[] duty() default {};
}

lib_compiler中處理注解

創(chuàng)建StudentProcessor類吞鸭,繼承AbstractProcessor寺董,其上添加注解@AutoService(Processor.class),代碼如下:

package com.ttdevs.demo.lib.compiler;
...
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedOptions({StudentProcessor.OPTIONS_PARAM_DEBUG})
public class StudentProcessor extends AbstractProcessor {
    protected static final String OPTIONS_PARAM_DEBUG = "debug";

    private Filer mFiler;
    private Elements mElements; // source file
    private Map<String, Element> mClassMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        LogUtils.init(processingEnv.getMessager());
        mFiler = processingEnv.getFiler();
        mElements = processingEnv.getElementUtils();

        LogUtils.d("Init debug: " + processingEnv.getOptions().get(OPTIONS_PARAM_DEBUG));
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(ClassUtils.CLASS_STUDENT.getCanonicalName());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        LogUtils.d(String.format("=========Process %s============", !roundEnv.processingOver() ? "start" : "  end"));

        for (Element item : roundEnv.getRootElements()) {
            LogUtils.d("Process Class: " + item.getSimpleName());
        }

        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(ClassUtils.CLASS_STUDENT);
        if (null != elements && !elements.isEmpty()) {
            for (Element element : elements) {
                if (element.getKind() == ElementKind.CLASS) {
                    Student student = element.getAnnotation(ClassUtils.CLASS_STUDENT);
                    mClassMap.put(student.name(), element);
                }
            }
            // Create StudentManager.java
            StudentManagerBuilder.create()
                    .filer(mFiler)
                    .build(mClassMap);
        }
        return true;
    }
}

不用太在意代碼長(zhǎng)度刻剥,只需關(guān)注關(guān)鍵點(diǎn)即可遮咖。下面對(duì)這個(gè)類做簡(jiǎn)要分析:

  1. @AutoService(Processor.class)

    這個(gè)注解會(huì)在META-INF/services/javax.annotation.processing.Processor這個(gè)文件中添加一行,內(nèi)容為當(dāng)前類的完整路徑造虏。若你有多個(gè)注解處理器類御吞,則會(huì)每個(gè)注解處理器都會(huì)在這個(gè)文件中占一行麦箍。別問(wèn)為什么要這樣做,問(wèn)就是javac規(guī)定的陶珠。

    DemoAnnotation/lib_compiler
    └── build
        └── classes
            └── java
                └── main
                    └── META-INF
                        └── services
                            └── javax.annotation.processing.Processor
    
    ?  DemoAnnotation git:(master) ? cat lib_compiler/build/classes/java/main/META-INF/services/javax.annotation.processing.Processor
    com.ttdevs.demo.lib.compiler.StudentProcessor
    ?  DemoAnnotation git:(master) ?
    
  2. 重寫(xiě)幾個(gè)重要方法

    • init(ProcessingEnvironment processingEnv)

      初始化的配置挟裂,一般包含MessagerElementsFiler揍诽。Messager用于打印Log诀蓉。注解處理器創(chuàng)建之后此方法只會(huì)被調(diào)用一次。

    • getSupportedSourceVersion()

      配置源碼的版本暑脆,等同于@SupportedSourceVersion(SourceVersion.RELEASE_8)渠啤。只在創(chuàng)建之后調(diào)用一次。

    • getSupportedAnnotationTypes()

      處理的注解類型添吗,這里只有一個(gè)Student注解沥曹。只在創(chuàng)建之后調(diào)用一次。

    • process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)

      具體的處理邏輯碟联,包含注解的處理架专,最終管理類的生成。每一輪注解處理此方法都會(huì)被調(diào)用玄帕。所以此方法會(huì)被調(diào)用多次。

  3. 編譯項(xiàng)目后會(huì)自動(dòng)生成下面這個(gè)類

    DemoAnnotation/app/build/generated/ap_generated_sources
        └── debug
            └── out
                └── com
                    └── ttdevs
                        └── demo
                            └── annotation
                                └── StudentManager.java
    

    此文件可以在自己的代碼中直接調(diào)用想邦,最終會(huì)和其他源碼共同參與編譯裤纹。具體如何生成參見(jiàn)后續(xù)介紹。

app中添加數(shù)據(jù)

創(chuàng)建幾個(gè)學(xué)生數(shù)據(jù):

DemoAnnotation/app/src/main/java
    └── com
        └── ttdevs
            └── demo
                └── annotation
                    ├── MainActivity.java
                    └── model
                        ├── BaseStudent.java
                        ├── David.java
                        ├── Harry.java
                        ├── Jason.java
                        └── Norris.java

重新編譯項(xiàng)目丧没,下面看一下具體生產(chǎn)的StudentManager代碼:

package com.ttdevs.demo.annotation;
...
public class StudentManager {
    private static final Map<String, BaseStudent> MAP_STUDENT_NAME = new HashMap<>();
    private static final Map<String, BaseStudent> MAP_STUDENT_DUTY = new HashMap<>();

    public static final StudentManager INSTANCE = new StudentManager();

    private StudentManager() {
        BaseStudent tempNorris = new BaseStudent();
        tempNorris.name = "Norris";
        tempNorris.age = 28;
        MAP_STUDENT_NAME.put("Norris", tempNorris);
        BaseStudent tempHarry = new BaseStudent();
        tempHarry.name = "Harry";
        tempHarry.age = 30;
        tempHarry.duty = new java.lang.String[]{"Math"};
        MAP_STUDENT_NAME.put("Harry", tempHarry);
        BaseStudent tempDavid = new BaseStudent();
        tempDavid.name = "David";
        tempDavid.age = 50;
        tempDavid.duty = new java.lang.String[]{"ClassMonitor"};
        MAP_STUDENT_NAME.put("David", tempDavid);
        BaseStudent tempJason = new BaseStudent();
        tempJason.name = "Jason";
        tempJason.age = 20;
        tempJason.duty = new java.lang.String[]{"Chinese", "Sport"};
        MAP_STUDENT_NAME.put("Jason", tempJason);
        ;
        MAP_STUDENT_DUTY.put("Math", MAP_STUDENT_NAME.get("Harry"));
        MAP_STUDENT_DUTY.put("ClassMonitor", MAP_STUDENT_NAME.get("David"));
        MAP_STUDENT_DUTY.put("Chinese", MAP_STUDENT_NAME.get("Jason"));
        MAP_STUDENT_DUTY.put("Sport", MAP_STUDENT_NAME.get("Jason"));
    }

    /**
     * Get student by duty
     *
     * @param duty
     * @return
     */
    public BaseStudent getStudent(String duty) {
        return MAP_STUDENT_DUTY.get(duty);
    }

    public int exam() {
        int result = 0;
        for (String key : MAP_STUDENT_NAME.keySet()) {
            BaseStudent student = MAP_STUDENT_NAME.get(key);
            result += student.exam();
        }
        return result / MAP_STUDENT_NAME.size();
    }

    public void study() {
        for (String key : MAP_STUDENT_NAME.keySet()) {
            BaseStudent student = MAP_STUDENT_NAME.get(key);
            student.study();
        }
    }

    public void work() {
        for (String key : MAP_STUDENT_NAME.keySet()) {
            BaseStudent student = MAP_STUDENT_NAME.get(key);
            student.work();
        }
    }
}

構(gòu)造方法中鹰椒,我們創(chuàng)建了一個(gè)以學(xué)生姓名為Key的Map,一個(gè)學(xué)生職責(zé)為Key的Map呕童。我們可以通過(guò)職責(zé)或者姓名查找到對(duì)應(yīng)的學(xué)生漆际,然后執(zhí)行他的方法。也可以對(duì)全班同學(xué)進(jìn)行操作夺饲,如考試等奸汇。

完整的代碼參考這里,根據(jù)這個(gè)例子往声,班級(jí)中若有學(xué)生加入或者離開(kāi)擂找,我們只需刪除或者添加對(duì)應(yīng)的學(xué)生類重新編譯即可。

0x02 annotationProcessor

APT是什么浩销?javac贯涎、apt、android-apt和annotationProcessor這幾個(gè)又是什么關(guān)系慢洋?

APT和javac

  • APT:Annotation Processing Tool

    APT是Sun(沒(méi)錯(cuò)塘雳,不是Oracle)在JDK1.5版本提供的處理源碼級(jí)別注解的工具(注解也是在JDK1.5版本引入的)陆盘。作用是根據(jù)源碼中的注解生成新的文件,這里主要還是java文件败明。不過(guò)在JDK1.6就無(wú)情的被javac取代了隘马。

  • annotationProcessor和android-apt

    二者是相同的東西,android-apt為個(gè)人開(kāi)發(fā)者開(kāi)發(fā)的肩刃,gradle2.2之前的版本被廣泛使用祟霍。gradle2.2版本,google官方出了annotationProcessor盈包,android-apt也隨之退出歷史舞臺(tái)沸呐。我的理解:annotationProcessor是一個(gè)將我們的寫(xiě)的注解相關(guān)代碼(注解,注解處理器等)打包傳給javac處理的工具呢燥,最終注解的處理還是由javac來(lái)完成崭添。

  • javac

    javac不僅負(fù)責(zé)java的編譯工作,同時(shí)還負(fù)責(zé)處理java源碼中的編譯期注解叛氨。引用一段關(guān)于javac的說(shuō)明:

The javac command provides direct support for annotation processing, superseding the need for the separate annotation processing command, apt.
簡(jiǎn)單翻譯:javac提供了對(duì)注解處理的直接支持呼渣,從而取代了對(duì)單獨(dú)處理注解命令apt的需求。

javac對(duì)注解的處理流程

  • 首先javac掃描所有源文件寞埠,確定有哪些類中包含注解屁置;
  • 然后javac查詢注解處理器確定他們處理的注解,查找路徑為META-INF/services/javax.annotation.processing.Processor仁连,此文件記錄了用戶的所有注解處理器蓝角,每行一個(gè)(用戶在自己的注解處理器中可聲明所處理的具體注解,若你不聲明則不會(huì)調(diào)用這個(gè)注解處理器的process方法)饭冬;
  • 根據(jù)注解處理器聲明的所處理的注解使鹅,將相應(yīng)的注解分配給對(duì)應(yīng)的注解處理類處理;
  • 若注解處理類產(chǎn)生了新的源文件昌抠,則重復(fù)上述動(dòng)作患朱,直到產(chǎn)生的新文件無(wú)注解為止;
  • 至此炊苫,注解處理流程結(jié)束裁厅,javac轉(zhuǎn)去處理其他工作。

更詳細(xì)準(zhǔn)確的介紹參見(jiàn)這里:https://docs.oracle.com/javase/8/docs/api/javax/annotation/processing/Processor.html

annotationProcessor

第一部分的例子已經(jīng)詳細(xì)介紹如何使用annotationProcessor劝评,下面介紹一下原理姐直。

com.google.auto.service:auto-service-annotations:1.0

這個(gè)Library僅僅包含了AutoService這個(gè)注解,源碼如下:

package com.google.auto.service;
@Documented
@Retention(CLASS)
@Target(TYPE)
public @interface AutoService {
  /** Returns the interfaces implemented by this service provider. */
  Class<?>[] value();
}

通過(guò)注釋蒋畜,我們可以得知声畏,使用時(shí)必須注意下面幾點(diǎn):

  • 必須用在非內(nèi)部,非匿名,具體的類上
  • 這個(gè)類必須包含一個(gè)public無(wú)參的構(gòu)造函數(shù)
  • 實(shí)現(xiàn)values()返回的接口類型

com.google.auto.service:auto-service:1.0

?  auto-service-1.0-sources tree
.
├── META-INF
│   ├── MANIFEST.MF
│   ├── gradle
│   │   └── incremental.annotation.processors
│   └── services
│       └── javax.annotation.processing.Processor
└── com
    └── google
        └── auto
            └── service
                └── processor
                    ├── AutoServiceProcessor.java
                    ├── ServicesFiles.java
                    └── package-info.java

jar包中主要包含兩部分

  • META-INF/services/javax.annotation.processing.Processor

    其內(nèi)容僅有一行插龄,如下:

    com.google.auto.service.processor.AutoServiceProcessor
    
  • AutoServiceProcessor

    public class AutoServiceProcessor extends AbstractProcessor {
      ...
      @Override
      public ImmutableSet<String> getSupportedAnnotationTypes() {
        return ImmutableSet.of(AutoService.class.getName());
      }
      ...
      @Override
      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {...}
    }
    

以上信息可以得知愿棋,AutoServiceProcessor是處理AutoService的注解處理器。通過(guò)源碼可以得知其主要功能是幫我們?cè)?code>META-INF/services/javax.annotation.processing.Processor中配置自定義的注解處理器均牢。

另外糠雨,AutoService.javacom.google.auto.service:auto-service:1.0這個(gè)庫(kù)中定義。其內(nèi)容僅僅包含AutoService.java這個(gè)注解的定義徘跪。大家思考一下為什么就這一個(gè)類不和com.google.auto.service:auto-service:1.0定義在一起甘邀?請(qǐng)自尋答案。

工作流程

用戶創(chuàng)建自定義注解垮庐,同時(shí)創(chuàng)建處理這個(gè)注解的注解處理器松邪,在注解處理器中使用@AutoService注解,javac檢測(cè)到這個(gè)注解丟給AutoServiceProcessor處理哨查,AutoServiceProcessor自動(dòng)幫我們把自定義的注解處理器配置到META-INF/services/javax.annotation.processing.Processor(當(dāng)然你也可以不用@AutoService注解自己手動(dòng)配置)逗抑。

以上可知,annotationProcessor僅僅告訴javac這個(gè)java library內(nèi)有注解需要處理寒亥。

0x03 JavaPoet

生成Java文件邮府,待續(xù)。

0x04 Debug

Log

StudentProcessorinit(ProcessingEnvironment processingEnv)被調(diào)用的時(shí)候溉奕,我們可以獲取一個(gè)Messager對(duì)象褂傀,通過(guò)這個(gè)對(duì)象我們可以向編譯控制臺(tái)輸出我們的調(diào)試信息,如下:

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class StudentProcessor extends AbstractProcessor {
    private Messager messager;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        messager = processingEnv.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        messager.printMessage(Diagnostic.Kind.NOTE, String.format("=========Process %s============",
                !roundEnv.processingOver() ? "start" : "  end"));

        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(ClassUtils.CLASS_STUDENT);
        if (null == elements || elements.isEmpty()) {
            return true;
        }
        for (Element element : elements) {
            Student student = element.getAnnotation(ClassUtils.CLASS_STUDENT);
            messager.printMessage(Diagnostic.Kind.ERROR, student.name()));
        }
        return true;
    }
}

實(shí)際使用我們可以把Messager封裝到一個(gè)工具類加勤,具體可參見(jiàn)DemoAndroid紊服。

Debug

除了打Log,我們也可以對(duì)相關(guān)代碼進(jìn)行遠(yuǎn)程調(diào)試胸竞,操作如下:

  1. Run/Debug Configuration > Edit Configurations...

    Debug_01_Configuration.png
  2. + > Remote:

    • Input Configuration Name: Your Config Name
    • Copy Command line arguments for remote JVM: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
    • Click OK
Debug_02_Add_Configuration.png
  1. Opengradle.properties, add line in the end:
    org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
Debug_03_Edit_Gradle_Properties.png
  1. Add breakpoints in your Processor files and click Debug button
Debug_04_Add_Breakpoints_Debug.png
  1. Rebuild your project, start debug

Congratulations!

0xFF Reference

  1. Interface: javax.annotation.processing.Processor
  2. javac: Annotation Processing
  3. Annotation Processing Tool (apt)
  4. Annotation Processing 101
  5. Annotation-Processing-Tool詳解
  6. how do you debug java annotation processors using intellij?
  7. auto-service-1.0
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市参萄,隨后出現(xiàn)的幾起案子卫枝,更是在濱河造成了極大的恐慌,老刑警劉巖讹挎,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件校赤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡筒溃,警方通過(guò)查閱死者的電腦和手機(jī)马篮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)怜奖,“玉大人浑测,你說(shuō)我怎么就攤上這事。” “怎么了迁央?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵掷匠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我岖圈,道長(zhǎng)讹语,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任蜂科,我火速辦了婚禮顽决,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘导匣。我一直安慰自己才菠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布逐抑。 她就那樣靜靜地躺著鸠儿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪厕氨。 梳的紋絲不亂的頭發(fā)上进每,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音命斧,去河邊找鬼田晚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛国葬,可吹牛的內(nèi)容都是我干的贤徒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼汇四,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼接奈!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起通孽,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤序宦,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后背苦,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體互捌,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年行剂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秕噪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡厚宰,死狀恐怖腌巾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤壤躲,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布城菊,位于F島的核電站,受9級(jí)特大地震影響碉克,放射性物質(zhì)發(fā)生泄漏凌唬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一漏麦、第九天 我趴在偏房一處隱蔽的房頂上張望客税。 院中可真熱鬧,春花似錦撕贞、人聲如沸更耻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)秧均。三九已至,卻和暖如春号涯,著一層夾襖步出監(jiān)牢的瞬間目胡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工链快, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留誉己,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓域蜗,卻偏偏與公主長(zhǎng)得像巨双,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子霉祸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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