总结:Android之Gradle总结

本文主要总结的内容如下:

  • Groovy介绍

  • 一、Gradle简介(定义与特性)

  • 二、Gradle使用

    • 1、Multi-Projects Build
    • 2、gradle命令
    • 3、gradle的工作流程
    • 4、Gradle编程模型
  • 三、Android中使用Gradle

    • 1、相关文件
    • 2、Android tasks
    • 3、相关配置
    • 4、Gradle模板举例

0、Groovy介绍:

Gradle 选择了GroovyGroovy基于Java并拓展了Java

Gradle选择了Groovy的原因:
Groovy基于Java并拓展了Java。 Java程序员可以无缝切换到使用Groovy开发程序。Groovy说白了就是把写Java程序变得像写脚本一样简单。写完就可以执行,Groovy内部会将其编译成Javaclass然后启动虚拟机来执行。

  • 1、定义:Groovy是在 java平台上的、 具有像PythonRubySmalltalk语言特性的灵活动态语言, Groovy保证了这些特性像 Java语法一样被 Java开发者使用。

  • 2、Groovy是一种动态语言。

  • 3、Groovy与Java的关系:
    当我执行Groovy脚本时,Groovy会先将其编译成Java类字节码,然后通过Jvm来执行这个Java类。


    Java、Groovy和Jvm的关系示意图
  • 4、特性:

    • Groovy注释标记和Java一样,支持//或者/**/
    • Groovy语句可以不用分号结尾。
    • Groovy中支持动态类型,即定义变量的时候可以不指定其类型。Groovy中,变量定义可以使用关键字def。注意,虽然def不是必须的,但是为了代码清晰,建议还是使用def关键字。
    • 函数定义时,参数的类型也可以不指定。
    • 除了变量定义可以不指定类型外,Groovy中函数的返回值也可以是无类型的
         //无类型的函数定义,必须使用def关键字
         def  nonReturnTypeFunc(){
             last_line   //最后一行代码的执行结果就是本函数的返回值
         }
         //如果指定了函数返回类型,则可不必加def关键字来定义函数
         String getString(){
            return"I am a string"
         }
      

快速学习攻略:Groovy脚本基础全攻略

一、Gradle简介

1、定义

  • 1、Gradle是一个工具,同时它也是一个编程框架。

  • 2、Gradle中,每一个待编译的工程都叫一个Project
    每一个Project在构建的时候都包含一系列的Task
    比如一个Android APK的编译可能包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。

  • 3、Gradle是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译Java有Java插件,编译Groovy有Groovy插件,编译Android APP有Android APP插件,编译Android Library有Android Library插件。

2、特性:

  • 使用灵活的语言来写构建规则。
  • Gradle是一种DSL,即Domain Specific Language,领域相关语言。

二、Gradle使用

1、Multi-Projects Build

将多个Project进行统一编译,类似这种操作,在Gradle中,可以称之为Multi-Projects Build

1.1、使用方式:
  • 1、在工程根目录(如图中gradleProjects)下添加一个build.gradle文件(用于配置其他子Project的)

  • 2、在工程根目录(如图中gradleProjects)下添加一个settings.gradle文件(名称必须为settings.gradle用于告知Gradle包含多少个子Project)。

    Multi-Projects Build 文件夹示意图
1.2、.gradle 内容:
  • 1、build.gralde:
    为子Project添加一些属性。这个build.gradle有没有都无所属。

  • 2、settings.gradle

       #settings.gradle
       include 'project1',
               'project2',
               'project3',
               'project4'
    

2、gradle命令:

2.1、查看工程信息:
  • 命令:gradle projects
    在根目录(如gradleProjects)下打开终端,执行此命令
  • 结果:显示有多少个project
gradle projects命令
2.2、查看任务信息:
  • 命令:gradleproject-path:tasks
    在子目录(如project1)下打开终端,执行此命令。若不在子目录中则需要加上project-path
  • 结果:显示当前项目的任务信息
    由于我这个子目录下什么也没有,所以显示的信息是这个样子。
