Gradle插件

一坪仇、插件相關(guān)API

PluginAware主要定義了插件相關(guān)API。

public interface PluginAware {
    PluginContainer getPlugins();
    void apply(Closure closure);
    void apply(Action< ? super ObjectConfigurationAction> action);
    void apply(Map<String, ?> options);
    PluginManager getPluginManager();
}

應(yīng)用插件

apply plugin: 'java',表示應(yīng)用Java插件鲁驶。這個語句調(diào)用了apply()方法,后面的plugin: 'java'是一個Map類型參數(shù)舞骆。

apply plugin: MyClass表示應(yīng)用指定class實現(xiàn)的插件钥弯,將在后面的Plugin中介紹。

org.gradle.api.Plugin

Plugin用于定義插件督禽。Gradle提供了完整的API框架脆霎,而很多工作實際是由插件實現(xiàn)的。Gradle內(nèi)置了Java狈惫、Groovy等幾種基礎(chǔ)插件睛蛛,也可以自定義插件。

Plugin接口很簡單胧谈,只有一個apply方法忆肾。

public interface Plugin<T> {
    /**
     * Apply this plugin to the given target object.
     *
     * @param target The target object
     */
    void apply(T target);
}

二、簡易插件開發(fā)

下面的示例代碼實現(xiàn)了HelloPlugin的簡易插件菱肖,代碼可直接寫在build.gradle中客冈。

插件在apply(Project)方法里,給Project創(chuàng)建了一個名為hello的Extension和一個名為welcome的Task蔑滓;Task執(zhí)行時讀取Extension并打印字符串郊酒。

build.gradle執(zhí)行到apply plugin: HelloPlugin時遇绞,HelloPlugin.apply(Project)方法被執(zhí)行,從而Project有了hello的Extension燎窘,于是后面可以調(diào)用hello {}對插件進行配置摹闽。

class HelloExtension {
    Boolean enable = true
    String text = ''
}
class HelloPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        project.extensions.create('hello', HelloExtension)
        project.task('welcome') {
            doLast {
                HelloExtension ext = project.extensions.hello;
                println ext.enable ? "Hello ${ext.text}!" : 'HelloPlugin is disabled.'
            }
        }
    }
}
apply plugin: HelloPlugin
hello {
    enable = true
    text = 'Gradle'
}

在命令行中執(zhí)行結(jié)果如下:

$ ./gradlew welcome
:welcome
Hello Gradle!
BUILD SUCCESSFUL

Android transform

android gradle plugin 提供了 transform api 用來在 .class to dex 過程中對 class 進行處理,可以理解為一種特殊的 Task褐健,因為 transform 最終也會轉(zhuǎn)化為 Task 去執(zhí)行
要實現(xiàn) transform 需要繼承 com.android.build.api.transform.Transform 并實現(xiàn)其方法付鹿,實現(xiàn)了 Transform 以后,要想應(yīng)用蚜迅,就調(diào)用project.android.registerTransform()

public class MyTransform extends Transform {
    @Override
    public String getName() {
        // 返回 transform 的名稱舵匾,最終的名稱會是 transformClassesWithMyTransformForDebug 這種形式   
        return "MyTransform";
    }

    @Override
    public Set<QualifiedContent.ContentType> getInputTypes() {
        /**
        返回需要處理的數(shù)據(jù)類型 有 下面幾種類型可選
        public static final Set<ContentType> CONTENT_CLASS = ImmutableSet.of(CLASSES);
        public static final Set<ContentType> CONTENT_JARS = ImmutableSet.of(CLASSES, RESOURCES);
        public static final Set<ContentType> CONTENT_RESOURCES = ImmutableSet.of(RESOURCES);
        public static final Set<ContentType> CONTENT_NATIVE_LIBS = ImmutableSet.of(NATIVE_LIBS);
        public static final Set<ContentType> CONTENT_DEX = ImmutableSet.of(ExtendedContentType.DEX);
        public static final Set<ContentType> DATA_BINDING_ARTIFACT = ImmutableSet.of(ExtendedContentType.DATA_BINDING);
        */
        return TransformManager.CONTENT_CLASS;
    }

