Android | dagger細(xì)枝篇

嗨列林,我是哈利迪~《看完不忘系列》之dagger(樹干篇)一文對dagger做了初步介紹,下面我們一起來瞅瞅dagger的一些細(xì)節(jié)杠氢。

本文約3.5k字,閱讀大約9分鐘续崖。

Dagger源碼基于最新版本2.28.3

目錄:

image

@Binds和@Provides區(qū)別

Binds和Provides都是搭配Module(模塊敲街,用于支持模塊化)使用的,Binds表示抽象和具體的綁定严望,作用在抽象方法上多艇,如下,

@Module
abstract class GasEngineModule {//汽油引擎模塊
    @Binds
    abstract IEngine makeGasEngine(GasEngine engine);
    //抽象的IEngine接口作為返回值像吻,具體的GasEngine作為入?yún)⒕颍纬山壎P(guān)系
    //當(dāng)有地方依賴了IEngine時(shí),這里可以為他提供GasEngine實(shí)例
}

為什么用抽象方法拨匆,因?yàn)檫@里我們要做的只是聲明綁定關(guān)系姆涩,dagger根據(jù)聲明就知道如何提供實(shí)例了,dagger不會調(diào)用這個(gè)方法或?yàn)樗删唧w實(shí)現(xiàn)惭每。

當(dāng)需要提供的實(shí)例不是一個(gè)而是一組時(shí)骨饿,Binds可以和集合注解:@IntoSet、@ElementsIntoSet台腥、@IntoMap一起使用宏赘,

@Module
abstract class GasEngineModule {//汽油引擎模塊
    @Binds
    @IntoMap //支持入map
    @StringKey("Gas") //使用IntoMap的話,還需要指定一個(gè)key
    abstract IEngine makeGasEngine(GasEngine engine);
}

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

key的指定也有多種類型黎侈,如@StringKey置鼻、@IntKey、@LongKey蜓竹、@ClassKey等。

而Provides就比較簡單了储藐,當(dāng)我們沒法用@Inject來標(biāo)記實(shí)例的創(chuàng)建姿勢時(shí)俱济,可以用@Provides來提供實(shí)例,比如Retrofit是三方庫的類我們沒法標(biāo)記其構(gòu)造方法钙勃,則可以用Provides提供蛛碌,

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

可見Binds用于聲明接口和實(shí)例的綁定關(guān)系,Provides用于直接提供實(shí)例辖源,他們都寫在可模塊化的Module里蔚携。

然后經(jīng)過Component指定modules,這些聲明就能被dagger找到了克饶,

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

@Subcomponent

Subcomponent即子組件酝蜒,可以將父組件的能力繼承過來,同時(shí)擴(kuò)展自己的能力》龋現(xiàn)在已經(jīng)有了@Component的CarGraph來造車亡脑,我們可以建一個(gè)@Subcomponent的WheelGraph來造輪胎,(雖是輪胎圖紙,但也有造車之心)

@Subcomponent //子組件霉咨,輪胎圖紙
public interface WheelGraph {
    Wheel makeWheel(); //造輪胎

    @Subcomponent.Factory //告知CarGraph如何創(chuàng)建WheelGraph
    interface Factory {
        WheelGraph create();
    }
}

下面要告知dagger WheelGraph是CarGraph的子組件蛙紫,需要提供Module,借助Module將子組件的類WheelGraph傳給subcomponents途戒,

@Module(subcomponents = WheelGraph.class)
public class WheelModule {
}

將Module添加到CarGraph坑傅,

@Component(modules = {WheelModule.class})
public interface CarGraph {
    //...

    //提供接口,讓使用者能借助Factory拿到WheelGraph實(shí)例
    WheelGraph.Factory wheelGraph();
}

使用喷斋,

//輪胎圖紙
WheelGraph wheelGraph = carGraph.wheelGraph().create();
//造輪胎
Wheel wheel = wheelGraph.makeWheel();

然后我們看下子組件是如何使用父組件的能力的唁毒,在WheelGraph里添加一個(gè)inject,

@Subcomponent
public interface WheelGraph {
    //...

    //告訴dagger誰要注入
    void inject(DaggerActivity activity);
}

把DaggerActivity里的 carGraph.inject(this) 換成 wheelGraph.inject(this)继准,發(fā)現(xiàn)mCar也能正常運(yùn)行枉证,