gradle:tasks命令
2.3、执行任务(task)
  • 命令:gradle task-name
    task-name为指定任务的名称,如clean清理任务
  • 示例:
    • gradle clean是执行清理任务,和make clean类似。
    • gradle properites 用来查看所有属性信息。

3、gradle的工作流程:

gradle工作流程

包含三个阶段:

  • 1、首先是初始化阶段(Initiliazation phase)。
    对我们前面的multi-project build而言,就是执行settings.gradle

  • 2、Configration阶段的目标是解析每个project中的build.gradle

    • 比如multi-project build例子中,解析每个子目录中的build.gradle。在这两个阶段之间,我们可以加一些定制化的Hook。这当然是通过API来添加的。

    • Configuration 阶段完了后,整个build的project以及内部的Task关系就确定了。一个Project包含很多Task,每个Task之间有依赖关系。Configuration会建立一个有向图来描述Task之间的依赖关系。所以,我们可以添加一个HOOK,即当Task关系图建立好后,执行一些操作。

  • 3、最后一个阶段就是执行任务了。
    你在gradle xxx中指定什么任务,gradle就会将这个xxx任务链上的所有任务全部按依赖顺序执行一遍!
    任务执行完后,我们还可以加Hook。

4、Gradle编程模型

Gradle基于Groovy,Groovy又基于Java。所以,Gradle执行的时候和Groovy一样,会把脚本转换成Java对象。

Gradle主要有三种对象,这三种对象和三种不同的脚本文件对应,在gradle执行的时候,会将脚本转换成对应的对端:

  • Gradle对象
    当我们执行gradle xxx或者什么的时候,gradle会从默认的配置脚本中构造出一个Gradle对象在整个执行过程中,只有这么一个对象。Gradle对象的数据类型就是Gradle。
    我们一般很少去定制这个默认的配置脚本。

  • Project对象:
    每一个build.gradle会转换成一个Project对象

  • Settings对象:
    每一个settings.gradle都会转换成一个Settings对象

各种类型Gradle对应的对象类型:

脚本类型 关联对象类型
Build script Project
Init script Gradle
Settings script Settings
4.1、Gradle对象:

gradle对象的属性如图,其中的几个属性说明如下:

  • hashCode()
    在settings.gradle和build.gradle中,gradle实例对象的hashCode是一样的
  • HomeDir
    是指在哪个目录存储的gradle可执行程序
  • User Home Dir
    gradle自己设置的目录,里边存储了一些配置文件,以及编译过程中的缓存文件,生成的类文件,编译中依赖的插件等等。
Gradle对象属性
4.2、Project对象:
  • 1、每一个build.gradle文件都会转换成一个Project对象。在Gradle术语中,Project对象对应的是BuildScript

  • 2、Project包含若干Tasks。由于Project对应具体的工程,所以需要为Project加载所需要的插件,比如为Java工程加载Java插件。其实,一个Project包含多少Task往往是插件决定的。

  • 3、一个项目在构建时都具备如下流程:

    • 为当前项目创建一个Settings类型的实例。
    • 如果当前项目存在settings.gradle文件,则通过该文件配置刚才创建的Settings实例。
    • 通过Settings实例的配置创建项目层级结构的Project对象实例。
    • 最后通过上面创建的项目层级结构Project对象实例去执行每个Project对应的build.gradle 脚本。
  • 4、在Project中,需要:

    • 加载插件(调用apply函数):
       //二进制插件(如jar包)
       //Android中的build.gradle
       apply plugin: 'com.android.library'    <==如果是编译Library,则加载此插件
       apply plugin: 'com.android.application'  <==如果是编译Android APP,则加载此插件
      
        //加载一个gradle文件
        apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
      
    • 不同插件有不同的配置。在Project中配置后,插件就知道从何处读取源文件
    • 设置属性:
      单个脚本,不需要考虑属性的跨脚本传播。
      extra property(额外属性):通过ext前缀标示是一个额外属性。定义好之后,后面的存取就不需要ext前缀了。ext属性支持Project和Gradle对象。即Project和Gradle对象都可以设置ext属性:
          //第一次定义或者设置它的时候需要ext前缀
          gradle.ext.api =properties.getProperty('sdk.api')
          println gradle.api  //再次存取api的时候,就不需要ext前缀了
      