    @Override
    public Set<? super QualifiedContent.Scope> getScopes() {
        /**
        返回需要處理內(nèi)容的范圍,有下面幾種類型
        PROJECT(1), 只處理項目的內(nèi)容
        SUB_PROJECTS(4), 只處理子項目
        EXTERNAL_LIBRARIES(16), 只處理外部庫
        TESTED_CODE(32), 只處理當前 variant 對應(yīng)的測試代碼
        PROVIDED_ONLY(64), 處理依賴
        @Deprecated
        PROJECT_LOCAL_DEPS(2),
        @Deprecated
        SUB_PROJECTS_LOCAL_DEPS(8);
        */
        return Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT);
    }

    @Override
    public boolean isIncremental() {
        // 是否增量谁不,如果返回 true坐梯,TransformInput 會包括一份修改的文件列表,返回 false刹帕,會進行全量編譯吵血,刪除上一次的輸出內(nèi)容
        return false;
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        // 在這里處理 class
        super.transform(transformInvocation)
        // 在 transform 里,如果沒有任何修改偷溺,也要把 input 的內(nèi)容輸出到 output蹋辅,否則會報錯
        for (TransformInput input : transformInvocation.inputs) {
            input.directoryInputs.each { dir ->
                // 獲取對應(yīng)的輸出目錄
                File output = transformInvocation.outputProvider.getContentLocation(dir.name, dir.contentTypes, dir.scopes, Format.DIRECTORY)
                dir.changedFiles // 增量模式下修改的文件
                dir.file // 獲取輸入的目錄
                FileUtils.copyDirectory(dir.file, output) // input 內(nèi)容輸出到 output
            }
            input.jarInputs.each { jar ->
                // 獲取對應(yīng)的輸出 jar
                File output = transformInvocation.outputProvider.getContentLocation(jar.name, jar.contentTypes, jar.scopes, Format.JAR)
                jar.file // 獲取輸入的 jar 文件
                FileUtils.copyFile(jar.file, output) // input 內(nèi)容輸出到 output
            }
        }
    }
}

// 注冊 transform
android.registerTransform(new MyTransform())

三、插件開發(fā)

  1. 在 android studio 中創(chuàng)建一個 java module

  2. 在 src/main 目錄下創(chuàng)建 groovy 目錄挫掏,然后創(chuàng)建自己的包名和插件類

  3. 在 src/main 目錄下創(chuàng)建 resources/META-INFO/gradle-plugins 目錄侦另,創(chuàng)建 myplugin.properties 文件,文件里內(nèi)容是

    implementation-class=com.*.MyPlugin // 這里是自己的插件類
    
  4. 修改 build.gradle 文件

    // 引入 groovy 和 java 插件
    apply plugin: 'groovy'
    apply plugin: 'java'
    
    buildscript {
        repositories {
            mavenLocal()
            maven { url 'https://maven.google.com' }
            jcenter()
        }
    }
    
    repositories {
        mavenLocal()
        maven { url 'https://maven.google.com' }
    }
    
    dependencies {
        compile gradleApi()
        compile localGroovy()
        compile 'com.android.tools.build:gradle:3.0.1'
    }
    
  1. 創(chuàng)建Plugin類

    創(chuàng)建插件類尉共,就可以寫插件的代碼了褒傅。插件類繼承 Plugin,并實現(xiàn) apply 接口爸邢,apply 就是在 build.gradle 里 apply plugin 'xxx' 的時候要調(diào)用的接口了
    插件開發(fā)可以使用 groovy 和 java樊卓,使用 groovy 的話可以有更多的語法糖,開發(fā)起來更方便一些

    package com.binzi.plugin
    
    import org.gradle.api.Plugin
    import org.gradle.api.Project
    
    class MyPlugin implements Plugin<Project> {
    
        @Override
        void apply(Project project) {
            println("apply my plugin")
        }
    }
    
  2. 創(chuàng)建插件的task

    我們再定義一個 task 類 MyTask杠河,繼承自 DefaultTask,簡單的輸出一些信息

    package com.binzi.plugin
    
    import org.gradle.api.DefaultTask
    import org.gradle.api.tasks.TaskAction
    
    class MyTask extends DefaultTask {
    
        @TaskAction
        void action() {
            println('my task run')
        }
    }
    

    然后在 plugin 中注冊這個 task

    class MyPlugin implements Plugin<Project> {
    
        @Override
        void apply(Project project) {
            println("apply my plugin")
            project.tasks.create("mytask", MyTask.class)
        }
    }
    
  3. 本地安裝插件

    這樣一個簡單的插件就開發(fā)好了浇辜,如何使用呢
    我們首先需要在 build.gradle 中引入 maven 插件券敌,并且配置 install 相關(guān)的屬性

    apply plugin: 'maven'
    
    install {
        repositories.mavenInstaller {
            pom.version = '0.0.1' // 配置插件版本號
            pom.artifactId = 'myplugin' // 配置插件標識
            pom.groupId = 'com.binzi.plugin' // 配置插件組織
        }
    }
    

    之后執(zhí)行 ./gradlew install 便會把插件安裝在本地 maven 倉庫
    之后在使用的地方引入我們插件的 classpath

    classpath 'com.binzi.plugin:myplugin:0.0.1'
    

    加載插件

    apply plugin; 'myplugin' // 這里的 myplugin 是前面說的 myplugin.properties 的名字
    
  4. 打包發(fā)布

    在插件 build.gradle 里新增上傳的配置如下

    uploadArchives {
        repositories {
            mavenDeployer {
                repository(url: "mavenUrl")
                pom.version = '0.0.1'
                pom.artifactId = 'myplugin'
            }
        }
    }
    

