Gradle基本学习路线总结

Groovy :是一种动态语言。

1:这种语言比较有特点,它和 Java 一样,也运行于 Java 虚拟机中。简单粗暴点儿看,你可以认为 Groovy 扩展了 Java 语言,二者关系如下图

。这点和Kotlin很像都是将源文件,先转成jvm能识别的字节码文件。

2:除了语言和 Java 相通外,Groovy 有时候又像一种脚本语言。当执行 Groovy 脚本时,Groovy 会先将其编译成 Java 类字节码,然后通过 Jvm 来执行这个 Java 类。

一:Groovy 语言学习

1:Groovy语言特性

1.1:Groovy 注释标记和 Java 一样,支持//或者/**/

1.2:Groovy 语句可以不用分号结尾。

每行代码不用加分号外,Groovy 中函数调用的时候还可以不加括号,如

println("test") ---> println "test"

1.3 属性相关

Groovy 中支持动态类型,即定义变量的时候可以不指定其类型。Groovy 中,变量定义可以使用关键字 def。注意,虽然 def 不是必须的,但是为了代码清晰,建议还是使用 def 关键字

def variable1 = 1  //可以不使用分号结尾

def varable2 = "I am a person"

def  int x = 1  //变量定义时,也可以直接指定类型

1.4函数相关

1.4.1 函数定义时,参数的类型也可以不指定。比如

