Dagger2 使用詳解

標簽: Android Dagger2


更新

伴隨著 Android Gradle 插件 2.2 版本的發布,近期 android-apt 作者在官網發表聲明證實了后續將不會繼續維護 android-apt,并推薦大家使用 Android 官方插件提供的相同能力。也就是說,大約三年前推出的 android-apt 即將告別開發者,退出歷史舞臺,Android Gradle 插件提供了名為 annotationProcessor 的功能來完全代替 android-apt
所以新的配置信息可以更加簡單,在Project的 build.gradle文件添加以下內容:

buildscript {
    
    dependencies {
        classpath 'me.tatarka:gradle-retrolambda:3.5.0'//支持lambda表達式,在Dagger2中可以不用添加
    }
}

然后在Module下的build.gradle添加以下內容:

apply plugin: 'me.tatarka.retrolambda'

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    annotationProcessor 'com.google.dagger:dagger-compiler:2.4'
    compile 'com.google.dagger:dagger:2.4'
    provided 'org.glassfish:javax.annotation:10.0-b28'
}

前言

Dagger2 是一款使用在Java和Android上的依賴注入的一個類庫。

配置信息

使用Android Studio 創建一個新的項目,在Project的 build.gradle文件添加以下內容:

buildscript {
    
    dependencies {
        classpath 'me.tatarka:gradle-retrolambda:3.2.4'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

并在Module下的build.gradle添加以下內容:

apply plugin: 'com.neenbedankt.android-apt'
apply plugin: 'me.tatarka.retrolambda'

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    apt 'com.google.dagger:dagger-compiler:2.4'
    compile 'com.google.dagger:dagger:2.4'
    provided 'org.glassfish:javax.annotation:10.0-b28'
}

這樣就基本完全了Dagger2的配置環境(順便也配置了支持lambda表達式)。

Dagger2基本使用

我們先簡單地創建一個類:

public class Poetry {
    private String mPemo;

    // 用Inject標記構造函數,表示用它來注入到目標對象中去
    @Inject
    public Poetry() {
        mPemo = "生活就像海洋";
    }

    public String getPemo() {
        return mPemo;
    }
}

然后我們在MainActivity中使用這個類:

public class MainActivity extends AppCompatActivity {

    //添加@Inject注解,表示這個mPoetry是需要注入的
    @Inject
    Poetry mPoetry;

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    private void initView() {
        mTextView = (TextView) findViewById(R.id.tv_poetry);
        mTextView.setText(mPoetry.getPoems());
    }
}

但是這樣直接運行是會出錯的,此時這樣子在MainActivity中的mPoetry對象是無法被注入的,因為MainActivity不知道去哪里找到它的實例去注入生成,這時我們需要一個連接器Component,讓上面這兩個類產生聯系:

//用@Component表示這個接口是一個連接器,能用@Component注解的只
//能是interface或者抽象類
@Component
public interface MainComponent {

    /**
     * 需要用到這個連接器的對象,就是這個對象里面有需要注入的屬性
     * (被標記為@Inject的屬性)
     * 這里inject表示注入的意思,這個方法名可以隨意更改,但建議就
     * 用inject即可。
     */
    void inject(MainActivity activity);
}

先運行一遍,AS會生成一些類,再修改一下MainActivity:

public class MainActivity extends AppCompatActivity {

    //添加@Inject注解,表示這個mPoetry是需要注入的
    @Inject
    Poetry mPoetry;

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 使用Dagger2生成的類 生成組件進行構造,并注入
        DaggerMainComponent.builder()
                .build()
                .inject(this);

        initView();
    }

    private void initView() {
        mTextView = (TextView) findViewById(R.id.tv_poetry);
        mTextView.setText(mPoetry.getPoems());
    }
}

運行,如下

運行結果
運行結果

上面MainActivity中的Poetry實例并不直接由MainActivity類創建,而是由MainActivityComponent類注入生成實例。以上就是一個簡單的Dagger2示例。

@Module

