Android |《看完不忘系列》之dagger

嗨谤草,我是哈利迪~《看完不忘系列》將以從樹干到細(xì)枝的思路分析一些技術(shù)框架犹褒,本文將對開源項(xiàng)目dagger進(jìn)行介紹。

本文約3800字渐行,閱讀大約10分鐘轰坊。

Dagger源碼基于最新版本2.28.3

image

背景

依賴注入(Dependency Injection,DI)遵循控制反轉(zhuǎn)(Inversion of Control祟印,IoC)原則肴沫,簡單來說就是創(chuàng)建對象時(shí)給對象傳入依賴,通過傳入不同實(shí)例來實(shí)現(xiàn)不同行為(控制)蕴忆,比如常見的構(gòu)造方法和setter都叫注入颤芬。

簡單概括一下谷歌的造車?yán)踝?/a>,

一套鹅、不注入站蝠,由Car類自己創(chuàng)建依賴的Engine實(shí)例,當(dāng)需要替換汽車引擎時(shí)卓鹿,需要修改Car類菱魔,違背了開放封閉原則,

class Car {
    private Engine engine = new Engine();
}

二减牺、手動依賴注入豌习,如構(gòu)造方法和setter,當(dāng)需要替換汽車引擎時(shí)拔疚,傳入不同的引擎實(shí)現(xiàn)(如ElectricEngine extends Engine)即可肥隆,無需修改Car類,

//1.構(gòu)造方法注入
class Car {
    private final Engine engine;
    
    public Car(Engine engine) {
        this.engine = engine;
    }
}

//2.setter注入
class Car {
    private Engine engine;

    public void setEngine(Engine engine) {
        this.engine = engine;
    }
}

三稚失、隨著汽車配件變多栋艳,依賴注入的代碼也會越來越多,如汽車需要引擎句各、輪胎吸占,引擎又需要?dú)飧缀突鸹ㄈ邕叮@樣一層層下去注入代碼成指數(shù)級上升,出現(xiàn)了大量創(chuàng)建對象的樣板代碼矾屯,

image
class Car {
    private final Engine engine;//引擎
    private final Wheel wheel;//輪胎

    public Car(Engine engine,Wheel wheel) {
        this.engine = engine;
        this.wheel = wheel;
    }
}

class Engine {
    private final Cylinder cylinder;//氣缸
    private final SparkPlug sparkPlug;//火花塞

    public Car(Cylinder cylinder,SparkPlug sparkPlug) {
        this.cylinder = cylinder;
        this.sparkPlug = sparkPlug;
    }
}

void main(){
    //為了造輛車兼蕊,要new出一堆對象,還要構(gòu)造方法注入件蚕,套娃既視感
    Cylinder cylinder = new Cylinder();
    SparkPlug sparkPlug = new SparkPlug();
    Engine engine = new Engine(cylinder,sparkPlug);
    Wheel wheel = new Wheel();
    Car car = new Car(engine,wheel);
}

從代碼可以看出有兩大痛點(diǎn)孙技,一是要?jiǎng)?chuàng)建很多對象,二是對象的創(chuàng)建過程還需有序排作,即要先有氣缸和火花塞才能造引擎牵啦,先有引擎和輪胎才能造車,這樣使用方還需花時(shí)間了解配件間的依賴關(guān)系妄痪,很是不便哈雏。

于是就有了一些庫來實(shí)現(xiàn)自動依賴注入,有兩個(gè)實(shí)現(xiàn)思路(koin的實(shí)現(xiàn)以后再聊~)衫生,

  1. 一是運(yùn)行期反射連接依賴項(xiàng)裳瘪,編譯影響小,但運(yùn)行慢
  2. 二是編譯期就連接依賴項(xiàng)障簿,創(chuàng)建輔助類需要額外的io和編譯耗時(shí)盹愚,會拖慢編譯速度,但運(yùn)行快

像Android內(nèi)存和算力都有限的終端設(shè)備站故,dagger當(dāng)然是選擇思路2啦。dagger通過注解標(biāo)記對象創(chuàng)建姿勢毅舆、依賴關(guān)系西篓、作用域等信息,在編譯期創(chuàng)建輔助類憋活,從而實(shí)現(xiàn)自動依賴注入岂津。不過dagger的上手成本略高,谷歌后來又推出了Hilt悦即,旨在讓我們用得舒心吮成,

