preface
To be a good Android developer, you need a complete set ofThe knowledge systemHere, let us grow together into what we think ~.
Groovy’s importance as the language at the heart of Gradle’s powerful building tool is self-evident, but Groovy itself is so complex that IT would take more than a few dozen swastikas to fully grasp it. Fortunately, the knowledge of Groovy in the Gradle world is very basic, so the purpose of this article is to lay a foundation for further exploration of Gradle.
First acquaintance with DSL
Domain Specific Languages (DSLS), such as Matliba, UML, HTML, XML, and so on. Groovy is a branch of DSL.
The characteristics of
- 1) Solve domain specific problems.
- 2) It goes to two extremes with system programming language. System programming language hopes to solve all problems. For example, Java language hopes to do Android development and server development, and it has the feature of horizontal expansion. DSLS have the ability to solve domain-specific problems in vertical depth.
In general, the core idea of DSLS is to solve domain-specific problems by focusing on specialization rather than completeness.
First acquaintance with Groovy
1. Groovy features
Groovy has the following three characteristics:
- Groovy is an agile development language based on the JVM.
- 2) Groovy combines many powerful features of Python, Ruby, and Smalltalk scripting languages.
- 3) Groovy can integrate perfectly with Java and use all Java libraries.
So why make Grvooy when other scripting languages already exist?
Because Groovy is very cheap to get started with compared to other programming languages, and because its syntax is an extension of Java, we can learn Groovy the same way we learn Java.
2. Features of the Groovy language itself
Its characteristics are mainly as follows:
- 1) Syntactically support dynamic type, closure and other new language features. Also, closures for the Groovy language are more powerful than closures for all other language types.
- 2) It seamlessly integrates all existing Java libraries because it is based on the JVM.
- 3) It supports both object-oriented programming (Java-based extensions) and procedural programming (a combination of many scripting languages).
Note that when we use Groovy for Gradle scripting, we use procedural programming.
3. Groovy’s advantages
Groovy has four advantages:
- 1) It is a more agile programming language: besides a lot of syntactic sugar in building syntactically, much of the code that needs to be written in the Java layer can be omitted in Groovy. As a result, we can do more with less code.
- 2) Simple to get started, but very powerful.
- 3) It can be used as both a programming language and a scripting language
- 4) Groovy will be very easy for those familiar with Java.
4. Groovy package structure
Groovy official website
Once you’ve downloaded the Groovy files from the official website, you can see the Groovy directory structure, where you’ll need to focus on the bin and doc folders.
Bin folder
The next three important executable files we need to know about in the bin folder are as follows:
- Groovy commands are similar to Java commands in Java and are used to execute Groovy Class bytecode files.
- 2) The Groovyc command is similar to the Javac command in Java and is used to compile Groovy source files into Groovy bytecode files.
- 3) The Groovysh command explains the execution of Groovy script files.
Doc folder
Under the doc folder there is an HTML file, where the API and documentation we need to focus on are as follows:
- API:A set of apis and documentation are provided for us in Groovy.
- Documentation:Here are some tutorials from Groovy.
5. Keywords in Groovy
Here are all the keywords in Groovy, especially when naming them, as follows:
as,assert,break,case,catch,class,const,continue,def,default,do,else,enum,extends,false,finally,for,goto,if,implements,import,in,instanceof,interface,new,null,package,return,super,switch,this,throw,throws,trait,true,try,while
Copy the code
6. Groovy && Java difference learning
Getter/setter
For each field, Groovy automatically creates corresponding getters and setters that can be called directly from the outside, And when you get a value using Object.fielda or assign a value using Object.fielda = value, the object.getFielda () and Object.setfielda (value) methods are actually automatically called instead.
We can use the.@ direct domain access operator if we don’t want to call this particular getter method.
2) In addition to no bonus points per line of code, Groovy also allows function calls without parentheses.
Note that we can use this function without parentheses if it is a common Groovy or Gradle API function, such as println. Otherwise, keep the parentheses. Otherwise, Groovy might confuse properties with function calls.
3) Groovy statements can be terminated without semicolons.
4) When a function is defined, the type of the parameter may not be specified.
5) The return value of Groovy functions can also be untyped, and functions that do not return types are internally treated as returning Object types.
If the function does not return a value using the return keyword, the default value is null, but the def keyword must be used.
In Groovy, you can omit.class for all Class types.
In Groovy, == equals to Java. If you want to compare two objects to be the same, use.is().
Groovy non-operators are as follows:
assert (!"android") = =false
Copy the code
Groovy supports the ** operator as follows:
assert 2六四屠杀3= =8
Copy the code
11) Judge whether it is true more succinctly:
if (android) {}
Copy the code
12) Ternary expressions can be more concise:
// omit name
defresult = name ? :"Unknown"
Copy the code
13) concise non-null judgment
println order? .customer? .addressCopy the code
Use assert to set assertions. When the assert condition is false, the program will throw an exception.
15) We can use the Number class to replace float, double, and so on.
16) The switch method can support more parameter types simultaneously.
Note that SWCTCH can match any element in the list, as shown in the sample code below:
/ / output is ok
def num = 5.21
switch (num) {
case [5.21.4."list"] :return "ok"
break
default:
break
}
Copy the code
Groovy basic syntax
Groovy’s basic syntax can be broken down into four main parts:
- 1) Groovy’s core basic syntax.
- 2) Groovy closures.
- 3) Groovy data structures.
- 4) Groovy is object-oriented
1. Groovy core basic syntax
Variables in Groovy
Variable types
In Groovy, as in Java, there are two types:
- 1) Basic types.
- 2) Object type.
Groovy is a dynamic language in which everything is an object, just like Python and Kotlin: all primitive types are object types. To verify this Case, we can create a new Groovy file, create a variable of type int and print it, which will result in ‘class java.lang.Integer’, so we can verify that we are right. In fact, Groovy’s compiler wraps all primitive types as object types.
Variable definitions
Groovy variables are defined quite differently from the way they are defined in Java. For Groovy, there are two ways to define variables, as follows:
- 1) Strongly typed: Groovy, like Java, allows you to define strongly typed variables, such as x, as an int. This is called strongly typed.
- 2) Weak type definition: we do not need to specify the type in advance like strong type. Instead, we use the def keyword to define any of our variables, because the compiler automatically assigns values based on the type of values.
So, in what scenarios should these two approaches be used?
If the variable is intended for the current class or file, and not for another class or application module, then def is recommended, because weak typing is sufficient in this scenario.
However, if you want to use a class or variable for another module, it is recommended not to use def. Instead, you should use Java’s strong type definition method, because with strong type definition, it cannot be dynamically converted to another type. It can guarantee that the value passed in from the outside is correct. If your variable is to be used by the outside world, and you use def to define it, what is the right thing for the outside world to pass you? This can confuse the caller.
If we change x1 to String in the following code, x1 will be inferred to String by the compiler, and we can guess that the variable defined using the def keyword is Obejct.
Strings in Groovy
Strings in Groovy are quite different from strings in Java, so we’ll focus on that here.
In addition to inherits Java’s traditional String usage, Groovy has added a new GString type, which can be used in at least seven or eight ways, but is commonly defined in three ways. In addition, a new set of operators have been added to GString to make it easier to manipulate String variables. Finally, there is a new set of useful apis in GString that we need to focus on.
There are three commonly used string definitions in Groovy
There are three common ways to define strings in Groovy, as follows:
- 1) string defined by single quotation marks
- 2) string defined by double quotes “”
- 3), a string defined by triple quotes ‘”” “”
First, it’s important to note that ‘either single, double, or triple quotes are of type java.lang.string’.
So what’s the difference between a single quote and a triple quote?
In fact, it is not true. We need to add ” when we write a single quoted string with an escape character, and forcing a single quoted string into multiple lines becomes a string concatenation of ‘+’ signs when the string needs to be multi-line formatted.
What’s the difference between a variable defined by a double quote and a single or triple quote?
Unlike single and triple quotes, double quotes define an extensible variable. Let’s take a look at the two ways in which double quotes are used, as shown below:
In the figure above, the first name String is a regular String, while the sayHello String is an extensible String because it refers to the name variable as ‘${name}’. And the output can be seen from its final type, extensible type is’ org. Codehaus. Groovy. Runtime. GStringImpl ‘type.
Note that extensible strings can be extended into arbitrary expressions, such as mathematical operations, such as the sum variable in the figure above.
With Groovy’s extensible strings, we can avoid concatenation of strings in Java and improve the runtime performance of Java programs.
So, since there are two types of strings, String and GString, do they need to be strong-cast before they are assigned to each other?
No, the compiler automatically converts between String and GString, so we don’t have to worry too much about the differences when we write.
2. Groovy Closures
The essence of a closure is a code block. The core content of a closure can be summarized as follows:
- 1) Closure concept
- define
- Closure calls
- 2) Closure parameters
- Common parameters
- Implicit parameter
- 3) Closure return value
- There is always a return value
Closure calls
clouser.call()
clouser()
def xxx = { paramters -> code }
defXXX = {pure code}Copy the code
From a C/C++ language perspective, closures are similar to function Pointers. Closures can be called with the.call method, or their constructor can be called directly, as follows:
Closure object. call(parameter) Closure object (parameter)Copy the code
If the closure does not define a parameter, there is an implicit parameter called it, which is similar to the function of this. It stands for closure parameters. Example code for a closure with no arguments:
def noParamClosure = { -> true }
Copy the code
Note: Omit the parentheses
The last argument to the function is a closure, similar to the use of the callback function, as follows:
task JsonChao {
doLast ({
println "love is peace~"}})// It seems as if doLast will execute immediately
task JsonChao {
doLast {
println "love is peace~"}}Copy the code
The use of closures
There are four common uses for closures:
- 1) Use in combination with basic types.
- 2) in combination with the String class.
- 3) Combination with data structure.
- 4), combined with documents, etc.
The closure in order
- 1) Key variables of closures
- this
- owner
- delegate
- 2) Closure delegate strategy
Key variables for closures
This and owner and delegate
The difference codes are as follows:
def scrpitClouser = {
// represents the class at the closure definition
printlin "scriptClouser this:" + this
// represents the class or object at the closure definition
printlin "scriptClouser this:" + owner
// Represents any object. Default is the same as ownner
printlin "scriptClouser this:" + delegate
}
// Outputs are scrpitClouse objects
scrpitClouser.call()
def nestClouser = {
def innnerClouser = {
// represents the class at the closure definition
printlin "scriptClouser this:" + this
// represents the class or object at the closure definition
printlin "scriptClouser this:" + owner
// Represents any object, default with ownner always
printlin "scriptClouser this:" + delegate
}
innnerClouser.call()
}
// This outputs a nestClouser object, while owner and delegate outputs innnerClouser objects
nestClouser.call()
Copy the code
As you can see, if we define a closure directly within a class, method, or variable, the values of all three key variables are the same. However, if we nested a closure within the closure, the values of this, owner, and delegate are not the same. In other words, this will also point to the class or instance itself at our closure definition, and the Owner and delegate will point to the closure object closest to it.
Delegate vs. this and owner
The difference codes are as follows:
def nestClouser = {
def innnerClouser = {
// represents the class at the closure definition
printlin "scriptClouser this:" + this
// represents the class or object at the closure definition
printlin "scriptClouser this:" + owner
// Represents any object. Default is the same as ownner
printlin "scriptClouser this:" + delegate
}
Change the default delegate
innnerClouser.delegate = p
innnerClouser.call()
}
nestClouser.call()
Copy the code
As you can see, the delegate value can be changed, and the delegate value will be different from ownner’s value only if we change the delegate value.
Delegate strategy for closures
The sample code is shown below:
def stu = new Student()
def tea = new Teacher()
stu.pretty.delegate = tea
// In order for the pretty Closure's delegate changes to take effect, you must select its delegate policy as Closure.DELEGATE_ONLY, which defaults to Closure.OWNER_FIRST.
stu.pretty.resolveStrategy = Closure.DELEGATE_ONLY
println stu.toString()
Copy the code
Note that for the delegate changes to the pretty Closure described above to take effect, you must select its delegate policy as Closure.DELEGATE_ONLY, which is the default for Closure.OWNER_FIRST.
3. Groovy data structures
There are four commonly used data structures in Groovy:
- 1) Array
- 2), the List
- 3), the Map
- 4), the Range
The use of arrays is similar to the Java language, with perhaps the biggest difference being the extension of the definition, as shown in the following code:
// Array definition
def array = [1.2.3.4.5] as int[]
int[] array2 = [1.2.3.4.5]
Copy the code
Now, let’s look at three other data structures.
1, the List
The List is a linked List whose underlying interface corresponds to the List interface in Java. Generally, ArrayList is used as the real implementation class. The List variable is defined by [], and its elements can be any object.
Elements in a linked list can be accessed by index without fear of index crossing. If the index exceeds the current List length, the List automatically adds elements to the index. Let’s look at some of the most commonly used operations on List.
1) Sorting
def test = [100."hello".true]
// A left shift adds a new element to the List
test << 200
/ / the list definition
def list = [1.2.3.4.5]
/ / sorting
list.sort()
// Use your own sorting rulesSortlist.sort {a, b -> a == b?0 :
Math.abs(a) < Math.abs(b) ? 1 : - 1
}
Copy the code
2) add
/ / add
list.add(6)
list.leftShift(7)
list << 8
Copy the code
3) Delete
/ / delete
list.remove(7)
list.removeAt(7)
list.removeElement(6)
list.removeAll { return it % 2= =0 }
Copy the code
4) Search
/ / to find
int result = findList.find { return it % 2= =0 }
def result2 = findList.findAll { return it % 2! =0 }
def result3 = findList.any { return it % 2! =0 }
def result4 = findList.every { return it % 2= =0 }
Copy the code
5) Obtain the minimum and maximum value
// Minimum value, maximum value
list.min()
list.max(return Math.abs(it))
Copy the code
6) Count the quantity that meets the conditions
// Count the number of conditions
def num = findList.count { return it >= 2 }
Copy the code
Map
Represents a key-value table, whose underlying counterpart is the LinkedHashMap in Java.
The Map variable is defined by [:], with key to the left and Value to the right of the colon. Key must be a string and value can be any object. In addition, the key can be wrapped in ‘or “, or without quotation marks. Let’s take a look at some of the most commonly used operations for Map.
1) Access
The sample code is shown below:
aMap.keyName
aMap['keyName']
aMap.anotherkey = "i am map"
aMap.anotherkey = [a: 1.b: 2]
Copy the code
2), each method
If the closure we pass is a parameter, it takes Entry as an argument. If we pass a closure with two arguments, it takes key and value as arguments.
def result = ""
[a:1.b:2].each { key, value ->
result += "$key$value"
}
assert result == "a1b2"
def socre = ""
[a:1.b:2].each { entry ->
result += entry
}
assert result == "a=1b=2"
Copy the code
3) eachWithIndex method
If the closure takes two parameters, map.entry and the item’s index (a zero-based counter) are passed; Otherwise, if the closure takes three parameters, the key, value, and index are passed.
def result = ""
[a:1.b:3].eachWithIndex { key, value, index -> result += "$index($key$value)" }
assert result == "0(a1)1(b3)"
def result = ""
[a:1.b:3].eachWithIndex { entry, index -> result += "$index($entry)" }
assert result == "0(a=1)1(b=3)"
Copy the code
4) groupBy method
To group the closures according to their conditions, the code looks like this:
def group = students.groupBy { def student ->
return student.value.score >= 60 ? 'pass' : 'Fail'
}
Copy the code
5) findAll method
It takes two arguments, and findAll passes in the Key and the Value. Also, if Closure returns true, the element is what you want, and if false, it is not what you are looking for.
Range
Range, which is actually an extension of List. It is represented by the begin value + two dots + end value. If you do not want to include the last element, the begin value + two dots + < + end is indicated. We can obtain the corresponding boundary elements by arange. from and arange. to.
If you need to learn more about how to manipulate data structures, you can refer directly to the Groovy API documentation.
4. Groovy is object-oriented
Groovy classes and their variables are public by default if you don’t declare public/private access.
1) Metaprogramming (Groovy runtime)
The Groovy runtime logic flow diagram looks like this:
To better illustrate the use of metaprogramming, we will first create a Person class and call its CRY method as follows:
// In the first Groovy file
def person = new Person(name: 'Qndroid'.age: 26)
println person.cry()
// In the second groovy file
class Person implements Serializable {
String name
Integer age
def increaseAge(Integer years) {
this.age += years
}
/** * if a method is not found, it is called instead of * @param name * @param args * @return */
def invokeMethod(String name, Object args) {
return "the method is ${name}, the params is ${args}"
}
def methodMissing(String name, Object args) {
return "the method ${name} is missing"}}Copy the code
To implement metaprogramming, we need to use metaClass, as shown in the following example:
ExpandoMetaClass.enableGlobally()
// Add an attribute to the class dynamically
Person.metaClass.sex = 'male'
def person = new Person(name: 'Qndroid'.age: 26)
println person.sex
person.sex = 'female'
println "the new sex is:" + person.sex
// Dynamically add methods to classes
Person.metaClass.sexUpperCase = { -> sex.toUpperCase() }
def person2 = new Person(name: 'Qndroid'.age: 26)
println person2.sexUpperCase()
// Add static methods to the class dynamically
Person.metaClass.static.createPerson = {
String name, int age -> new Person(name: name, age: age)
}
def person3 = Person.createPerson('renzhiqiang'.26)
println person3.name + " and " + person3.age
Copy the code
It is important to note that the method of adding an element through the metaClass of the class needs to be added again each time it is used. Fortunately, we can call globally valid handling before injection, as shown below:
ExpandoMetaClass.enableGlobally()
// We can add methods to third-party classes during application initialization
Person.metaClass.static.createPerson = { String name,
int age ->
new Person(name: name, age: age)
}
Copy the code
2) Variables and scopes in scripts
For each Groovy script, it generates a static void main function, which calls a run function, and all the code in the script is contained within the run function. You can use the groovyc command to copy the compiled class files to the Classes folder:
Groovyc is a groovy compile command, and -d classes is used to copy compiled class files into the classes folder
groovyc -d classes test.groovy
Copy the code
When we define a variable in a Groovy script, it is not accessible to other methods in the script or other scripts because it is actually created in the run function. At this point, we need to mark the current variable as a member variable using @field, which looks like this:
import groovy.transform.Field;
@Field author = JsonChao
Copy the code
Iv. Document processing
1. Routine document processing
1) Read the file
EachLine method
We can use the eachLine method to read eachLine of the file and its only argument is a Closure that takes the contents of eachLine of the file. The sample code looks like this:
def file = newEachLine {String oneLine -> println oneLine}def text = file.getText()
def text2 = file.readLines()
file.eachLine { oneLine, lineNo ->
println "${lineNo} ${oneLine}"
}
Copy the code
We can then use ‘targetfile.bytes’ to get the contents of the file directly.
Using InputStream
Alternatively, we can stream files, as shown in the following code:
// Open the ISM and close it
def ism = targetFile.newInputStream()
// do sth
ism.close
Copy the code
Operate inputStream using closures
Using closures to operate on inputStream is more powerful, and is recommended as follows:
targetFile.withInputStream{ ism ->
// Do not use close to operate the ISM. Groovy will automatically close for you
}
Copy the code
2) Write a document
About writing files there are two common forms of operation, namely through withOutputStream/withInputStream or withReader/withWriter writing. The sample code looks like this:
With withOutputStream/, withInputStream copy file
def srcFile = newFile(source File name)def targetFile = newThe File (the target File name) targetFile. WithOutputStream {OS - > srcFile. WithInputStream {ins - > OS < < insOutput from inputStream to OutputStream // using the << operator overload of OutputStream}}Copy the code
WithReader, withWriter copy file
def copy(String sourcePath, String destationPath) {
try {
// Create the target file first
def desFile = new File(destationPath)
if(! desFile.exists()) { desFile.createNewFile() }//开始copy
new File(sourcePath).withReader { reader ->
def lines = reader.readLines()
desFile.withWriter { writer ->
lines.each { line ->
writer.append(line + "\r\n")}}}return true
} catch (Exception e) {
e.printStackTrace()
}
return false
}
Copy the code
In addition, we also can be read by withObjectOutputStream/withObjectInputStream to preserve and Object. The sample code looks like this:
Save the corresponding Object to the file
def saveObject(Object object, String path) {
try {
// Create the target file first
def desFile = new File(path)
if(! desFile.exists()) { desFile.createNewFile() } desFile.withObjectOutputStream { out -> out.writeObject(object) }return true
} catch (Exception e) {
}
return false
}
Copy the code
Read an Object from a file
def readObject(String path) {
def obj = null
try {
def file = new File(path)
if (file == null| |! file.exists())return null
// Read objects from files
file.withObjectInputStream { input ->
obj = input.readObject()
}
} catch (Exception e) {
}
return obj
}
Copy the code
2. XML file operation
1) Get XML data
First, we define a string containing XML data, as follows:
final String xml =
</author> </book> <book available="13" ID ="3">
<author ID ="4">
Vue
'''
Copy the code
We can then use XmlSlurper to parse this XML data as follows:
def xmlSluper = new XmlSlurper()
def response = xmlSluper.parseText(xml)
// Get a specific attribute value by specifying a label
println response.value.books[0].book[0].title.text()
println response.value.books[0].book[0].author.text()
println response.value.books[1].book[0].@available
def list = []
response.value.books.each { books ->
// Start traversing the book node
books.book.each { book ->
def author = book.author.text()
if (author.equals('李刚')) {
list.add(book.title.text())
}
}
}
println list.toListString()
Copy the code
2) Two traversal methods to obtain XML data
There are two traversal methods for retrieving XML data: deep traversal of XML data and wide traversal of XML data. Let’s take a look at their respective uses, as follows:
Deep traversal of XML data
def titles = response.depthFirst().findAll { book ->
return book.author.text() == '李刚' ? true : false
}
println titles.toListString()
Copy the code
Breadth traverses XML data
def name = response.value.books.children().findAll { node ->
node.name() == 'book' && node.@id= ='2'
}.collect { node ->
return node.title.text()
}
Copy the code
In practice, we can use XmlSlurper to get the versionName of androidmanifest.xml as follows:
def androidManifest = new XmlSlurper().parse("AndroidManifest.xml") println androidManifest['@android:versionName']
或者
println androidManifest.@'android:versionName'
Copy the code
3) Generate XML data
In addition to parsing XML data with XmlSlurper, we can also use xmlBuilder to create XML files, as shown in the following code:
Java
Groovy
JavaScript < language > < / langs > * /
def sw = new StringWriter()
// The core class used to generate XML data
def xmlBuilder = new MarkupBuilder(sw)
// Langs was created successfully
xmlBuilder.langs(type: 'current'.count: '3'.mainstream: 'true') {
// The first language node
language(flavor: 'static'.version: '1.5') {
age('16')
}
language(flavor: 'dynamic'.version: '1.6') {
age('10')
}
language(flavor: 'dynamic'.version: '1.9'.'JavaScript')}// println sw
def langs = new Langs()
xmlBuilder.langs(type: langs.type, count: langs.count,
mainstream: langs.mainstream) {
// Walk through all the children
langs.languages.each { lang ->
language(flavor: lang.flavor,
version: lang.version, lang.value)
}
}
println sw
// corresponds to the LANGs node in XML
class Langs {
String type = 'current'
int count = 3
boolean mainstream = true
def languages = [
new Language(flavor: 'static'.version: '1.5'.value: 'Java'),
new Language(flavor: 'dynamic'.version: '1.3'.value: 'Groovy'),
new Language(flavor: 'dynamic'.version: '1.6'.value: 'JavaScript')]}// corresponds to the LANGUang node in XML
class Language {
String flavor
String version
String value
}
Copy the code
4) Json in Groovy
Instead of using Gson to parse network responses, we can use the JsonSlurper class provided in Groovy to avoid introducing the Gson library when we write the plug-in, as shown in the following example:
def reponse =
getNetworkData(
'http://yuexibo.top/yxbApp/course_detail.json')
println reponse.data.head.name
def getNetworkData(String url) {
// Send an HTTP request
def connection = new URL(url).openConnection()
connection.setRequestMethod('GET')
connection.connect()
def response = connection.content.text
// Convert json to entity objects
def jsonSluper = new JsonSlurper()
return jsonSluper.parseText(response)
}
Copy the code
Five, the summary
In this article, we learned the essential core syntax in Groovy from four aspects:
- 1) Basic syntax of variables, strings, loops, etc.
- 2) Data structures in Groovy: arrays, lists, maps, ranges.
- 3) Groovy’s object-oriented, powerful runtime mechanism for methods, classes, etc.
- 4) Processing of ordinary files, XML and JSON files in Groovy.
In the future, we will use these skills when we customize Gradle plug-ins. Therefore, it is important to have a good command of Groovy. Only with a solid foundation can we go further.
Reference links:
-
1. Groovy API detail documentation
-
2. Chapter 1-5 “MoOCs Gradle3.0 Automation Project Construction Technology Intensive + Actual Combat”
-
3. Understanding Android Gradle in Depth
-
Gradle from the Beginning to the Practical – Groovy basics
-
5. Groovy Script Basics Overview