三、Android中的Gradle

Android自己定义了好多Script Block。Android定义的DSL参考文档在
https://developer.android.com/tools/building/plugin-for-gradle.html下载。

Android的DSL参考信息

1、相关文件

  • Android中gradle项目基本的目录结构:
     demoApp
        |-- build.gradle
        |-- settings.gradle
        |-- app
            |-- build.gradle
        |-- gradlew
        |-- gradlew.bat
        |-- gradle
            |-- wrapper
                |-- gradle-wrapper.jar
                |-- gradle-wrapper.properties
        |-- gradle.properties
        |-- local.properties
    
1.1、local.properties

对于Android来说,local.properties 是必须的,下面两个配置sdk.dir和ndk.dir是Android Gradle必须要指定的。

   #设置sdk目录
   sdk.dir=xxx
   #设置ndk目录
   ndk.dir=yyy
1.2、项目根目录(顶层)的build.gradle

一般情况下,这个build.gradle 是做一些全局配置,其配置最终会被应用到所有项目中。它典型的配置如下:

   apply from: 'dependencies.gradle'
   
   buildscript {
       repositories {
           jcenter()
           mavenCentral()
       }
       dependencies {
           classpath 'com.android.tools.build:gradle:3.2.1'
       }
   }
   
   allprojects {
       repositories {
           jcenter()
           google()
       }
   }
   
   task clean(type: Delete) {
       delete rootProject.buildDir
   }
  • buildscript:定义了 Android 编译工具的类路径。repositories 中的 jCentermavenCentral` 是两个仓库。

  • allprojects:中定义的属性会被应用到所有 module 中,但是为了保证每个项目的独立性,我们一般不会在这里面操作太多共有的东西。(一般外部依赖库都在其中,gradle会通过查找在这里配置的仓库来比编译指定的依赖库?)

1.3、每个子项目中的build.gradle

针对每个module 的配置,如果其build.gradle中定义的选项和顶层build.gradle定义的相同,则顶层中的配置会被覆盖。典型的配置内容如下:

      apply plugin: 'com.android.application'
      
      android {
          compileSdkVersion 28
          buildToolsVersion "28.0.3"
      
          defaultConfig {
              applicationId "com.demo.testapp"
              minSdkVersion 14
              targetSdkVersion 28
      
              versionCode 1.1.1
              versionName "111"
          }
      
          signingConfigs {
              debug {
                  storeFile file("../debug.jks")
      
                  storePassword "123456"
                  keyAlias "haha"
                  keyPassword "123456"
              }
      
              release {
                  storeFile file("../release.jks")
      
                  storePassword "123456"
                  keyAlias "haha"
                  keyPassword "123456"
              }
          }
      
          buildTypes {
              debug {
                  minifyEnabled false      //是否混淆
                  shrinkResources false    //去除没有用到的资源文件
              }
      
              release {
                  minifyEnabled true
                  shrinkResources true
                  proguardFiles getDefaultProguardFile('proguard-android.txt'), "proguard-rules.pro"
                  signingConfig signingConfigs.release
              }
          }
      
          repositories {
              //添加aar
              flatDir {
                  dirs 'libs'
              }
          }
      
      }
      
      dependencies {
          api fileTree(include: ['*.jar'], dir: 'libs')
          api "com.android.support:appcompat-v7:27.1.1"
      }
  • apply plugin:gradle插件,作为Android 的应用程序,这一步是必须的,因为plugin中提供了Android 编译、测试、打包等等的所有task。

  • android:关于android 的所有特殊配置都在这个代码块中,这就是由上面声明的 plugin 提供的。

    • defaultConfig:程序的默认配置;这里配置的相关属性会覆盖AndroidManifest.xml 中定义的属性(如versionName)
    • applicationId:定义了APP的包名,不同于AndroidManifest.xml中的package属性。
  • buildTypes:定义了编译类型,针对每个类型我们可以有不同的编译配置,不同的编译配置对应的有不同的编译命令。默认的有debugrelease 的类型。

  • dependencies:gradle 的依赖配置。它定义了当前项目需要依赖的其他库。

1.4、Gradle Wrapper

gradle wrapper 是针对gradle新版本对旧版本的向后兼容性问题而使用的。

gradlw wrapper 包含一些脚本文件和针对不同系统下面的运行文件。wrapper 有版本区分,但是并不需要你手动去下载,当你运行脚本的时候,如果本地没有会自动下载对应版本文件。

在不同操作系统下面执行的脚本不同,在 Mac 系统下执行./gradlew ...,在windows 下执行gradle.bat进行编译。

  • gradle wrapper目录结构:
     demoApp
        |-- gradlew
        |-- gradlew.bat
        |-- gradle
            |-- wrapper
                |-- gradle-wrapper.jar
                |-- gradle-wrapper.properties
    

2、Android tasks

2.1、基本的tasks:
  • assemble:对buildType 下的所有配置类型生成相应的apk包
  • clean:移除所有编译输出的文件,如apk
  • check:执行lint检测编译
  • build:同时执行assemblecheck

这些基本的task,在实际项目中会根据不同的配置,会对这些task 设置不同的依赖。如默认的 assmeble 会依赖 assembleDebugassembleRelease,如果直接执行assmeble,最后会编译debugrelease 的所有版本出来。如果我们只需要编译debug 版本,我们可以运行assembleDebug

2.2、新增task:
  • install,会将编译后的apk 安装到连接的设备。

3、相关配置

3.1、BuildConfig

这个类是由gradle根据配置文件生成的,通过配置一些key-value 的键值对,可以用BuildConfig获取相应的字段。例如:

    //module下的build.gradle
    buildTypes {
        debug {
            //配置网络请求根地址
            buildConfigField 'String', 'API_URL', '"http://test.com/api/debug/"'
            //配置app名称
            resValue "string", "app_name", "测试版"
        }

        release {
            //配置网络请求根地址
            buildConfigField 'String', 'API_URL', '"http://test.com/api/release/"'
            //配置app名称
            resValue "string", "app_name", "上线版"
        }
    }

在使用时,只需要调用BuildConfig.API_URL,就能获得当前环境(debug或release)下的值。
而用resValue配置的,则不需要再strings.xml中配置相应名称的值,否则会报错。

3.2、Repositories

repositories中配置的是代码仓库。在dependencies 中配置的一些依赖库都是从这里下载的。
Gradle 支持三种类型的仓库:MavenIvy和一些静态文件或者文件夹。在编译的执行阶段,gradle 将会从仓库中取出对应需要的依赖文件,当然,gradle 本地也会有自己的缓存,不会每次都去取这些依赖。

  • gradle 支持多种 Maven 仓库,一般常用的是jCenter

     //项目根目录下的build.gradle
     repositories {
         jcenter()
         google()
     }
    
  • 有一些项目,可能是一些公司私有的仓库中的,这时候需要手动加入仓库连接,而有的需要用户名和密码,

     //项目根目录下的build.gradle
     repositories {
        maven {
            url 'https://jitpack.io'
            credentials {
               username 'xxx'
               password 'yyy'
            }
        }
      }
    
  • 可以使用相对路径配置本地仓库,通过配置项目中存在的静态文件夹作为本地仓库:

       //module下的build.gradle
       android {
         ......
         repositories {
            flatDir {
                dirs 'libs'
            }
          }
       }
    
3.3、Dependencies

依赖库的配置。

  • File dependencies
    通过files()方法可以添加文件依赖,如果有很多jar文件,我们也可以通过fileTree()方法添加一个文件夹,除此之外,我们还可以通过通配符的方式添加,如下:
   //module下的build.gradle
   dependencies {
       api fileTree(include: ['*.jar'], dir: 'libs')
   }
  • aar dependencies
    可以将项目打包成aar 文件,通过文件的形式来引用:
   //module下的build.gradle
   //其中的aarname是打包的aar文件的名称,后面的ext是固定的
   dependencies {
       api(name: 'aarname', ext: 'aar')
   }
3.4、Source sets

封装所有变体的源集配置。

  • 1、Native libraries
    配置本地 .so库。在配置文件中做如下配置,然后在对应位置建立文件夹,加入对应平台的.so文件。
   //方式一:
    android {
        sourceSets {
            main {
                jniLibs.srcDirs = ['libs', , 'src/main/jni']
            }
        }
    }

    //方式二
    android {
        sourceSets.main {
            jni.srcDirs 'libs'
        }
    }
jni目录示意图
  • 2、配置源集:
        sourceSets {
            main {
                //更改Java源的目录。默认目录是:'src/main/java'。
                java.srcDirs = ['other/java']
                // 默认目录是'src/main/res'
                res.srcDirs = [ 'other/res1', 'other/res2' ]
                //设置清单。默认目录是:src/main/AndroidManifest.xml
                manifest.srcFile 'other/AndroidManifest.xml'
                ...
            }
            //创建其他块以配置其他源集。
            androidTest {
                setRoot 'src/tests'
                ...
            }
        }
    
    • main中的res.srcDirs如果列出多个目录,Gradle将使用它们来收集源。由于Gradle为这些目录提供了相同的优先级,如果在多个目录中定义相同的资源,则在合并资源时会出现错误。

    • 对于每个源集,只能指定一个AndroidManifest.xml。

    • 如果源集的所有文件都位于单个根目录下,则可以使用setRoot属性指定该目录。收集源集的源时,Gradle仅在相对于您指定的根目录的位置中查找。例如,在将配置应用于androidTest源集之后,Gradle仅在src/tests/java/ 目录中查找Java源。

3.5、Product flavors:

关于构建不同的Product flavors,可以参考如下:
Build Variants
技术:Android打包签名总结

4、Gradle模板举例

  • 4.1、Project的build.gradle

       // Top-level build file where you can add configuration options common to all sub-projects/modules.
       //全局引入gradle文件
       apply from: 'dependencies.gradle'
       
       //gradle脚本执行所需依赖,包含依赖仓库和插件
       buildscript {
       
           //配置远程仓库
           repositories {
               //从Android Studio3.0后新增了google()配置,可以引用google上的开源项目
               google()
               //一个类似于github的代码托管仓库,声明了jcenter()配置,可以轻松引用 jcenter上的开源项目
               jcenter()
       
               //本地仓库
               mavenLocal()
               //maven中央仓库
               mavenCentral()
               //指定maven仓库
               maven { url "https://jitpack.io" }
               maven { url 'https://maven.google.com' }
           }
       
           //配置插件
           dependencies {
               //此处是android的插件gradle,gradle是一个强大的项目构建工具
               classpath 'com.android.tools.build:gradle:3.2.1'
               //其他插件,这里是数据库greendao的插件
               classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
           }
       }
       
       //项目本身需要的依赖,比如项目所需的maven库
       allprojects {
           repositories {
               jcenter()
               google()
               maven { url 'https://jitpack.io' }
               maven { url 'https://maven.google.com' }
           }
       }
       
       //清除任务
       task clean(type: Delete) {
           delete rootProject.buildDir
       }
    
  • 4.2、Module的build.gradle

       //app应用插件,声明为Android应用程序
       apply plugin: 'com.android.application' //表示是一个应用程序模块,直接运行
       //apply plugin: 'com.android.library'   //表示是一个库模块,被其他module依赖
       
       apply plugin: 'walle'   //其他插件,这里是打包插件
       
       //定义变量
       def versionConfigs = rootProject.extensions.getByName("ext")
       
       android {
           //编译时用的Android版本
           compileSdkVersion versionConfigs.androidCompileSdkVersion
           //构建工具的版本
           buildToolsVersion versionConfigs.androidBuildToolsVersion
       
           //默认配置,会被下面的相同的配置覆盖
           defaultConfig {
               //项目包名,唯一标识,和AndroidManifest.xml中的package(Java文件包名)不同
               applicationId "com.example.app"
               //程序最低兼容的运行版本,低于此版本无法安装
               minSdkVersion versionConfigs.androidMinSdkVersion
               //项目的目标版本
               targetSdkVersion versionConfigs.androidTargetSdkVersion
       
               //版本号
               versionCode VERSION_CODE as int
               //版本名称,给用户看的
               versionName VERSION_NAME
       
               //支持multidex
       //        multiDexEnabled true
       
               //表明要使用AndroidJUnitRunner进行单元测试
               testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
           }
       
           //程序在编译的时候会检查lint,有任何错误提示会停止build,我们可以关闭这个开关
           lintOptions {
               //打包release版本的时候进行检测
               checkReleaseBuilds false
               //即使报错也不会停止打包
               abortOnError false
               //屏蔽translate引起的warning
               disable "MissingTranslation"
           }
       
           //自动化打包配置,可以自定义配置多种打包配置
           signingConfigs {
               //测试配置
               debug {
                   //debug签名文件路径
                   storeFile file("../debug.jks")
       
                   //签名相关信息,配置在了gradle.properties中了
                   storePassword SIGNINGCONFIGS_PSW
                   keyAlias SIGNINGCONFIGS_KEYALIAS
                   keyPassword SIGNINGCONFIGS_PSW
               }
       
               //上线配置
               release {
                   //release签名文件路径
                   storeFile file("../release.jks")
       
                   //签名相关信息,配置在了gradle.properties中了
                   storePassword SIGNINGCONFIGS_PSW
                   keyAlias SIGNINGCONFIGS_KEYALIAS
                   keyPassword SIGNINGCONFIGS_PSW
               }
           }
       
           //构建类型,可自定义多种类型
           buildTypes {
               debug {
                   minifyEnabled false      //是否混淆
                   shrinkResources false    //去除没有用到的资源文件
               }
       
               release {
                   minifyEnabled true
                   shrinkResources true
                   proguardFiles getDefaultProguardFile('proguard-android.txt'), "$rootDir/ModuleLibs/config/proguard-rules.pro"
                   signingConfig signingConfigs.release
               }
           }
       
           repositories {
               //添加本地aar目录
               flatDir {
                   dirs 'libs'
               }
           }
       
           //配置源集
           sourceSets {//目录指向配置
               main {
                   jniLibs.srcDirs = ['libs']//指定lib库目录
               }
           }
       
       }
       
       //依赖
       dependencies {
           //本地文件依赖
           api fileTree(include: ['*.jar'], dir: 'libs')
       
           //测试用例库
           testImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
               exclude group: 'com.android.support', module: 'support-annotations'
           })
       
           //依赖corelib的module
           api project(':corelib')
           
           //定义外部变量的引用
           def andlibs = rootProject.ext.andlibs
           def libs = rootProject.ext.libs
           api andlibs.appcompat_v7
       }
    

相关链接:
Android Plugin DSL Reference
Gradle Android插件用户指南翻译

深入理解Android之Gradle
史上最全Android build.gradle配置详解
Gradle 完整指南(Android)
Gradle 提速:每天为你省下一杯喝咖啡的时间

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

推荐阅读更多精彩内容

  • 在 Android Studio 构建的项目中,基于 Gradle 进行项目的构建,同时使用 Android DS...
    Ant_way阅读 7,327评论 0 16
  • 是什么? 在语法上是基于Groovy语言的(Groovy 是一种基于JVM的敏捷开发语言,可以简单的理解为强类型语...
    千山万水迷了鹿阅读 99,465评论 4 122
  • Gradle是一款非常优秀的构建系统工具,它的DSL基于Groovy实现,可以让你很方便的通过代码控制这些DSL来...
    飞雪无情flysnow_org阅读 3,507评论 0 35
  • 说明 本文主要介绍和Gradle关系密切、相对不容易理解的配置,偏重概念介绍。部分内容是Android特有的(例如...
    jzj1993阅读 15,573评论 1 62
  • 今天上午儿子在家把妹妹的红雨鞋复习完了一遍,I0点后去上英语课,在英语课上,儿子表现积极,得了最高分,6个...
    钧宇阅读 265评论 0 0