Hilt 是推薦用于在 Android 中實(shí)現(xiàn)依賴項(xiàng)注入的 Jetpack 庫。Hilt 通過為項(xiàng)目中的每個(gè) Android 類提供容器并自動為您管理其生命周期辜梳,定義了一種在應(yīng)用中執(zhí)行 DI 的標(biāo)準(zhǔn)方法粱甫。

Hilt 在熱門 DI 庫 Dagger 的基礎(chǔ)上構(gòu)建而成,因而能夠受益于 Dagger 提供的編譯時(shí)正確性作瞄、運(yùn)行時(shí)性能茶宵、可伸縮性和 Android Studio 支持。

-- 谷歌

Hilt就先放一放宗挥,下面我們先開始dagger之旅吧~

補(bǔ):關(guān)于手動注入的痛點(diǎn)乌庶,可以看下谷歌的手動依賴項(xiàng)注入(看完或許能更好的理解dagger的設(shè)計(jì))种蝶。

樹干

簡單使用

依賴,

implementation 'com.google.dagger:dagger:2.28.3'
annotationProcessor 'com.google.dagger:dagger-compiler:2.28.3'

@Inject和@Component

@Inject標(biāo)記實(shí)例的創(chuàng)建姿勢瞒大,汽車和引擎類螃征,

class Car {
    private final Engine mEngine;

    @Inject //告訴dagger如何創(chuàng)建Car
    public Car(Engine engine) {
        mEngine = engine;
    }

    public void start() {
        mEngine.start();
    }
}

class Engine {
    @Inject //告訴dagger如何創(chuàng)建Engine
    public Engine() {
    }

    public void start() {
        Log.e("哈利迪", "引擎發(fā)動,前往秋名山");
    }
}

這樣dagger就能生成類似 new Car(new Engine()) 的代碼來創(chuàng)建實(shí)例透敌,

@Component標(biāo)記所要?jiǎng)?chuàng)建的實(shí)例有哪些会傲,如在造車圖紙(接口)里聲明要造車,

@Component //告訴dagger要造啥
interface CarGraph {
    //造車
    Car makeCar();
}

make一下項(xiàng)目拙泽,生成Car_Factory淌山、Engine_Factory和DaggerCarGraph三個(gè)類,在Activity中使用顾瞻,

class DaggerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dagger);
        //得到造車圖紙
        CarGraph carGraph = DaggerCarGraph.create();
        //造出一輛汽車
        Car car = carGraph.makeCar();
        //引擎發(fā)動
        car.start();
    }
}

注入姿勢二泼疑,還可以直接把汽車注入給Activity,

@Component //告訴dagger要造啥
public interface CarGraph {
    //造車
    Car makeCar();

    //新增:告訴dagger有個(gè)Activity要注入
    void inject(DaggerActivity activity);
}

make一下荷荤,這時(shí)會多出一個(gè)類DaggerActivity_MembersInjector(成員注入器)退渗,我們后面再看,在Activity中蕴纳,

class DaggerActivity extends AppCompatActivity {
    //向Activity注入汽車
    @Inject
    Car mCar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dagger);
        //得到造車圖紙
        CarGraph carGraph = DaggerCarGraph.create();
        //告訴dagger有個(gè)Activity要注入
        carGraph.inject(this);
        //此時(shí)会油,mCar已被dagger自動賦值,我們可以直接發(fā)動引擎
        mCar.start();
    }
}

不管是哪種姿勢古毛,都可以看出翻翩,Activity只管提車,無需關(guān)心造車內(nèi)部的細(xì)節(jié)(用了什么引擎)稻薇,這樣以后要換引擎了嫂冻,Activity和Car也不用改動代碼。

@Module和@Binds

那么問題來了塞椎,通常我們都是面向接口編程桨仿,現(xiàn)在想把Engine換成IEngine接口咋整呢?因?yàn)槲矣袃煞N引擎案狠,分別是汽油車的GasEngine服傍,和電動車的ElectricEngine,接口沒有構(gòu)造方法怎么注入骂铁?此時(shí)@Module和@Binds注解就派上用場了吹零。