有時候我們并不能直接在構造函數里面添加@Inject注解,或者類中存在多個構造函數時,@Inject也只能注解其中一個構造函數,不能注解多個構造函數,這里是會產生歧義性,因為Dagger2無法確認調用哪一個構造函數來生成例的實例對象。另外一種情況是我們在項目中引用第三方類庫時,也是無法直接在類構造函數中添加@Inject注解的,所以我們需要用到@Module注解了。
@Module是用來生產實例來注入對象的,它類似一個工廠,集中創建要注入的類的對象實例。下面我們引用一下Gson庫來看看@Module是怎么使用的,創建MainModule類:

/*
@Module注解表示這個類提供生成一些實例用于注入
 */
@Module
public class MainModule {

    /**
     * @Provides 注解表示這個方法是用來創建某個實例對象的,這里我們創建返回Gson對象
     * 方法名隨便,一般用provideXXX結構
     * @return 返回注入對象
     */
    @Provides
    public Gson provideGson(){
        return new Gson();
    }
}

添加完這個類后,我們要與之前寫的類產生關聯,不然誰知道你這里提供了生成Gson實例的方法啊。修改MainCompontent:

//這里表示Component會從MainModule類中拿那些用@Provides注解的方法來生成需要注入的實例
@Component(modules = MainModule.class)
public interface MainComponent {

    /**
     * 需要用到這個連接器的對象,就是這個對象里面有需要注入的屬性
     * (被標記為@Inject的屬性)
     * 這里inject表示注入的意思,這個方法名可以隨意更改,但建議就
     * 用inject即可。
     */
    void inject(MainActivity activity);
}

這里多了一個依賴,依賴MainModule類中的方法生成Gson實例,我們在MainActivity里注入Gson實例:

public class MainActivity extends AppCompatActivity {

    //添加@Inject注解,表示這個mPoetry是需要注入的
    @Inject
    Poetry mPoetry;

    @Inject
    Gson mGson;

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 使用Dagger2生成的類 生成組件進行構造,并注入
        DaggerMainComponent.builder()
                .build()
                .inject(this);

        initView();
    }

    private void initView() {
        mTextView = (TextView) findViewById(R.id.tv_poetry);
        String json = mGson.toJson(mPoetry);
        mTextView.setText(json);
    }
}

運行,結果如下:

運行結果
運行結果

Component可以依賴多個Module對象,以上的構造方法與生成方法都是無參生成實例的,如果我們帶參數應該怎么做了?我們創建多一個PoetryModule用于提供Poetry實例:

@Module
public class PoetryModule {

    // 這個方法需要一個String參數,在Dagger2注入中,這些參數也是注入形式的,也就是
    // 要有其他對方提供參數poems的生成,不然會造成編譯出錯
    @Provides
    public Poetry providePoetry(String poems){
        return new Poetry(poems);
    }
    
    // 這里提供了一個生成String的方法,在這個Module里生成Poetry實例時,會查找到這里
    // 可以為上面提供String類型的參數
    @Provides
    public String providePoems(){
        return "只有意志堅強的人,才能到達彼岸";
    }
}

修改MainComponent依賴:

//這里表示Component會從MainModule類中拿那些用@Provides注解的方法來生成需要注入的實例
@Component(modules = {MainModule.class,PoetryModule.class})
public interface MainComponent {

    /**
     * 需要用到這個連接器的對象,就是這個對象里面有需要注入的屬性
     * (被標記為@Inject的屬性)
     * 這里inject表示注入的意思,這個方法名可以隨意更改,但建議就
     * 用inject即可。
     */
    void inject(MainActivity activity);
}

運行,就可以看到不同的詩詞了:

運行結果
運行結果

細心的同學就會發現了,我們提供了兩個可以生成Poetry實例的方法,一個是在Poetry類的構造函數時候用@Inject提供的實例創建方法,一個是在PoetryModule中的@Privodes注解的providePoetry方法,而在上面的運行結果中我們發現是調用了PoetryModule提供的方法,這里就要說明一下優先級的問題,在上面這種既在構造函數中用@Inject提供注入來源,也在@Module中用@Privodes注解提供注入來源的,Dagger2是先從@Privodes查找類實例,如果找到了就用@Module提供的方法來創建類實例,如果沒有就從構造函數里用@Inject注解的生成類實例,如果二者都沒有,則報錯,簡而言之,就是@Module的優先級高于@Inject
另外這里還要說明一點,在providePoetry(String)方法中,String這個參數也是要注入提供的,必須也要有在同一個連接器里面有提供,其中在構建類實例的時候,會按照以下順序執行:

  1. 從Module中查找類實例創建方法
  2. Module中存在創建方法,則看此創建方法有沒有參數
    1. 如果有參數,這些參數也是由Component提供的,返回步驟1逐一生成參數類實例,最后再生成最終類實例
    2. 如果無參數,則直接由這個方法生成最終類實例
  3. Module中沒有創建方法,則從構造函數里面找那個用@Inject注解的構造函數
    1. 如果該構造函數有參數,則也是返回到步驟1逐一生成參數類實例,最后調用該構造函數生成類實例
    2. 如果該構造函數無參數,則直接調用該構造函數生成類實例