Android Plugin主要流程

插件入口

在前面講解自定義插件的時候說到過,要定義一個 xxx.properties 文件柳洋,里面聲明插件的入口類待诅,要知道 android gradle plugin 的入口類,看源碼中android.properties 文件就可以熊镣,內(nèi)容如下:

implementation-class=com.android.build.gradle.AppPlugin

這里定義了入口是 AppPlugin卑雁,AppPlugin 繼承自AbstractAppPlugin,AbstractAppPlugin繼承自BasePlugin募书。
AbstractAppPlugin 里沒有做太多的操作,主要是重寫了 createTaskManager 和 createExtension测蹲,剩下的大部分工作還是在 BasePlugin 里做的莹捡。

BasePlugin中的的入口函數(shù)是apply(0

 @Override
 public final void apply(@NonNull Project project) {
     CrashReporting.runAction(
         () -> {
         basePluginApply(project);
         pluginSpecificApply(project);
     });
 }

這里主要調(diào)用

 @Override
 public final void apply(@NonNull Project project) {
     CrashReporting.runAction(
         () -> {
         basePluginApply(project);
         pluginSpecificApply(project);
     });
 }

這里主要調(diào)用

 @Override
 public final void apply(@NonNull Project project) {
     CrashReporting.runAction(
         () -> {
         basePluginApply(project);
         pluginSpecificApply(project);
     });
 }

這里主要調(diào)用basePluginApply和pluginSpecificApply,其中pluginSpecificApply是個抽象函數(shù)扣甲,在pluginSpecificApply中是個空實現(xiàn)篮赢,主要操作都在basePluginApply中。

下面來看看basePluginApply中都干了些啥:

//檢查工程路徑
checkPathForErrors();
//檢查兩個module是否有相同的id
checkModulesForErrors();
//PluginInitializer初始化
PluginInitializer.initialize(project);
//ProfilerInitializer初始化
ProfilerInitializer.init(project, projectOptions);
threadRecorder = ThreadRecorder.get();

// 使用project的選項配置worker
Workers.INSTANCE.initFromProject(
projectOptions,
ForkJoinPool.commonPool());
//profiler中寫入plugin信息
ProcessProfileWriter.getProject(project.getPath())
    .setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION)
    .setAndroidPlugin(getAnalyticsPluginType())
    .setPluginGeneration(GradleBuildProject.PluginGeneration.FIRST)
    .q(AnalyticsUtil.toProto(projectOptions));

 if (!projectOptions.get(BooleanOption.ENABLE_NEW_DSL_AND_API)) {
    //配置project
     threadRecorder.record(
         ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
         project.getPath(),
         null,
         this::configureProject);
    //配置Extension
     threadRecorder.record(
         ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
         project.getPath(),
         null,
         this::configureExtension);
    //創(chuàng)建task
     threadRecorder.record(
         ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
         project.getPath(),
         null,
         this::createTasks);
 }

配置Project