定義引擎接口和實(shí)現(xiàn)類,

interface IEngine {
    public void start();
}

class GasEngine implements IEngine {//汽油引擎
    @Inject
    public GasEngine() {
    }

    @Override
    public void start() {
        Log.e("哈利迪", "汽油 引擎發(fā)動从铲,前往秋名山");
    }
}

class ElectricEngine implements IEngine {//電動引擎
    @Inject
    public ElectricEngine() {
    }

    @Override
    public void start() {
        Log.e("哈利迪", "電動 引擎發(fā)動瘪校,前往秋名山");
    }
}

然后Car我們不要了,新寫一個(gè)汽車類NewCar,讓他依賴于接口IEngine而非類阱扬,

class NewCar {
    //依賴于接口
    private final IEngine mEngine;

    @Inject //告訴dagger如何創(chuàng)建NewCar
    public NewCar(IEngine engine) {
        mEngine = engine;
    }

    public void start() {
        mEngine.start();
    }
}

下面有請@Module和@Binds登場泣懊!建一個(gè)抽象類GasEngineModule,表示汽油引擎模塊麻惶,用于提供汽油引擎馍刮,

@Module
abstract class GasEngineModule {//汽油引擎模塊
    @Binds
    abstract IEngine makeGasEngine(GasEngine engine);
}

然后在造車圖紙CarGraph里支持一下NewCar的創(chuàng)建,把Module引入Component窃蹋,

@Component(modules = {GasEngineModule.class})
//引入汽油引擎模塊
public interface CarGraph {
    //...

    //造新車
    NewCar makeNewCar();
}

Activity使用卡啰,

//DaggerActivity.java
void onCreate(Bundle savedInstanceState) {
    CarGraph carGraph = DaggerCarGraph.create();
    NewCar newCar = carGraph.makeNewCar();
    newCar.start();
}

搞定~

image

這時(shí)如果想要把NewCar換成電動引擎,直接依賴新的模塊即可警没,新增模塊ElectricEngineModule匈辱,用于提供電動引擎,

@Module
public abstract class ElectricEngineModule {//電動引擎模塊
    @Binds
    abstract IEngine makeElectricEngine(ElectricEngine engine);
}

GasEngineModule模塊換成ElectricEngineModule杀迹,

@Component(modules = {ElectricEngineModule.class})//替換
public interface CarGraph {
    //...
}

運(yùn)行就能得到電動車了亡脸,可見這個(gè)過程我們不需要修改NewCar就能換掉引擎(偷梁換柱)。

@IntoMap和@StringKey

又有一天树酪,突然想開雙混動汽車了咋整浅碾,就是說汽油引擎和電動引擎我全都要,我們發(fā)現(xiàn)@Component的modules可以傳入數(shù)組续语,那試試把兩個(gè)引擎模塊都傳進(jìn)去垂谢,

@Component(modules = {GasEngineModule.class, ElectricEngineModule.class})
public interface CarGraph {
    //...
}

make一下,不用想也知道會出錯(cuò)疮茄,NewCar只有一個(gè)依賴IEngine接口滥朱,這時(shí)dagger已經(jīng)不知道,造NewCar到底要傳入哪個(gè)引擎了娃豹,

image

那怎么辦焚虱?支持多綁定的@IntoMap和@StringKey登場了。

IntoMap表示會把這個(gè)Module存進(jìn)map里懂版,StringKey表示Module名字舀射,這個(gè)名字也會作為map的key炬丸,

@Module
abstract class GasEngineModule {//汽油引擎模塊
    @Binds
    @IntoMap
    @StringKey("Gas")
    abstract IEngine makeGasEngine(GasEngine engine);
}

@Module
abstract class ElectricEngineModule {//電動引擎模塊
    @Binds
    @IntoMap
    @StringKey("Electric")
    abstract IEngine makeElectricEngine(ElectricEngine engine);
}

然后修改一下NewCar,此時(shí)不再只是依賴一個(gè)引擎接口IEngine了误趴,而是一組引擎薇芝,

class NewCar {
    //我現(xiàn)在是混動車了蓬抄,要用map接收引擎
    private final Map<String, IEngine> mEngineMap;

    @Inject //告訴dagger如何創(chuàng)建NewCar
    public NewCar(Map<String, IEngine> engineMap) {
        mEngineMap = engineMap;
    }