String testFunction(arg1,arg2){//无需指定参数类型...}

1.4.2:除了变量定义可以不指定类型外,Groovy 中函数的返回值也可以是无类型的。

可以不使用 return xxx 来设置 xxx 为函数返回值。如果不使用 return 语句的话,则函数里最后一句代码的执行结果被设置成返回值。比如:

//无类型的函数定义,必须使用 def 关键字

def getSomething(){

"getSomething return value" //如果这是最后一行代码,则返回类型为 String

1000 //如果这是最后一行代码,则返回类型为 Integer

}

//如果指定了函数返回类型,则可不必加 def 关键字来定义函数

String  getString(){return "I am a string"}

1.5 Groovy 中的数据类型

除了java定义的数据类型外,groovy有2种比较特殊的数据类型

1.5.1Groovy 中的容器类。(其实只是对java容器进行了拓展,相对比较好理解)

Groovy 中的容器类很简单,就三种:

List:链表,其底层对应 Java 中的 List 接口,一般用 ArrayList 作为真正的实现类。

Map:键-值表,其底层对应 Java 中的 LinkedHashMap。

Range:范围,它其实是 List 的一种拓展。

1.List 类

变量定义:List 变量由[]定义,比如

def aList = [5,'string',true] //List 由[]定义,其元素可以是任何对象

变量存取:可以直接通过索引存取,而且不用担心索引越界。如果索引超过当前链表长度,List 会自动 往该索引添加元素

assert aList[1] == 'string'

assert aList[5] == null //第 6 个元素为空

aList[100] = 100  //设置第 101 个元素的值为 10

assert aList[100] == 100

那么,aList 到现在为止有多少个元素呢?

println aList.size  ===>结果是 101

2.Map 类

容器变量定义

变量定义:Map 变量由[:]定义,比如

def aMap = ['key1':'value1','key2':true]

Map 由[:]定义,注意其中的冒号。冒号左边是 key,右边是 Value。key 必须是字符串,value 可以是任何对象。另外,key 可以用''或""包起来,也可以不用引号包起来。比如

def aNewMap = [key1:"value",key2:true] //其中的 key1 和 key2 默认被

处理成字符串"key1"和"key2" 不过 Key 要是不使用引号包起来的话,也会带来一定混淆,比如

def key1="wowo"

def aConfusedMap=[key1:"who am i?"]

aConfuseMap 中的 key1 到底是"key1"还是变量 key1 的值“wowo”?显然,答案是字符串"key1"。如果要是"wowo"的话,则 aConfusedMap 的定义必须设置成:

def aConfusedMap=[(key1):"who am i?"]

Map 中元素的存取更加方便,它支持多种方法:

println aMap.keyName    <==这种表达方法好像 key 就是 aMap 的一个成员变量一样

println aMap['keyName'] <==这种表达方法更传统一点

aMap.anotherkey = "i am map"  <==为 map 添加新元素

3.Range 类

Range 是 Groovy 对 List 的一种拓展,变量定义和大体的使用方法如下:

def aRange = 1..5  <==Range 类型的变量 由 begin 值+两个点+end 值表示

左边这个 aRange 包含 1,2,3,4,5 这 5 个值

如果不想包含最后一个元素,则

def aRangeWithoutEnd = 1..<5  <==包含 1,2,3,4 这 4 个元素

println aRange.from

println aRange.to

先定位到 Range 类。它位于 groovy.lang 包中:

有了 API 文档,你就可以放心调用其中的函数了。不过,不过,不过:我们刚才代码中用到了 Range.from/to 属性值,但翻看 Range API 文档的时候,其实并没有这两个成员变量。下图 是 Range 的方法

文档中并没有说明 Range 有 from 和 to 这两个属性,但是却有 getFrom 和 getTo 这两个函数。原来:

根据 Groovy 的原则,如果一个类中有名为 xxyyzz 这样的属性(其实就是成员变量),Groovy 会自动为它添加 getXxyyzz 和 setXxyyzz 两个函数,用于获取和设置 xxyyzz 属性值。

注意,get 和 set 后第一个字母是大写的

所以,当你看到 Range 中有 getFrom 和 getTo 这两个函数时候,就得知道潜规则下,Range 有 from 和 to 这两个属性。当然,由于它们不可以被外界设置,所以没有公开 setFrom 和 setTo 函数。

1.5.2 闭包(英文叫 Closure,是 Groovy 中非常重要的一个数据类型或者说一种概念了,脚本中最经常使用)

闭包,是一种数据类型,它代表了一段可执行的代码。其外形如下:

def aClosure = {//闭包是一段代码,所以需要用花括号括起来..

String param1, int param2 ->  //这个箭头很关键。箭头前面是参数定义,箭头后面是代码

println "this is code" //这是代码,最后一句是返回值,

//也可以使用 return,和 Groovy 中普通函数一样

}

简而言之,Closure 的定义格式是:

def xxx = {paramters -> code}  //或者

def xxx = {无参数,纯 code}  这种 case 不需要->符号

闭包中注意点a

如果闭包没定义参数的话,则隐含有一个参数,这个参数名字叫 it,和 this 的作用类似。it 代表闭包的参数。如项目中用到的


再如:

def greeting = { "Hello, $it!" }

assert greeting('Patrick') == 'Hello, Patrick!'

等同于:

def greeting = { it -> "Hello, $it!" }

assert greeting('Patrick') == 'Hello, Patrick!'

但是,如果在闭包定义时,采用下面这种写法,则表示闭包没有参数!

def noParamClosure = { -> true }

这个时候,我们就不能给 noParamClosure 传参数了!

noParamClosure ("test")  <==报错

注意点b

闭包在 Groovy 中大量使用,比如很多类都定义了一些函数,这些函数最后一个参数都是一个闭包。比如:

public static List each(List self, Closure closure)

上面这个函数表示针对 List 的每一个元素都会调用 closure 做一些处理。这里的 closure,就有点回调函数的感觉。但是,在使用这个 each 函数的时候,我们传递一个怎样的 Closure 进去?比如:

def iamList = [1,2,3,4,5]  //定义一个 List

iamList.each{  //调用它的 each,这段代码的格式看不懂了吧?each 是个函数,圆括号去哪了?

println it

}

特点经常出现(这就是1.2中指出的函数省略括号特性),因为以后在 Gradle 中经常会出现图 7 这样的代码:

经常碰见图 7 这样的没有圆括号的代码。省略圆括号虽然使得代码简洁,看起来更像脚本语言,以 doLast 为例,完整的代码应该按下面这种写法:

doLast({

println 'Hello world!'

})

有了圆括号,就知道 doLast 只是把一个 Closure 对象传了进去。很明显,它不代表这段脚本解析到 doLast 的时候就会调用 println 'Hello world!' 。但是把圆括号去掉后,就感觉好像 println 'Hello world!'立即就会被调用一样!

注意点c 如何确定 Closure 的参数

能写成下面这样吗?

iamList.each{String name,int x ->

return x

}  //运行的时候肯定报错!

所以,Closure 虽然很方便,但是它一定会和使用它的上下文有极强的关联。要不,作为类似回调这样的东西,我如何知道调用者传递什么参数给 Closure 呢?

此问题如何破解?只能通过查询 API 文档才能了解上下文语义。比如下图 :

图 中:

each 函数说明中,将给指定的 closure 传递 Set 中的每一个 item。所以,closure 的参数只有一个。

findAll 中,绝对抓瞎了。一个是没说明往 Closure 里传什么。另外没说明 Closure 的返回值是什么.....。

对 Map 的 findAll 而言,Closure 可以有两个参数。findAll 会将 Key 和 Value 分别传进去。并且,Closure 返回 true,表示该元素是自己想要的。返回 false 表示该元素不是自己要找的。示意代码如图  所示:

Closure 的使用有点坑,很大程度上依赖于你对 API 的熟悉程度,所以最初阶段,SDK 查询是少不了的

1.5.3 String 特点

Groovy 对字符串支持相当强大,充分吸收了一些脚本语言的优点:

1  单引号''中的内容严格对应 Java 中的 String,不对$符号进行转义

def singleQuote='I am $ dolloar'  //输出就是 I am $ dolloar

2  双引号""的内容则和脚本语言的处理有点像,如果字符中有$号的话,则它会$表达式先求值。

def doubleQuoteWithoutDollar = "I am one dollar" //输出 I am one dollar

def x = 1

def doubleQuoteWithDollar = "I am $x dolloar" //输出 I am 1 dolloar

3 三个引号'''xxx'''中的字符串支持随意换行 比如

def multieLines = ''' begin

line  1

line  2

end '''

NOTE:这就是为什么gradle脚本中如果 我们想用$占位符来输入版本时,必须用双引号testCompile "com.meiyou:peroid.base:${PERIOD_BASE_VERSION}" ,而如果强制指定版本,单引号和双引号都可以

1.6 Groovy脚本

Groovy 中可以像 Java 那样写 package,然后写类。比如在文件夹 com/cmbc/groovy/目录中放一个文件,叫 Test.groovy,如图 所示:

如果不声明 public/private 等访问权限的话,Groovy 中类及其变量默认都是 public 的,这点与java有所不同。

Java 中,我们最熟悉的是如上图。但是我们在 Java 的一个源码文件中,不能不写 class(interface 或者其他....),而 Groovy 可以像写脚本一样,把要做的事情都写在 xxx.groovy 中,而且可以通过 groovy xxx.groovy 直接执行这个脚本,如


test.groovy 的运行结果是:

println 'Groovy world!'

Groovy 把它转换成这样的 Java 类,下面将进行验证

执行 groovyc -d classes test.groovy

groovyc 是 groovy 的编译命令,-d classes 用于将编译得到的 class 文件拷贝到 classes 文件夹下

是 test.groovy 脚本转换得到的 java class。用 jd-gui 反编译它的代码:

2:Groovy常用API介绍(Groovy 的 API 文档位于http://www.groovy-lang.org/api.html

2.1文件 I/O 操作

从 Groovy 的文件 I/O 操作掌握下Groovy常用api。比 Java 看起来简单,但要理解起来其实比较难。尤其是当你要自己查 SDK 并编写代码的时候。

整体说来,Groovy 的 I/O 操作是在原有 Java I/O 操作上进行了更为简单方便的封装,并且使用 Closure 来简化代码编写。主要封装了如下一些了类:

2.1.1.读文件

Groovy 中,文件读操作简单到令人发指:

def targetFile = new File(文件名) <==File 对象还是要创建的。

然后打开http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.html看看 Groovy 定义的 API:

1 读该文件中的每一行:eachLine 的唯一参数是一个 Closure。Closure 的参数是文件每一行的内容

其内部实现肯定是 Groovy 打开这个文件,然后读取文件的一行,然后调用 Closure...

targetFile.eachLine{

String oneLine ->

println oneLine

2 直接得到文件内容

targetFile.getBytes() <==文件内容一次性读出,返回类型为 byte[] 注意前面提到的 getter 和 setter 函数,这里可以直接使用 targetFile.bytes //....

3 使用 InputStream.InputStream 的 SDK 在http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html

def ism =  targetFile.newInputStream()

//操作 ism,最后记得关掉

ism.close

4 使用闭包操作 inputStream,以后在 Gradle 里会常看到这种搞法

targetFile.withInputStream{ ism ->

操作 ism. 不用 close。Groovy 会自动替你 close

}

确实够简单,令人发指。我当年死活也没找到 withInputStream 是个啥意思。所以,请各位开发者牢记 Groovy I/O 操作相关类的 SDK 地址:

java.io.File:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.htmljava.io.InputStream:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html

java.io.OutputStream:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/OutputStream.htmljava.io.Reader:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Reader.htmljava.io.Writer:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Writer.htmljava.nio.file.Path:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/nio/file/Path.html

2.2.2.写文件

和读文件差不多。不再啰嗦。这里给个例子,如何 copy 文件。

def srcFile = new File(源文件名)

def targetFile = new File(目标文件名)

targetFile.withOutputStream{ os->

srcFile.withInputStream{ ins->

os << ins  //利用 OutputStream 的<<操作符重载,完成从 inputstream 到 OutputStream

//的输出

}

}

关于 OutputStream 的<<操作符重载,查看 SDK 文档后可知:

...

NOTE:Groovy及gradle中的 << 并不是类似 C++中的插入运算符,而是相应对象的leftShift函数。如项目中


其实是做了两件事(将在gradle部分析)

1:调用project的task函数为project增加一个task 

2:调用 task对象的leftShift函数,传入一个clouse作为参数,而task的leftShift函数其实和dolast是一个作用

所以不同对象的不同<<符号是不一样的

XML 操作

除了 I/O 异常简单之外,Groovy 中的 XML 操作也极致得很。Groovy 中,XML 的解析提供了和 XPath 类似的方法,名为 GPath。这是一个类,提供相应 API。关于 XPath,请脑补https://en.wikipedia.org/wiki/XPath

GPath 功能包括:给个例子好了,来自 Groovy 官方文档。

test.xml 文件:

Don Xijote

Manuel De Cervantes

Catcher in the Rye

JD Salinger

Alice in Wonderland

Lewis Carroll

Don Xijote

Manuel De Cervantes

现在来看怎么玩转 GPath:

//第一步,创建 XmlSlurper 类

def xparser = new XmlSlurper()

def targetFile = new File("test.xml")

//轰轰的 GPath 出场

GPathResult gpathResult = xparser.parse(targetFile)

//开始玩 test.xml。现在我要访问 id=4 的 book 元素。

//下面这种搞法,gpathResult 代表根元素 response。通过 e1.e2.e3 这种

//格式就能访问到各级子元素....

def book4 = gpathResult.value.books.book[3]

//得到 book4 的 author 元素

def author = book4.author

//再来获取元素的属性和 textvalue

assert author.text() == ' Manuel De Cervantes '

获取属性更直观

author.@id == '4' 或者 author['@id'] == '4'

属性一般是字符串,可通过 toInteger 转换成整数

author.@id.toInteger() == 4

使用 Gradle 的时候有个需求,就是获取 AndroidManifest.xml 版本号(versionName)。有了 GPath,一行代码搞定,请看:

def androidManifest = new XmlSlurper().parse("AndroidManifest.xml")

println androidManifest['@android:versionName']

或者

println androidManifest.@'android:versionName'

groovy既然是一门编程语言,就能做很多事,本文只是列举常用及学习gradle中部分必须了解的知识

二:gradle相关学习

 Gladle 可以理解成基G于Groovy的一套框架,同时也是DSL。 

1:框架中常用概念介绍

https://docs.gradle.org/current/dsl/

https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html

这2个文档非常重要重点,介绍了gradle框架学习中必须掌握的DS。

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

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

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

Settings 对象:显然,每一个 settings.gradle 都会转换成一个 Settings 对象。

脚本中除了可以用到的delegate object外还有Scriptinterface 的api,因为:



而具体一个脚本中能有多少方法及多少属性可用,主要来源于以下几个方面



每一个对象都有对应的API,具体可以点开 api文档进行查阅。

2:gradle命令介绍 

点开AS gradle窗口即可看到目前项目中能执行的task(当然也可执行tasks任务查看。注:不包含引入的插件所 定义的可执行task,如需查看所有需加上all参数执行),执行gradle +taskName或者右键点击对应task即可执行对应task ,如执行projects,即可看到项目相应工程信息


3:gradle工作流程

Gradle 工作大概包含三个阶段:

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

Initiliazation phase 的下一个阶段是 Configration 阶段。

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

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

4:gradle常用api介绍(官方文档位置 https://docs.gradle.org/current/javadoc/)

app 有一个特点。它有三个版本,分别是 debug、release 和 demo。这三个版本对应的代码都完全一样,但是在运行的时候需要从 assets/runtime_config 文件中读取参数。参数不同,则运行的时候会跳转到 debug、release 或者 demo 的逻辑上。

引入 gradle 后,我们该如何处理呢?

解决方法是:在编译 build、release 和 demo 版本前,在 build.gradle 中自动设置 runtime_config 的内容。代码如下所示:

[build.gradle]

apply plugin: 'com.android.application'  //加载 APP 插件

//加载 utils.gradle

apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"

//buildscript 设置 android app 插件的位置

buildscript {

repositories { jcenter() }

dependencies { classpath 'com.android.tools.build:gradle:1.2.3' }

}

//android ScriptBlock

android {

compileSdkVersion gradle.api

buildToolsVersion "22.0.1"

sourceSets{ //源码设置 SB

main{

manifest.srcFile 'AndroidManifest.xml'

jni.srcDirs = []

jniLibs.srcDir 'libs'

aidl.srcDirs=['src']

java.srcDirs=['src']

res.srcDirs=['res']

assets.srcDirs = ['assets'] //多了一个 assets 目录

}

}

signingConfigs {//签名设置

debug {  //debug 对应的 SB。注意

if(project.gradle.debugKeystore != null){

storeFile file("file://${project.gradle.debugKeystore}")

storePassword "android"

keyAlias "androiddebugkey"

keyPassword "android"

}

}

}

/*

最关键的内容来了: buildTypes ScriptBlock.

buildTypes 和上面的 signingConfigs,当我们在 build.gradle 中通过{}配置它的时候,

其背后的所代表的对象是 NamedDomainObjectContainer 和

NamedDomainObjectContainer

注意,NamedDomainObjectContainer是一种容器,

容器的元素是 BuildType 或者 SigningConfig。我们在 debug{}要填充 BuildType 或者

SigningConfig 所包的元素,比如 storePassword 就是 SigningConfig 类的成员。而 proguardFile 等

是 BuildType 的成员。

那么,为什么要使用 NamedDomainObjectContainer 这种数据结构呢?因为往这种容器里

添加元素可以采用这样的方法: 比如 signingConfig 为例

signingConfig{//这是一个 NamedDomainObjectContainer

test1{//新建一个名为 test1 的 SigningConfig 元素,然后添加到容器里

//在这个花括号中设置 SigningConfig 的成员变量的值

}

test2{//新建一个名为 test2 的 SigningConfig 元素,然后添加到容器里

//在这个花括号中设置 SigningConfig 的成员变量的值

}

}

在 buildTypes 中,Android 默认为这几个 NamedDomainObjectContainer 添加了

debug 和 release 对应的对象。如果我们再添加别的名字的东西,那么 gradle assemble 的时候

也会编译这个名字的 apk 出来。比如,我添加一个名为 test 的 buildTypes,那么 gradle assemble

就会编译一个 xxx-test-yy.apk。在此,test 就好像 debug、release 一样。

*/

buildTypes{

debug{ //修改 debug 的 signingConfig 为 signingConfig.debug 配置

signingConfig signingConfigs.debug

}

demo{ //demo 版需要混淆

proguardFile 'proguard-project.txt'

signingConfig signingConfigs.debug

}

//release 版没有设置,所以默认没有签名,没有混淆

}

......//其他和 posdevice 类似的处理。来看如何动态生成 runtime_config 文件

def  runtime_config_file = 'assets/runtime_config'

/*

我们在 gradle 解析完整个任务之后,找到对应的 Task,然后在里边添加一个 doFirst Action

这样能确保编译开始的时候,我们就把 runtime_config 文件准备好了。

注意,必须在 afterEvaluate 里边才能做,否则 gradle 没有建立完任务有向图,你是找不到

什么 preDebugBuild 之类的任务的

*/

project.afterEvaluate{

//找到 preDebugBuild 任务,然后添加一个 Action

tasks.getByName("preDebugBuild"){

it.doFirst{

println "generate debug configuration for ${project.name}"

def configFile = new File(runtime_config_file)

configFile.withOutputStream{os->

os << I am Debug\n'  //往配置文件里写 I am Debug

}

}

}

//找到 preReleaseBuild 任务

tasks.getByName("preReleaseBuild"){

it.doFirst{

println "generate release configuration for ${project.name}"

def configFile = new File(runtime_config_file)

configFile.withOutputStream{os->

os << I am release\n'

}

}

}

//找到 preDemoBuild。这个任务明显是因为我们在 buildType 里添加了一个 demo 的元素

//所以 Android APP 插件自动为我们生成的

tasks.getByName("preDemoBuild"){

it.doFirst{

println "generate offlinedemo configuration for ${project.name}"

def configFile = new File(runtime_config_file)

configFile.withOutputStream{os->

os << I am Demo\n'

}

}

}

}

}

.....//copyOutput

三:android中由于一般使用的是'com.android.application' 和 'com.android.library' 两个插件,所以还需要去了解这2个插件中的api

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

推荐阅读更多精彩内容

  • 导语: 随着技术的发展,不管是前端开发、服务端开发或者是移动端开发(移动也是前端的一个分支)中都会用到自动化构建工...
    伊始雨深阅读 2,919评论 0 4
  • 什么是 Groovy? 简言之,Groovy是一种基于JVM(Java虚拟机)的敏捷动态开发语言。它是一种成熟的面...
    北纬26阅读 4,054评论 0 14
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,036评论 18 139
  • 参考资料:http://gold.xitu.io/post/580c85768ac247005b5472f9htt...
    zhaoyubetter阅读 10,862评论 0 6
  • Android Studio作为Android应用开发的官方IDE,默认使用Gradle作为构建工具,所以对于An...
    feil0n9wan9阅读 1,570评论 1 6