class DaggerActivity extends AppCompatActivity {
    @Inject
    Car mCar;
    void onCreate(Bundle savedInstanceState) {
        CarGraph carGraph = DaggerCarGraph.create();
        //carGraph.inject(this);
        WheelGraph wheelGraph = carGraph.wheelGraph().create();
        wheelGraph.inject(this);
        //正常發(fā)動
        mCar.start();
    }
}

可見wheelGraph把carGraph的造車能力也給繼承過來了。

我們看到生成的DaggerCarGraph類移必,里面有個(gè)內(nèi)部類WheelGraphImpl(子組件不會生成單獨(dú)的DaggerXXX類室谚,而是依附于父組件),

class WheelGraphImpl implements WheelGraph {
    
    @Override
    public Wheel makeWheel() {
        return new Wheel();
    }

    @Override
    public void inject(DaggerActivity activity) {
        injectDaggerActivity(activity);// ↓
    }

    private DaggerActivity injectDaggerActivity(DaggerActivity instance) {
        //跟父組件的注入邏輯一致崔泵,還調(diào)用了父組件的造車能力DaggerCarGraph.this.makeCar()
        DaggerActivity_MembersInjector.injectMCar(instance, DaggerCarGraph.this.makeCar());
        return instance;
    }
}

谷歌示例會形象些秒赤,ApplicationComponent和LoginComponent就是父與子的關(guān)系,LoginComponent自己管理LoginViewModel憎瘸,但同時(shí)又繼承了父組件的數(shù)據(jù)獲取能力入篮,

image

@Scope

Scope即作用域,是一種元注解幌甘,即作用于注解的注解潮售。@Singleton可以實(shí)現(xiàn)全局單例,當(dāng)父組件CarGraph標(biāo)記了@Singleton锅风,子組件WheelGraph就不能使用相同的@Singleton了酥诽,我們可以自定義注解。

@Scope //元注解
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope { //表示我想要一個(gè)Activity級別的作用域
}

使用皱埠,

@ActivityScope //使用自定義注解
public class Wheel {
}

@ActivityScope //使用自定義注解
@Subcomponent
public interface WheelGraph {
}

@Qualifier

Qualifier即限定符肮帐,也是一種元注解。當(dāng)有多個(gè)相同類型的實(shí)例要提供時(shí)边器,比如輪胎有最小和最大尺寸都是相同的int類型训枢,

public class Wheel {
    int minSize;
    int maxSize;

    @Inject
    public Wheel(int minSize, int maxSize) {
        this.minSize = minSize;
        this.maxSize = maxSize;
    }
}

WheelModule進(jìn)行提供,

@Module(subcomponents = WheelGraph.class)
public class WheelModule {
    @Provides
    static int minSize() {
        return 12;
    }

    @Provides
    static int maxSize() {
        return 23;
    }
}

這時(shí)dagger就會出錯(cuò)了忘巧,他不知道多個(gè)int的對應(yīng)關(guān)系恒界,需要自定義注解來指定,

@Qualifier //元注解
@Retention(RUNTIME)
public @interface MinSize {} //限定符注解

@Qualifier //元注解
@Retention(RUNTIME)
public @interface MaxSize {} //限定符注解

然后在Wheel和WheelModule都加上限定即可

public class Wheel {
    int minSize;
    int maxSize;

    @Inject
    public Wheel(@MinSize int minSize, @MaxSize int maxSize) {//使用自定義注解
        this.minSize = minSize;
        this.maxSize = maxSize;
    }
}

@Module(subcomponents = WheelGraph.class)
public class WheelModule {
    @Provides
    @MinSize //使用自定義注解
    static int minSize() {
        return 12;
    }

    @Provides
    @MaxSize //使用自定義注解
    static int maxSize() {
        return 23;
    }
}

SPI機(jī)制

dagger文檔里提到了SPI機(jī)制砚嘴,哈迪蹩腳的英文看得一頭霧水仗处,不過最近剛好看到了好友丙的文章Dubbo的SPI機(jī)制眯勾,做了個(gè)簡單的理解,SPI(Service Provider Interface婆誓,服務(wù)提供者接口)簡單來說吃环,就是先統(tǒng)一接口,然后按需擴(kuò)展自己的實(shí)現(xiàn)類洋幻,到了運(yùn)行的時(shí)候再去查找加載對應(yīng)的實(shí)現(xiàn)類郁轻,不過這里的實(shí)現(xiàn)類,我們集中放在一個(gè)事先約定好的地方文留。

比如MySQL好唯,

約定在 Classpath 下的 META-INF/services/ 目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件,然后文件里面記錄的是此 jar 包提供的具體實(shí)現(xiàn)類的全限定名燥翅。