    public void start() {
        //雙混動走起~
        for (Map.Entry<String, IEngine> entry : mEngineMap.entrySet()) {
            entry.getValue().start();
            //汽油 引擎發(fā)動,前往秋名山
            //電動 引擎發(fā)動夯到,前往秋名山
        }
    }
}

Activity代碼不用改動嚷缭,雙混動車就造好了。

image

@Singleton

如果想要給雙混動NewCar限量全球一款,可以用@Singleton指定為單例阅爽,

@Singleton //單例路幸,我現(xiàn)在是限量款混動車
public class NewCar {
    //...
}

@Singleton //造車圖紙也需跟著單例
@Component(modules = {GasEngineModule.class, ElectricEngineModule.class})
public interface CarGraph {
    //...
}

Activity運(yùn)行,

//DaggerActivity.java
void onCreate(Bundle savedInstanceState) {
    CarGraph carGraph = DaggerCarGraph.create();
    NewCar newCar = carGraph.makeNewCar();
    newCar.start();
    NewCar newCar2 = carGraph.makeNewCar();
    newCar2.start();
    //newCar和newCar2是同一個(gè)實(shí)例
    Log.e("哈利迪", newCar.hashCode() + "," + newCar2.hashCode());
}

dagger的使用就先聊到這啦付翁,相信對dagger也已經(jīng)有了初步認(rèn)識简肴,還有些注解沒講到,比如:

@Provides:當(dāng)我們沒法用@Inject來標(biāo)記實(shí)例的創(chuàng)建姿勢時(shí)百侧,可以用@Module和@Provides來提供實(shí)例砰识,比如Retrofit是三方庫的類我們沒法標(biāo)記其構(gòu)造方法,則可以用Provides提供佣渴,

@Module
public class NetworkModule {
    @Provides
    public Retrofit provideRetrofit() {
        return new Retrofit.Builder()
            .baseUrl("xxx")
            .build();
    }
}

@Scope:作用域 ...

@Subcomponent:子組件...

注解還有很多辫狼,準(zhǔn)備放到細(xì)枝篇來寫了...??

實(shí)現(xiàn)原理

dagger編譯期解析注解創(chuàng)建輔助類的過程就不分析了,我們直接看他生成的輔助類辛润,

image

注:一開始寫接口名字時(shí)膨处,用造車圖紙CarGraph而不是造車廠CarFactory,是為了避免和dagger的生成類搞混频蛔,用CarGraph有幾何圖的寓意灵迫,可以理解成造車藍(lán)圖(PPT),讓我們一起晦溪,為夢想窒息...

我們看到DaggerCarGraph瀑粥,他實(shí)現(xiàn)了我們的CarGraph接口,

class DaggerCarGraph implements CarGraph {
    //Provider三圆,提供多引擎map狞换,<引擎名字、引擎實(shí)例>
    private Provider<Map<String, IEngine>> mapOfStringAndIEngineProvider;
    //Provider舟肉,提供NewCar
    private Provider<NewCar> newCarProvider;

    private DaggerCarGraph() {
        initialize();
    }

    public static Builder builder() {
        return new Builder();
    }

    public static CarGraph create() {
        //用builder創(chuàng)建DaggerCarGraph實(shí)例
        return new Builder().build();
    }

    private void initialize() {
        //創(chuàng)建提供汽油引擎的Provider和電動引擎的Provider
        //用put將他們存起來修噪,合并成一個(gè)提供多引擎map的Provider
        this.mapOfStringAndIEngineProvider = MapFactory.<String, IEngine>builder(2)
            .put("Gas", (Provider) GasEngine_Factory.create())
            .put("Electric", (Provider) ElectricEngine_Factory.create())
            .build();
        //把提供多引擎map的Provider傳入NewCar_Factory,
        //然后用DoubleCheck包一層路媚,他會加鎖黄琼、處理單例邏輯
        this.newCarProvider = DoubleCheck.provider(
            NewCar_Factory.create(mapOfStringAndIEngineProvider));
    }

    @Override
    public Car makeCar() {
        //1. 老的造車:姿勢一,用makeCar直接造
        return new Car(new Engine());
    }