private void configureProject() {
    final Gradle gradle = project.getGradle();
    ObjectFactory objectFactory = project.getObjects();
    //配置項對應(yīng)的model
    extraModelInfo = new ExtraModelInfo(project.getPath(), projectOptions, project.getLogger());

    sdkHandler = new SdkHandler(project, getLogger());
    if (!gradle.getStartParameter().isOffline()
        && projectOptions.get(BooleanOption.ENABLE_SDK_DOWNLOAD)) {
        SdkLibData sdkLibData = SdkLibData.download(getDownloader(), getSettingsController());
        sdkHandler.setSdkLibData(sdkLibData);
    }
    //創(chuàng)建AndroidBuilder
    AndroidBuilder androidBuilder =
        new AndroidBuilder(
            project == project.getRootProject() ? project.getName() : project.getPath(),
            creator,
            new GradleProcessExecutor(project),
            new GradleJavaProcessExecutor(project),
            extraModelInfo.getSyncIssueHandler(),
            extraModelInfo.getMessageReceiver(),
            getLogger());
    dataBindingBuilder = new DataBindingBuilder();
    dataBindingBuilder.setPrintMachineReadableOutput(
        SyncOptions.getErrorFormatMode(projectOptions) == ErrorFormatMode.MACHINE_PARSABLE);

    if (projectOptions.hasRemovedOptions()) {
        androidBuilder
        .getIssueReporter()
        .reportWarning(Type.GENERIC, projectOptions.getRemovedOptionsErrorMessage());
    }

    if (projectOptions.hasDeprecatedOptions()) {
        extraModelInfo
        .getDeprecationReporter()
        .reportDeprecatedOptions(projectOptions.getDeprecatedOptions());
    }

    if (!projectOptions.getExperimentalOptions().isEmpty()) {
        projectOptions
        .getExperimentalOptions()
        .forEach(extraModelInfo.getDeprecationReporter()::reportExperimentalOption);
    }

    // Enforce minimum versions of certain plugins
    GradlePluginUtils.enforceMinimumVersionsOfPlugins(
        project, androidBuilder.getIssueReporter());

    //應(yīng)用java插件
    project.getPlugins().apply(JavaBasePlugin.class);

    DslScopeImpl dslScope =
        new DslScopeImpl(
            extraModelInfo.getSyncIssueHandler(),
            extraModelInfo.getDeprecationReporter(),
            objectFactory);
    //創(chuàng)建build緩存文件
    @Nullable
    FileCache buildCache = BuildCacheUtils.createBuildCacheIfEnabled(project, projectOptions);

    globalScope =
        new GlobalScope(
            project,
            new ProjectWrapper(project),
            projectOptions,
            dslScope,
            androidBuilder,
            sdkHandler,
            registry,
            buildCache);

    project.getTasks()
    .getByName("assemble")
    .setDescription(
        "Assembles all variants of all applications and secondary packages.");

    // 添加build監(jiān)聽
    gradle.addBuildListener(
        new BuildListener() {
            @Override
            public void buildStarted(@NonNull Gradle gradle) {}

            @Override
            public void settingsEvaluated(@NonNull Settings settings) {}

            @Override
            public void projectsLoaded(@NonNull Gradle gradle) {}

            @Override
            public void projectsEvaluated(@NonNull Gradle gradle) {}

            @Override
            public void buildFinished(@NonNull BuildResult buildResult) {
                // Do not run buildFinished for included project in composite build.
                if (buildResult.getGradle().getParent() != null) {
                    return;
                }
                ModelBuilder.clearCaches();
                sdkHandler.unload();
                threadRecorder.record(
                    ExecutionType.BASE_PLUGIN_BUILD_FINISHED,
                    project.getPath(),
                    null,
                    () -> {
                        WorkerActionServiceRegistry.INSTANCE
                        .shutdownAllRegisteredServices(
                            ForkJoinPool.commonPool());
                        Main.clearInternTables();
                    });
                DeprecationReporterImpl.Companion.clean();
            }
        });

    createLintClasspathConfiguration(project);
}

配置Extension

private void configureExtension() {
    ...
        
    extension = createExtension(
                    project,
                    projectOptions,
                    globalScope,
                    sdkHandler,
                    buildTypeContainer,
                    productFlavorContainer,
                    signingConfigContainer,
                    buildOutputs,
                    sourceSetManager,
                    extraModelInfo);
    
    taskManager = createTaskManager(
                    globalScope,
                    project,
                    projectOptions,
                    dataBindingBuilder,
                    extension,
                    sdkHandler,
                    variantFactory,
                    registry,
                    threadRecorder);
    
    ...
     variantFactory.createDefaultComponents(
                buildTypeContainer, productFlavorContainer, signingConfigContainer);
}