以上就是一次注入生成類實例的生成步驟。

@Scope

我們創建多一個Activity,這個Activity也注入了Poetry跟Gson對象:

public class OtherActivity extends AppCompatActivity {

    //添加@Inject注解,表示這個mPoetry是需要注入的
    @Inject
    Poetry mPoetry;

    @Inject
    Gson mGson;

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other);

        MainComponent.getInstance()
                .inject(this);

        initView();
    }

    private void initView() {
        mTextView = (TextView) findViewById(R.id.tv_poetry);
        String json = mGson.toJson(mPoetry);
        String text = json + ",mPoetry:"+mPoetry;
        mTextView.setText(text);
    }
}

我們順便也把MainComponent改成抽象類的形式,并添加返回MainComponent單例的方法,對應添加MainActivity跳轉到OtherActivity的方法.

@Component(modules = {MainModule.class,PoetryModule.class})
public abstract class MainComponent {

    /**
     * 需要用到這個連接器的對象,就是這個對象里面有需要注入的屬性
     * (被標記為@Inject的屬性)
     * 這里inject表示注入的意思,這個方法名可以隨意更改,但建議就
     * 用inject即可。
     */
    abstract void inject(MainActivity activity);

    abstract void inject(OtherActivity activity);

    private static MainComponent sComponent;
    public static MainComponent getInstance(){
        if (sComponent == null){
            sComponent = DaggerMainComponent.builder().build();
        }
        return sComponent;
    }
}

public class MainActivity extends AppCompatActivity {

    //添加@Inject注解,表示這個mPoetry是需要注入的
    @Inject
    Poetry mPoetry;

    @Inject
    Gson mGson;

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 使用Dagger2生成的類 生成組件進行構造,并注入
        MainComponent.getInstance()
                .inject(this);

        initView();
    }

    private void initView() {
        mTextView = (TextView) findViewById(R.id.tv_poetry);
        String json = mGson.toJson(mPoetry);
        String text = json + ",mPoetry:"+mPoetry;
        mTextView.setText(text);

        findViewById(R.id.open).setOnClickListener(view ->
                startActivity(new Intent(this,OtherActivity.class)));
    }
}

運行結果如下:

運行結果
運行結果
運行結果
運行結果

可以看到,調用同一個MainComponent實例多次注入的時候每次都重新生成Poetry實例,有時候我們需要只希望生成一個共用實例的時候應該怎么辦呢,這里我們就需要用到Dagger2的@Scope屬性了,Scope是作用域的意思,我們先自定義一個@Scope注解:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PoetryScope {
}

同時在Module與Component加上這個自定義Scope:

@PoetryScope
@Component(modules = {MainModule.class,PoetryModule.class})
public abstract class MainComponent {
    /**
     * 需要用到這個連接器的對象,就是這個對象里面有需要注入的屬性
     * (被標記為@Inject的屬性)
     * 這里inject表示注入的意思,這個方法名可以隨意更改,但建議就
     * 用inject即可。
     */
    abstract void inject(MainActivity activity);

    abstract void inject(OtherActivity activity);

    private static MainComponent sComponent;
    public static MainComponent getInstance(){
        if (sComponent == null){
            sComponent = DaggerMainComponent.builder().build();
        }
        return sComponent;
    }
}

@Module
public class PoetryModule {

    // 這個方法需要一個String參數,在Dagger2注入中,這些參數也是注入形式的,也就是
    // 要有其他對方提供參數poems的生成,不然會造成編譯出錯
    @PoetryScope
    @Provides
    public Poetry providePoetry(String poems){
        return new Poetry(poems);
    }