    @Override
    public void inject(DaggerActivity activity) {
        //2. 老的造車:姿勢二整慎,用@Inject注入 ↓
        injectDaggerActivity(activity);
    }

    private DaggerActivity injectDaggerActivity(DaggerActivity instance) {
        //2. 老的造車:姿勢二脏款,先創(chuàng)建后注入
        //實(shí)例的創(chuàng)建也用makeCar,如果我們接口沒有定義這個(gè)方法裤园,dagger會生成一個(gè)功能一樣的getCar
        DaggerActivity_MembersInjector.injectMCar(instance, makeCar());
        return instance;
    }

    @Override
    public NewCar makeNewCar() {
        //3. 新的造車撤师,從Provider獲取
        return newCarProvider.get();
    }

    public static final class Builder {
        private Builder() {
        }
        public CarGraph build() {
            return new DaggerCarGraph();
        }
    }
}
  1. 老的造車:姿勢一,用makeCar直接造

在造老車Car時(shí)拧揽,姿勢一是直接調(diào)接口的makeCar方法來造剃盾,實(shí)現(xiàn)就是簡單的new出實(shí)例腺占。

  1. 老的造車:姿勢二,用@Inject注入

姿勢二用@Inject注入實(shí)例痒谴,可見他也是先調(diào)makeCar()得到實(shí)例衰伯,然后調(diào)DaggerActivity_MembersInjector.injectMCar進(jìn)行注入,

//DaggerActivity_MembersInjector.java
@InjectedFieldSignature("com.holiday.srccodestudy.dagger.DaggerActivity.mCar")
public static void injectMCar(DaggerActivity instance, Car mCar) {
    //引用DaggerActivity的成員闰歪,為其賦值嚎研,可見mCar不能聲明為private
    instance.mCar = mCar;
}
  1. 新的造車,從Provider獲取

在造新車NewCar時(shí)库倘,是從Provider獲取的临扮,跟進(jìn)newCarProvider.get(),如果使用了單例@Singleton教翩,NewCar_Factory會被DoubleCheck包一層杆勇,DoubleCheck會加鎖和處理單例邏輯,我們直接看NewCar_Factory的get就行了饱亿,

//Factory<T> extends Provider<T>
class NewCar_Factory implements Factory<NewCar> {
    private final Provider<Map<String, IEngine>> engineMapProvider;

    public NewCar_Factory(Provider<Map<String, IEngine>> engineMapProvider) {
        this.engineMapProvider = engineMapProvider;
    }

    @Override
    public NewCar get() {
        //通過engineMapProvider獲取多引擎map
        return newInstance(engineMapProvider.get());
    }

    public static NewCar_Factory create(Provider<Map<String, IEngine>> engineMapProvider) {
        return new NewCar_Factory(engineMapProvider);
    }

    public static NewCar newInstance(Map<String, IEngine> engineMap) {
        //new出新車實(shí)例
        return new NewCar(engineMap);
    }
}

看下多引擎map又是如何獲取的蚜退,engineMapProvider.get(),

//MapFactory.java
//MapFactory extends AbstractMapFactory implements Factory
public Map<K, V> get() {
    //創(chuàng)建新的LinkedHashMap
    Map<K, V> result = newLinkedHashMapWithExpectedSize(contributingMap().size());
    //遍歷已存入的引擎彪笼,存進(jìn)新map
    for (Entry<K, Provider<V>> entry : contributingMap().entrySet()) {
        result.put(entry.getKey(), entry.getValue().get());
    }
    //返回不可修改的map
    return unmodifiableMap(result);
}

至此钻注,dagger的各生產(chǎn)線流程就分析完了,

image

細(xì)枝

@Scope作用域配猫、@Subcomponent子組件幅恋、還有SPI(Service Provider Interface)、grpc(谷歌的遠(yuǎn)程過程調(diào)用框架)等泵肄,都留到細(xì)枝篇來寫啦捆交。

使用場景

那dagger在Android中有哪些用武之地?首先是從架構(gòu)角度腐巢,在谷歌示例中品追,結(jié)合了Activity、偽ViewModel冯丙、Repository肉瓦、DataSource和Retrofit來使用dagger,(還沒用過胃惜,不知道香不香风宁、坑多不多,靠屏幕前的大佬們反饋了~)

