Gradle插件

一、插件相关API

PluginAware主要定义了插件相关API。

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

应用插件

apply plugin: 'java',表示应用Java插件。这个语句调用了apply()方法,后面的plugin: 'java'是一个Map类型参数。

apply plugin: MyClass表示应用指定class实现的插件,将在后面的Plugin中介绍。

org.gradle.api.Plugin

Plugin用于定义插件。Gradle提供了完整的API框架,而很多工作实际是由插件实现的。Gradle内置了Java、Groovy等几种基础插件,也可以自定义插件。

Plugin接口很简单,只有一个apply方法。

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

二、简易插件开发

下面的示例代码实现了HelloPlugin的简易插件,代码可直接写在build.gradle中。

插件在apply(Project)方法里,给Project创建了一个名为hello的Extension和一个名为welcome的Task;Task执行时读取Extension并打印字符串。

build.gradle执行到apply plugin: HelloPlugin时,HelloPlugin.apply(Project)方法被执行,从而Project有了hello的Extension,于是后面可以调用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'
}

在命令行中执行结果如下:

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

Android transform

android gradle plugin 提供了 transform api 用来在 .class to dex 过程中对 class 进行处理,可以理解为一种特殊的 Task,因为 transform 最终也会转化为 Task 去执行
要实现 transform 需要继承 com.android.build.api.transform.Transform 并实现其方法,实现了 Transform 以后,要想应用,就调用project.android.registerTransform()

public class MyTransform extends Transform {
    @Override
    public String getName() {
        // 返回 transform 的名称,最终的名称会是 transformClassesWithMyTransformForDebug 这种形式   
        return "MyTransform";
    }

    @Override
    public Set<QualifiedContent.ContentType> getInputTypes() {
        /**
        返回需要处理的数据类型 有 下面几种类型可选
        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() {
        /**
        返回需要处理内容的范围,有下面几种类型
        PROJECT(1), 只处理项目的内容
        SUB_PROJECTS(4), 只处理子项目
        EXTERNAL_LIBRARIES(16), 只处理外部库
        TESTED_CODE(32), 只处理当前 variant 对应的测试代码
        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,会进行全量编译,删除上一次的输出内容
        return false;
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        // 在这里处理 class
        super.transform(transformInvocation)
        // 在 transform 里,如果没有任何修改,也要把 input 的内容输出到 output,否则会报错
        for (TransformInput input : transformInvocation.inputs) {
            input.directoryInputs.each { dir ->
                // 获取对应的输出目录
                File output = transformInvocation.outputProvider.getContentLocation(dir.name, dir.contentTypes, dir.scopes, Format.DIRECTORY)
                dir.changedFiles // 增量模式下修改的文件
                dir.file // 获取输入的目录
                FileUtils.copyDirectory(dir.file, output) // input 内容输出到 output
            }
            input.jarInputs.each { jar ->
                // 获取对应的输出 jar
                File output = transformInvocation.outputProvider.getContentLocation(jar.name, jar.contentTypes, jar.scopes, Format.JAR)
                jar.file // 获取输入的 jar 文件
                FileUtils.copyFile(jar.file, output) // input 内容输出到 output
            }
        }
    }
}

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

三、插件开发

  1. 在 android studio 中创建一个 java module

  2. 在 src/main 目录下创建 groovy 目录,然后创建自己的包名和插件类

  3. 在 src/main 目录下创建 resources/META-INFO/gradle-plugins 目录,创建 myplugin.properties 文件,文件里内容是

    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. 创建Plugin类

    创建插件类,就可以写插件的代码了。插件类继承 Plugin,并实现 apply 接口,apply 就是在 build.gradle 里 apply plugin 'xxx' 的时候要调用的接口了
    插件开发可以使用 groovy 和 java,使用 groovy 的话可以有更多的语法糖,开发起来更方便一些

    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. 创建插件的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. 本地安装插件

    这样一个简单的插件就开发好了,如何使用呢
    我们首先需要在 build.gradle 中引入 maven 插件,并且配置 install 相关的属性

    apply plugin: 'maven'
    
    install {
        repositories.mavenInstaller {
            pom.version = '0.0.1' // 配置插件版本号
            pom.artifactId = 'myplugin' // 配置插件标识
            pom.groupId = 'com.binzi.plugin' // 配置插件组织
        }
    }
    

    之后执行 ./gradlew install 便会把插件安装在本地 maven 仓库
    之后在使用的地方引入我们插件的 classpath

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

    加载插件

    apply plugin; 'myplugin' // 这里的 myplugin 是前面说的 myplugin.properties 的名字
    
  4. 打包发布

    在插件 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 文件就可以,内容如下:

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

这里定义了入口是 AppPlugin,AppPlugin 继承自AbstractAppPlugin,AbstractAppPlugin继承自BasePlugin。
AbstractAppPlugin 里没有做太多的操作,主要是重写了 createTaskManager 和 createExtension,剩下的大部分工作还是在 BasePlugin 里做的。

BasePlugin中的的入口函数是apply(0

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

这里主要调用

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

这里主要调用

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

这里主要调用basePluginApply和pluginSpecificApply,其中pluginSpecificApply是个抽象函数,在pluginSpecificApply中是个空实现,主要操作都在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);
    //创建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();
    //配置项对应的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);
    }
    //创建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());

    //应用java插件
    project.getPlugins().apply(JavaBasePlugin.class);

    DslScopeImpl dslScope =
        new DslScopeImpl(
            extraModelInfo.getSyncIssueHandler(),
            extraModelInfo.getDeprecationReporter(),
            objectFactory);
    //创建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监听
    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);
}

其中主要是调用 createExtension来加载gradle脚本中的配置信息,在使用解析完后的配置信息创建taskManager、variantManager等其他组件。

创建 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 的调用时机是在 project.afterEvaluate 里调用的,这个时候所有模块配置已经完成了
在 BasePlugin.createAndroidTasks 里,是调用 VariantManager.createAndroidTasks 完成工作的。

四、Android Plugin主要 Task

1、Android 打包流程

官方介绍的流程如下:

  1. 编译器将您的源代码转换成 DEX(Dalvik Executable) 文件(其中包括 Android 设备上运行的字节码),将所有其他内容转换成已编译资源。
  2. APK 打包器将 DEX 文件和已编译资源合并成单个 APK。 不过,必须先签署 APK,才能将应用安装并部署到 Android 设备上。
  3. APK 打包器使用调试或发布密钥库签署您的 APK:
  4. 在生成最终 APK 之前,打包器会使用 zipalign 工具对应用进行优化,减少其在设备上运行时占用的内存。

首先我们看一下 打包一个 apk 需要哪些 task。
在项目根目录下执行命令:

./gradlew assembleDebug --console=plain

看一下输出结果

> 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 对应实现类

我们先看看每个 task 都是做什么的,以及其对应的实现类。
先回忆一下,我们在前面 android-gradle-plugin 主要流程分析里说到过,task 的实现可以在 TaskManager 里找到,创建 Task 的方法主要是两个,TaskManager.createTasksBeforeEvaluate() 和 ApplicationTaskManager.createTasksForVariantScope(),所以这些 task 的实现,也在这两个类里找就可以,下面列出了各个 task 的作用及实现类。

Task 对应实现类 作用
preBuild 空 task,只做锚点使用
preDebugBuild 空 task,只做锚点使用,与 preBuild 区别是这个 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 了,节省时间
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,锚点
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容