其中主要是調(diào)用 createExtension來加載gradle腳本中的配置信息琉挖,在使用解析完后的配置信息創(chuàng)建taskManager启泣、variantManager等其他組件。

創(chuàng)建 task

    private void createTasks() {
        threadRecorder.record(
                ExecutionType.TASK_MANAGER_CREATE_TASKS,
                project.getPath(),
                null,
                () -> taskManager.createTasksBeforeEvaluate());

        project.afterEvaluate(
                CrashReporting.afterEvaluate(
                        p -> {
                            sourceSetManager.runBuildableArtifactsActions();

                            threadRecorder.record(
                                    ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
                                    project.getPath(),
                                    null,
                                    this::createAndroidTasks);
                        }));
    }

createAndroidTasks 的調(diào)用時機是在 project.afterEvaluate 里調(diào)用的示辈,這個時候所有模塊配置已經(jīng)完成了
在 BasePlugin.createAndroidTasks 里寥茫,是調(diào)用 VariantManager.createAndroidTasks 完成工作的。

四矾麻、Android Plugin主要 Task

1坠敷、Android 打包流程

官方介紹的流程如下:

  1. 編譯器將您的源代碼轉(zhuǎn)換成 DEX(Dalvik Executable) 文件(其中包括 Android 設(shè)備上運行的字節(jié)碼),將所有其他內(nèi)容轉(zhuǎn)換成已編譯資源射富。
  2. APK 打包器將 DEX 文件和已編譯資源合并成單個 APK膝迎。 不過,必須先簽署 APK胰耗,才能將應(yīng)用安裝并部署到 Android 設(shè)備上限次。
  3. APK 打包器使用調(diào)試或發(fā)布密鑰庫簽署您的 APK:
  4. 在生成最終 APK 之前风钻,打包器會使用 zipalign 工具對應(yīng)用進行優(yōu)化瓮孙,減少其在設(shè)備上運行時占用的內(nèi)存。

首先我們看一下 打包一個 apk 需要哪些 task仅父。
在項目根目錄下執(zhí)行命令:

./gradlew assembleDebug --console=plain

看一下輸出結(jié)果

> Task :app:preBuild UP-TO-DATE
> Task :app:prepareLintJar UP-TO-DATE
> Task :app:preDebugBuild
> Task :app:compileDebugAidl NO-SOURCE
> Task :app:compileDebugRenderscript NO-SOURCE
> Task :app:checkDebugManifest
> Task :app:generateDebugBuildConfig
> Task :app:generateDebugSources
> Task :app:javaPreCompileDebug
> Task :app:mainApkListPersistenceDebug
> Task :app:generateDebugResValues
> Task :app:generateDebugResources
> Task :app:mergeDebugResources
> Task :app:createDebugCompatibleScreenManifests
> Task :app:processDebugManifest
> Task :app:processDebugResources
> Task :app:compileDebugJavaWithJavac
> Task :app:compileDebugSources
> Task :app:mergeDebugShaders
> Task :app:compileDebugShaders
> Task :app:generateDebugAssets
> Task :app:mergeDebugAssets
> Task :app:checkDebugDuplicateClasses
> Task :app:transformClassesWithDexBuilderForDebug
> Task :app:validateSigningDebug
> Task :app:signingConfigWriterDebug
> Task :app:mergeExtDexDebug
> Task :app:mergeDexDebug
> Task :app:mergeDebugJniLibFolders
> Task :app:transformNativeLibsWithMergeJniLibsForDebug
> Task :app:transformNativeLibsWithStripDebugSymbolForDebug
> Task :app:processDebugJavaRes NO-SOURCE
> Task :app:transformResourcesWithMergeJavaResForDebug
> Task :app:packageDebug
> Task :app:assembleDebug

Task 對應(yīng)實現(xiàn)類

我們先看看每個 task 都是做什么的赠群,以及其對應(yīng)的實現(xiàn)類羊始。
先回憶一下,我們在前面 android-gradle-plugin 主要流程分析里說到過查描,task 的實現(xiàn)可以在 TaskManager 里找到突委,創(chuàng)建 Task 的方法主要是兩個,TaskManager.createTasksBeforeEvaluate() 和 ApplicationTaskManager.createTasksForVariantScope()冬三,所以這些 task 的實現(xiàn)匀油,也在這兩個類里找就可以,下面列出了各個 task 的作用及實現(xiàn)類勾笆。