image

然后我們在項(xiàng)目中的一些場景蛹疯,是從業(yè)務(wù)角度切入,在個(gè)別復(fù)雜度較高的業(yè)務(wù)線上單獨(dú)使用dagger热监。例如錢包業(yè)務(wù)捺弦,有大量實(shí)例大量頁面/視圖存在多對多關(guān)系,比如錢包Act需要錢包Api、錢包用戶信息Manager列吼;充值A(chǔ)ct需要支付Api幽崩、充值Service;銀行卡列表View需要銀行Service...像這種多對多寞钥、對象依賴關(guān)系雜亂無章的場景慌申,很適合用dagger來幫我們注入。

@PurseScope //錢包作用域
@Component(modules = PurseModule.class) //由PurseModule提供實(shí)例
public interface PurseComponent {

    //為act注入
    void inject(退款詳情Act xxxAct);
    void inject(銀行卡首頁Act xxxAct);
    void inject(錢包首頁Act xxxAct);
    void inject(錢包設(shè)置Act xxxAct);
    void inject(余額首頁Act xxxAct);
    //...

    //為view注入
    void inject(錢包首頁GridContainer xxxGridContainer);
    void inject(銀行卡列表View xxxView);

    //提供網(wǎng)絡(luò)調(diào)用
    支付Api xxxApi();

    //提供錢包用戶信息
    錢包用戶信息Manager xxxManager();

    //...
}

然后看到PurseModule理郑,用于提供各種實(shí)例蹄溉,可以new、手動單例您炉、工廠生產(chǎn)...

@Module
public class PurseModule {
    @Provides
    @PurseScope
    public 支付Api provide支付Api(...) {
        return new 支付Api(...);
    }

    @Provides
    @PurseScope
    public 用戶信息Manager provide用戶信息Manager(...) {
        return 用戶信息Manager.get(...);
    }

    @Provides
    @PurseScope
    public 充值Service provide充值Service(...) {
        return new 充值ServiceFactory.create(...);
    }

    //...
}

尾聲

簡單總結(jié)下dagger的優(yōu)缺點(diǎn)~

  • 優(yōu)勢:無反射柒爵、支持增量編譯
  • 缺點(diǎn):構(gòu)建過程需要而外的io和編譯時(shí)間、生成類會增大包體積赚爵、不夠好用棉胀、

后續(xù)計(jì)劃:dagger細(xì)枝、hilt冀膝、koin唁奢、順便看看spring上的注入@Autowired是怎么做的...學(xué)海無涯啊~

image

系列文章:

參考資料


歡迎關(guān)注原創(chuàng)技術(shù)公眾號:哈利迪ei

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窝剖,一起剝皮案震驚了整個(gè)濱河市麻掸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌枯芬,老刑警劉巖论笔,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異千所,居然都是意外死亡狂魔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門淫痰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來最楷,“玉大人,你說我怎么就攤上這事待错∽阉铮” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵火俄,是天一觀的道長犯建。 經(jīng)常有香客問我,道長瓜客,這世上最難降的妖魔是什么适瓦? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任竿开,我火速辦了婚禮,結(jié)果婚禮上玻熙,老公的妹妹穿的比我還像新娘否彩。我一直安慰自己,他們只是感情好嗦随,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布列荔。 她就那樣靜靜地躺著,像睡著了一般枚尼。 火紅的嫁衣襯著肌膚如雪贴浙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天姑原,我揣著相機(jī)與錄音悬而,去河邊找鬼。 笑死锭汛,一個(gè)胖子當(dāng)著我的面吹牛笨奠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播唤殴,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼般婆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了朵逝?” 一聲冷哼從身側(cè)響起蔚袍,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎配名,沒想到半個(gè)月后啤咽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡渠脉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年宇整,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芋膘。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鳞青,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出为朋,到底是詐尸還是另有隱情臂拓,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布习寸,位于F島的核電站胶惰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏霞溪。R本人自食惡果不足惜童番,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一精钮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剃斧,春花似錦、人聲如沸忽你。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽科雳。三九已至根蟹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間糟秘,已是汗流浹背简逮。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留尿赚,地道東北人散庶。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像凌净,于是被迫代替她去往敵國和親悲龟。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348