這樣當(dāng)我們引用了某個(gè) jar 包的時(shí)候就可以去找這個(gè) jar 包的 META-INF/services/ 目錄骑篙,再根據(jù)接口名找到文件,然后讀取文件里面的內(nèi)容去進(jìn)行實(shí)現(xiàn)類的加載與實(shí)例化森书。

-- 敖丙

image

聲明好了實(shí)現(xiàn)類后靶端,運(yùn)行時(shí)就會有java自帶的ServiceLoader來解析文件,加載實(shí)現(xiàn)類(不過Dubbo為了優(yōu)化自建了一套)凛膏。

再比如我們更為熟悉的gradle-plugin開發(fā)杨名,也會約定一個(gè)地方來聲明實(shí)現(xiàn)類,在插件工程的resources/META-INF/gradle-plugins/目錄下建一個(gè)plugin_name.properties文件猖毫,聲明類的全名台谍,(參見butterknife

implementation-class=butterknife.plugin.ButterKnifePlugin

所以SPI簡單理解就是一種可擴(kuò)展的、提供實(shí)現(xiàn)類的約定機(jī)制吁断。那么SPI對dagger來說有啥用途呢趁蕊?

注意:dagger的SPI還是實(shí)驗(yàn)性的,隨時(shí)可變

我們看到dagger的spi包下的一個(gè)接口BindingGraphPlugin仔役,

//我是一個(gè)插件接口掷伙,可以插在dagger圖綁定(創(chuàng)建類連接依賴關(guān)系)的過程中
public interface BindingGraphPlugin {
    //為Dagger處理器遇到的每個(gè)有效根綁定圖調(diào)用一次。 可以使用diagnosticReporter報(bào)告診斷
    void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter);

    //使用Filer初始化此插件骂因,該Filer可用于基于綁定圖編寫Java或其他文件。 
    //在訪問任何圖形之前赃泡,此插件的每個(gè)實(shí)例將調(diào)用一次
    default void initFiler(Filer filer) {}

    //...
    default void initTypes(Types types) {}

    //...
    default void initElements(Elements elements) {}

    //...
    default void initOptions(Map<String, String> options) {}

    //返回此插件用于配置行為的注釋處理選項(xiàng)寒波。
    default Set<String> supportedOptions() {
        return Collections.emptySet();
    }

    //插件的唯一名稱,將在打印到Messager的診斷程序中使用升熊。 默認(rèn)情況下俄烁,使用插件的標(biāo)準(zhǔn)名稱
    default String pluginName() {
        return getClass().getCanonicalName();
    }
}

咦,BindingGraphPlugin是啥玩意级野?你想想页屠,再好好想想,他是不是長得很像我們開發(fā)注解處理器APT時(shí)做的一些事情,看下ButterKnifeProcessor辰企,

@AutoService(Processor.class)
//使用AutoService自動幫我們做SPI聲明风纠,
//他會創(chuàng)建META-INF/services/javax.annotation.processing.Processor文件,幫我們寫入注解處理器的類全名
public final class ButterKnifeProcessor extends AbstractProcessor {
    public void init(ProcessingEnvironment env) {}
    public Set<String> getSupportedOptions() {}
    public Set<String> getSupportedAnnotationTypes() {}
    public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {}
}

怎么樣牢贸,長得是不是還挺像竹观,那就很好理解了,注解處理器在構(gòu)建時(shí)插入一段邏輯來解析注解生成輔助類潜索,那dagger的BindingGraphPlugin就是在圖綁定階段插一段邏輯來做自定義處理嘛臭增。

image

dagger內(nèi)置了一些實(shí)現(xiàn)類,如DependencyCycleValidator用來檢測是否有循環(huán)依賴竹习、FailingPlugin用來生成錯(cuò)誤報(bào)告誊抛、InjectBindingValidator用于驗(yàn)證@Inject標(biāo)記的構(gòu)造方法...

如果有特殊需求,就可以自定義插件整陌,插入自己的邏輯拗窃,例如實(shí)現(xiàn)以下功能:

  • 添加項(xiàng)目特定的錯(cuò)誤和警告,例如android.content.Context的綁定必須具有@Qualifier或?qū)崿F(xiàn)DatabaseRelated的綁定必須是作用域
  • 生成額外的Java源文件
  • 將Dagger模型序列化為資源文件
  • 建立Dagger模型的可視化(可將綁定圖(依賴關(guān)系)轉(zhuǎn)成json/proto蔓榄,然后渲染成UI)

-- dagger