Task 對應(yīng)實現(xiàn)類 作用
preBuild 空 task敌蚜,只做錨點使用
preDebugBuild 空 task,只做錨點使用窝爪,與 preBuild 區(qū)別是這個 task 是 variant 的錨點
compileDebugAidl AidlCompile 處理 aidl
compileDebugRenderscript RenderscriptCompile 處理 renderscript
checkDebugManifest CheckManifest 檢測 manifest 是否存在
generateDebugBuildConfig GenerateBuildConfig 生成 BuildConfig.java
prepareLintJar PrepareLintJar 拷貝 lint jar 包到指定位置
generateDebugResValues GenerateResValues 生成 resvalues弛车,generated.xml
generateDebugResources 空 task齐媒,錨點
mergeDebugResources MergeResources 合并資源文件
createDebugCompatibleScreenManifests CompatibleScreensManifest manifest 文件中生成 compatible-screens,指定屏幕適配
processDebugManifest MergeManifests 合并 manifest 文件
splitsDiscoveryTaskDebug SplitsDiscovery 生成 split-list.json纷跛,用于 apk 分包
processDebugResources ProcessAndroidResources aapt 打包資源
generateDebugSources 空 task喻括,錨點
javaPreCompileDebug JavaPreCompileTask 生成 annotationProcessors.json 文件
compileDebugJavaWithJavac AndroidJavaCompile 編譯 java 文件
compileDebugNdk NdkCompile 編譯 ndk
compileDebugSources 空 task,錨點使用
mergeDebugShaders MergeSourceSetFolders 合并 shader 文件
compileDebugShaders ShaderCompile 編譯 shaders
generateDebugAssets 空 task忽舟,錨點
mergeDebugAssets MergeSourceSetFolders 合并 assets 文件
transformClassesWithDexBuilderForDebug DexArchiveBuilderTransform class 打包 dex
transformDexArchiveWithExternalLibsDexMergerForDebug ExternalLibsMergerTransform 打包三方庫的 dex双妨,在 de增量的時候就不需要再 merge 了,節(jié)省時間
transformDexArchiveWithDexMergerForDebug DexMergerTransform 打包最終的 dex
mergeDebugJniLibFolders MergeSouceSetFolders 合并 jni lib 文件
transformNativeLibsWithMergeJniLibsForDebug MergeJavaResourcesTransform 合并 jnilibs
transformNativeLibsWithStripDebugSymbolForDebug StripDebugSymbolTransform 去掉 native lib 里的 debug 符號
processDebugJavaRes ProcessJavaResConfigAction 處理 java res
transformResourcesWithMergeJavaResForDebug MergeJavaResourcesTransform 合并 java res
validateSigningDebug ValidateSigningTask 驗證簽名
packageDebug PackageApplication 打包 apk
assembleDebug 空 task叮阅,錨點
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刁品,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子浩姥,更是在濱河造成了極大的恐慌挑随,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勒叠,死亡現(xiàn)場離奇詭異兜挨,居然都是意外死亡,警方通過查閱死者的電腦和手機眯分,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門拌汇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弊决,你說我怎么就攤上這事噪舀。” “怎么了飘诗?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵与倡,是天一觀的道長。 經(jīng)常有香客問我昆稿,道長纺座,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任溉潭,我火速辦了婚禮净响,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岛抄。我一直安慰自己别惦,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布夫椭。 她就那樣靜靜地躺著,像睡著了一般氯庆。 火紅的嫁衣襯著肌膚如雪蹭秋。 梳的紋絲不亂的頭發(fā)上扰付,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音仁讨,去河邊找鬼羽莺。 笑死,一個胖子當著我的面吹牛洞豁,可吹牛的內(nèi)容都是我干的盐固。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼丈挟,長吁一口氣:“原來是場噩夢啊……” “哼刁卜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起曙咽,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤蛔趴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后例朱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孝情,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年洒嗤,在試婚紗的時候發(fā)現(xiàn)自己被綠了箫荡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡渔隶,死狀恐怖羔挡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情派撕,我是刑警寧澤婉弹,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站终吼,受9級特大地震影響镀赌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜际跪,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一商佛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧姆打,春花似錦良姆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春痊剖,著一層夾襖步出監(jiān)牢的瞬間韩玩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工陆馁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留找颓,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓叮贩,卻偏偏與公主長得像击狮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子益老,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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