    // 這里提供了一個生成String的方法,在這個Module里生成Poetry實例時,會查找到這里
    // 可以為上面提供String類型的參數
    @Provides
    public String providePoems(){
        return "只有意志堅強的人,才能到達彼岸";
    }
}

重新運行:


運行結果
運行結果
運行結果
運行結果

這時你會發現這兩個Poetry實例是同一個實例來的,通過實現自定義@Scope注解,標記當前生成對象的使用范圍,標識一個類型的注射器只實例化一次,在同一個作用域內,只會生成一個實例,然后在此作用域內共用一個實例。這樣看起來很像單例模式,我們可以查看@Singleton其實就是@Scope的一個默認實現而已。當然,你得是同一個Component對象來生成,這點我們應該可以理解的吧。
我們可以通過自定義Scope來組織Component的作用域,使得每個Component的作用域清晰明了,各施其職。

組織Component

我們在一個項目之中不可能只使用一個Component連接器來注入對象完成注入工作,一般除了一個全局的ApplicationComponent之外,還有一些作用域在Activity/Fragment的Component,Component之間存在依賴關系與從屬關系。如果我們已經創建好了一個全局的ApplicationComponent,然后其它的Component剛好需要ApplicationComponent里面的一個全局屬性,想要與ApplicationComponent共享同一個實例,這時就需要用到依賴關系了。

依賴方式

一個Component可以依賴一個或多個Component,并拿到被依賴Component暴露出來的實例,Component的dependencies屬性就是確定依賴關系的實現。
這里的有點像數學里面的交集方式,被依賴的Component主動暴露對象給二者共享,如我們在ApplicationModule提供了一個全局的Gson對象,我們想要提供給其他Component時,要在ApplicationComponent顯式的提供一個接口:

@Module
public class ApplicationModule {

    /**
     * @Provides 注解表示這個方法是用來創建某個實例對象的,這里我們創建返回Gson對象
     * 方法名隨便,一般用provideXXX結構
     * @return 返回注入對象
     */
    @Singleton
    @Provides
    public Gson provideGson(){
        return new Gson();
    }
}

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

    Gson getGson();// 暴露Gson對象接口
}

并在自定義的MainApplication中初始化它,更改MainComponent:

public class MainApplication extends Application {

    private ApplicationComponent mApplicationComponent;
    private static MainApplication sApplication;

    public static MainApplication getInstance() {
        return sApplication;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        sApplication = this;

        mApplicationComponent = DaggerApplicationComponent.builder().build();
    }

    public ApplicationComponent getApplicationComponent() {
        return mApplicationComponent;
    }
}

//這里表示Component會從MainModule類中拿那些用@Provides注解的方法來生成需要注入的實例
@PoetryScope
@Component(dependencies = ApplicationComponent.class, modules = {MainModule.class,PoetryModule.class})
public abstract class MainComponent {

    /**
     * 需要用到這個連接器的對象,就是這個對象里面有需要注入的屬性
     * (被標記為@Inject的屬性)
     * 這里inject表示注入的意思,這個方法名可以隨意更改,但建議就
     * 用inject即可。
     */
    abstract void inject(MainActivity activity);

    abstract void inject(OtherActivity activity);

    private static MainComponent sComponent;
    public static MainComponent getInstance(){
        if (sComponent == null){
            sComponent = DaggerMainComponent.builder()
                    .applicationComponent(MainApplication.getInstance()
                    .getApplicationComponent())
                    .build();
        }
        return sComponent;
    }
}

這樣就達到了MainComponent依賴ApplicationComponent。并且這里需要注意的是,MainComponent的作用域不能和ApplicationComponent的作用域一樣,否則會報錯,一般來講,我們應該對每個Component都定義不同的作用域。

包含方式(從屬方式)@SubComponent

如果我們需要父組件全部的提供對象,這時我們可以用包含方式而不是用依賴方式,相比于依賴方式,包含方式不需要父組件顯式顯露對象,就可以拿到父組件全部對象。且SubComponent只需要在父Component接口中聲明就可以了。添加多一個AActivity,AComponent:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface AScope {
}

@Module
public class AModule {