自定義插件使用@AutoService(BindingGraphPlugin.class)進(jìn)行SPI聲明并炮,例如dagger的一個(gè)TestPlugin

@AutoService(BindingGraphPlugin.class)
public final class TestPlugin implements BindingGraphPlugin {
    private Filer filer;//文件操作

    @Override
    public void initFiler(Filer filer) {
        this.filer = filer;
    }

    @Override
    public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {
        //定制邏輯...
    }
}

定義好插件后甥郑,dagger就會使用java自帶的ServiceLoader進(jìn)行查找和加載逃魄,在SpiModule中,

@Module
abstract class SpiModule {
    private SpiModule() {}

    @Provides
    @Singleton
    static ImmutableSet<BindingGraphPlugin> externalPlugins(
        @TestingPlugins Optional<ImmutableSet<BindingGraphPlugin>> testingPlugins,
        @ProcessorClassLoader ClassLoader processorClassLoader) {
        return testingPlugins.orElseGet(
            () ->
            ImmutableSet.copyOf(
                //ServiceLoader.load
                ServiceLoader.load(BindingGraphPlugin.class, processorClassLoader)));
    }
}

對ServiceLoader.load實(shí)現(xiàn)感興趣的讀者澜搅,可以看敖丙的文章Dubbo的SPI機(jī)制伍俘,實(shí)現(xiàn)不是很復(fù)雜。

延伸

延伸部分略枯燥勉躺,純屬個(gè)人裝B學(xué)習(xí)記錄用癌瘾,不感興趣可跳過~??

偷偷裝個(gè)B,哈迪在公司其實(shí)腳本和后端都寫過(當(dāng)然只是內(nèi)部的管理平臺饵溅,不可能是核心業(yè)務(wù)啦)妨退,所以分析技術(shù)時(shí)已經(jīng)習(xí)慣了各種發(fā)散和對比,越發(fā)覺得其實(shí)很多端的技術(shù)很是相通~看過Tangram系列的讀者應(yīng)該也見過TangramService項(xiàng)目蜕企,這還是用大學(xué)時(shí)搭的殼來寫的...

@Autowired注入

源碼基于spring 4.3.13疹味,spring boot 1.5.9

使用姿勢齿诉,

@RestController
public class ActivityController {
    @Autowired //自動注入
    private ActivityService activityService;
}

@Service
public class ActivityService {} //注意:因?yàn)榇a不多,所以就沒按規(guī)范抽成接口和Impl

那么activityService實(shí)例是如何自動注入的呢?

Spring會掃描@Component注解的Bean掏呼,然后AbstractAutowireCapableBeanFactory#populateBean負(fù)責(zé)注入bean的屬性鲜屏,他里邊調(diào)了ibp.postProcessPropertyValues风范,我們看到AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues,

//AutowiredAnnotationBeanPostProcessor.java
public PropertyValues postProcessPropertyValues() {
    //1.獲取Class中關(guān)于屬性注入相關(guān)注解的元數(shù)據(jù)
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    //2.完成屬性注入操作
    metadata.inject(bean, beanName, pvs);
    return pvs;
}

1.findAutowiringMetadata主要是做檢查和緩存聚唐,他調(diào)了buildAutowiringMetadata,跟進(jìn)腔召,

//AutowiredAnnotationBeanPostProcessor.java
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    LinkedList<InjectionMetadata.InjectedElement> elements = 
        new LinkedList<InjectionMetadata.InjectedElement>();
    //目標(biāo)類杆查,如我們的ActivityController
    Class<?> targetClass = clazz;
    do {
        //收集
        final LinkedList<InjectionMetadata.InjectedElement> currElements =
            new LinkedList<InjectionMetadata.InjectedElement>();
        //記錄有@Autowired,@Value等注解的field
        ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                AnnotationAttributes ann = findAutowiredAnnotation(field);
                if (ann != null) {
                    //...
                    currElements.add(new AutowiredFieldElement(field, required));
                }
            }
        });
        //記錄有@Autowired宴咧,@Value等注解的method
        ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {
            @Override
            public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                //...
                AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
                if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                    //...
                    currElements.add(new AutowiredMethodElement(method, required, pd));
                }
            }
        });
        //收集
        elements.addAll(0, currElements);
        //繼續(xù)往上查找父類
        targetClass = targetClass.getSuperclass();
    }while (targetClass != null && targetClass != Object.class);
    //返回包裝好的元數(shù)據(jù)
    return new InjectionMetadata(clazz, elements);
}