    @AScope
    @Provides
    public Poetry getPoetry(){
        return new Poetry("萬物美好");
    }
}

@AScope
@Subcomponent(modules = AModule.class)
public interface AComponent {
    void inject(AActivity activity);
}

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

    Gson getGson();// 暴露Gson對象接口

    //AComponent plus();
    AComponent plus(AModule module);//添加聲明
}


public class MainApplication extends Application {

    private ApplicationComponent mApplicationComponent;
    private AComponent mAComponent;
    private static MainApplication sApplication;

    public static MainApplication getInstance() {
        return sApplication;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        sApplication = this;

        mApplicationComponent = DaggerApplicationComponent.builder().build();
    }

    public ApplicationComponent getApplicationComponent() {
        return mApplicationComponent;
    }

    public AComponent getAComponent() {
        if (mAComponent == null){
            mAComponent = mApplicationComponent.plus(new AModule());
        }
        return mAComponent;
    }
}

public class AActivity extends AppCompatActivity {
    TextView mTextView;

    @Inject
    Gson mGson;

    @Inject
    Poetry mPoetry;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);

        MainApplication.getInstance()
                .getAComponent()
                .inject(this);

        mTextView = (TextView) findViewById(R.id.text);
        String text = mPoetry.getPoems()+",mPoetry:"+mPoetry+(mGson == null ? "Gson沒被注入" : "Gson已經被注入");
        mTextView.setText(text);
    }
}

最后我們在OtherActivity中添加一個按鈕跳轉到AActivity,運行結果如下:


運行結果
運行結果

@Qualifier

假如在上面的AActivity里面我們想要注入兩個不同的Poetry(指peoms不一樣)實例,我們可以在AModule下添加多一個生成Poetry的方法:

@Module
public class AModule {

    @AScope
    @Provides
    public Poetry getPoetry(){
        return new Poetry("萬物美好");
    }
    
    @AScope
    @Provides
    public Poetry getOtherPoetry(){
        return new Poetry("我在中間");
    }
}

但是直接這樣做Dagger2是無法區分調用哪個方法生成Poetry實例的,這個時候就需要自定義@Qualifier限定符來匹配注入方法了,添加一個自定義Qualifier并修AMoudule,AActivity:

@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PoetryQualifier {
    String value() default "";
}

@Module
public class AModule {

    @PoetryQualifier("A")
    @AScope
    @Provides
    public Poetry getPoetry(){
        return new Poetry("萬物美好");
    }

    @PoetryQualifier("B")
    @AScope
    @Provides
    public Poetry getOtherPoetry(){
        return new Poetry("我在中間");
    }
}

public class AActivity extends AppCompatActivity {
    TextView mTextView;

    @Inject
    Gson mGson;

    // 匹配Module中同樣注解的方法
    @PoetryQualifier("A")
    @Inject
    Poetry mPoetry;
    
    // 匹配Module中同樣注解的方法
    @PoetryQualifier("B")
    @Inject
    Poetry mPoetryB;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);

        MainApplication.getInstance()
                .getAComponent()
                .inject(this);

        mTextView = (TextView) findViewById(R.id.text);
        String text = mPoetry.getPoems()+",mPoetryA:"+mPoetry+
                mPoetryB.getPoems()+",mPoetryB:"+mPoetryB+
                (mGson == null ? "Gson沒被注入" : "Gson已經被注入");
        mTextView.setText(text);
    }
}

重新編譯運行:


運行結果
運行結果

而Dagger2已經默認幫我們實現了一個@Named:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

跟我們自定義的PoetryQualifier其實是一樣的。

后記

這篇是我參考了其他文章之后自己又重新總結一遍的,錯誤之處請幫忙指出,大家一起進步。除了以上常用到的注解之外,Dagger還提供了其他一些注解,如Set,Map類的注解,具體可以參考以下文章。

參考

Dagger2圖文完全教程
Google官方MVP+Dagger2架構詳解【從零開始搭建android框架系列(6)】
Android:dagger2讓你愛不釋手-基礎依賴注入框架篇
Android:dagger2讓你愛不釋手-重點概念講解、融合篇
Android:dagger2讓你愛不釋手-終結篇
Android:Dagger2學習之由淺入深

Demo地址

https://github.com/EvilBT/-Dagger2Demo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內容