解析類的注解得到包裝好的元數(shù)據(jù)后根灯,就調(diào)用2.metadata.inject進(jìn)行注入,他又調(diào)了element.inject掺栅,我們直接看到AutowiredFieldElement#inject烙肺,

//AutowiredFieldElement
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
    //根據(jù)元數(shù)據(jù),解析屬性值氧卧,找到我們的ActivityService實(shí)例
    //DefaultListableBeanFactory.resolveDependency -> doResolveDependency -> 
    //findAutowireCandidates -> addCandidateEntry -> candidates.put -> 
    //descriptor.resolveCandidate -> AbstractBeanFactory.getBean -> getObjectForBeanInstance...
    value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
    //設(shè)置可訪問
    ReflectionUtils.makeAccessible(field);
    //將實(shí)例注入
    field.set(bean, value);
}

可見@Autowired的注入是基于反射來做的~

image

gRPC

RPC(Remote Procedure Call)是遠(yuǎn)程過程調(diào)用桃笙,后端進(jìn)行服務(wù)化后,通常會在一個(gè)主機(jī)上調(diào)用另一個(gè)主機(jī)提供的方法沙绝,比如獲取一個(gè)用戶收藏的商品搏明,用戶服務(wù)器就會調(diào)用商品服務(wù)器的方法,什么闪檬?居然還能調(diào)用遠(yuǎn)程主機(jī)應(yīng)用的方法星著?其實(shí)可以這么理解,就是基于一定協(xié)議和數(shù)據(jù)格式進(jìn)行通信粗悯,比如http+json/proto虚循,但是直接這么用不方便,就封裝成接口方法的方式來調(diào)用了样傍,看起來是調(diào)了一個(gè)方法横缔,其實(shí)內(nèi)部進(jìn)行了遠(yuǎn)程通信。像Dubbo衫哥、Spring Cloud茎刚、gRPC就是一些遠(yuǎn)程過程調(diào)用的框架〕贩辏可以類比Android的跨進(jìn)程通信IPC膛锭,只不過RPC不僅跨了進(jìn)程,還跨了主機(jī)蚊荣。

gRPC是谷歌開源的高性能遠(yuǎn)程過程調(diào)用框架初狰,dagger文檔的gRPC就寥寥幾句,還不完善妇押,看不出個(gè)所以然跷究,就先不看了姓迅,簡單了解下就行...

尾聲

由于哈迪沒有dagger實(shí)戰(zhàn)經(jīng)驗(yàn)敲霍,寫起來還是有點(diǎn)吃力的俊马,寫著寫著就有種搬文檔的感覺...而且dagger的使用程度貌似也不怎么高,真要用得爐火純青肩杈,可能還得要靠深刻理解業(yè)務(wù)柴我,能夠發(fā)掘契合度極高的業(yè)務(wù)高手了吧...怎么說呢,趁年輕好奇心足夠強(qiáng)烈扩然,多學(xué)點(diǎn)還是好的艘儒,我們下期見~??

系列文章:

參考資料


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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市夫偶,隨后出現(xiàn)的幾起案子界睁,更是在濱河造成了極大的恐慌,老刑警劉巖兵拢,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翻斟,死亡現(xiàn)場離奇詭異,居然都是意外死亡说铃,警方通過查閱死者的電腦和手機(jī)访惜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腻扇,“玉大人债热,你說我怎么就攤上這事∮卓粒” “怎么了窒篱?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蚓峦。 經(jīng)常有香客問我舌剂,道長,這世上最難降的妖魔是什么暑椰? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任霍转,我火速辦了婚禮,結(jié)果婚禮上一汽,老公的妹妹穿的比我還像新娘避消。我一直安慰自己,他們只是感情好召夹,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布岩喷。 她就那樣靜靜地躺著,像睡著了一般监憎。 火紅的嫁衣襯著肌膚如雪纱意。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天鲸阔,我揣著相機(jī)與錄音偷霉,去河邊找鬼迄委。 笑死,一個(gè)胖子當(dāng)著我的面吹牛类少,可吹牛的內(nèi)容都是我干的叙身。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼硫狞,長吁一口氣:“原來是場噩夢啊……” “哼信轿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起残吩,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤财忽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后泣侮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體定罢,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年旁瘫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祖凫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酬凳,死狀恐怖惠况,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宁仔,我是刑警寧澤稠屠,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站翎苫,受9級特大地震影響权埠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜煎谍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一攘蔽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呐粘,春花似錦满俗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至痘儡,卻和暖如春辕万,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工渐尿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留价捧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓涡戳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脯倚。 傳聞我的和親對象是個(gè)殘疾皇子渔彰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評論 2 359