全面攻略Groovy脚本基础知识

assert numbers.size() == 3

//List中存储任意类型

def heterogeneous = [1, “a”, true]

//判断List默认类型

def arrayList = [1, 2, 3]

assert arrayList instanceof java.util.ArrayList

//使用as强转类型

def linkedList = [2, 3, 4] as LinkedList

assert linkedList instanceof java.util.LinkedList

//定义指定类型List

LinkedList otherLinked = [3, 4, 5]

assert otherLinked instanceof java.util.LinkedList

//定义List使用

def letters = [‘a’, ‘b’, ‘c’, ‘d’]

//判断item值

assert letters[0] == ‘a’

assert letters[1] == ‘b’

//负数下标则从右向左index

assert letters[-1] == ‘d’

assert letters[-2] == ‘c’

//指定item赋值判断

letters[2] = ‘C’

assert letters[2] == ‘C’

//给List追加item

letters << ‘e’

assert letters[ 4] == ‘e’

assert letters[-1] == ‘e’

//获取一段List子集

assert letters[1, 3] == [‘b’, ‘d’]

assert letters[2…4] == [‘C’, ‘d’, ‘e’]

//多维List支持

def multi = [[0, 1], [2, 3]]

assert multi[1][0] == 2

2-8 Arrays类型


Groovy中数组和Java类似,具体如下:

//定义初始化String数组

String[] arrStr = [‘Ananas’, ‘Banana’, ‘Kiwi’]

assert arrStr instanceof String[]

assert !(arrStr instanceof List)

//使用def定义初始化int数组

def numArr = [1, 2, 3] as int[]

assert numArr instanceof int[]

assert numArr.size() == 3

//声明定义多维数组指明宽度

def matrix3 = new Integer[3][3]

assert matrix3.size() == 3

//声明多维数组不指定宽度

Integer[][] matrix2

matrix2 = [[1, 2], [3, 4]]

assert matrix2 instanceof Integer[][]

//数组的元素使用及赋值操作

String[] names = [‘Cédric’, ‘Guillaume’, ‘Jochen’, ‘Paul’]

assert names[0] == ‘Cédric’

names[2] = ‘Blackdrag’

assert names[2] == ‘Blackdrag’

2-9 Maps类型


Map是“键-值”对的集合,在Groovy中键key不一定是String,可以是任何对象(实际上Groovy中的Map就是java.util.Linke dHashMap)。如下:

//定义一个Map

def colors = [red: ‘#FF0000’, green: ‘#00FF00’, blue: ‘#0000FF’]

//获取一些指定key的value进行判断操作

assert colors[‘red’] == ‘#FF0000’

assert colors.green == ‘#00FF00’

//给指定key的对赋值value操作与判断

colors[‘pink’] = ‘#FF00FF’

colors.yellow = ‘#FFFF00’

assert colors.pink == ‘#FF00FF’

assert colors[‘yellow’] == ‘#FFFF00’

//判断Map的类型

assert colors instanceof java.util.LinkedHashMap

//访问Map中不存在的key为null

assert colors.unknown == null

//定义key类型为数字的Map

def numbers = [1: ‘one’, 2: ‘two’]

assert numbers[1] == ‘one’

对于Map需要特别注意一种情况,如下:

//把一个定义的变量作为Map的key,访问Map的该key是失败的

def key = ‘name’

def person = [key: ‘Guillaume’]

assert !person.containsKey(‘name’)

assert person.containsKey(‘key’)

//把一个定义的变量作为Map的key的正确写法—添加括弧,访问Map的该key是成功的

person = [(key): ‘Guillaume’]

assert person.containsKey(‘name’)

assert !person.containsKey(‘key’)

【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】

3 运算符

=========

关于Groovy的运算符介绍类似于上面一样,我们重点突出与Java的不同点,相同点自行脑补。

Groovy支持**次方运算符,如下:

assert 2 ** 3 == 8

def f = 3

f **= 2

assert f == 9

Groovy非运算符如下:

assert (!true) == false

assert (!‘foo’) == false

assert (!‘’) == true

Groovy支持?.安全占位符,这个运算符主要用于避免空指针异常,譬如:

def person = Person.find { it.id == 123 }

def name = person?.name

assert name == null

Groovy支持.@直接域访问操作符,因为Groovy自动支持属性getter方法,但有时候我们有一个自己写的特殊getter方法,当不想调用这个特殊的getter方法则可以用直接域访问操作符。如下:

class User {

public final String name

User(String name) { this.name = name}

String getName() { “Name: $name” }

}

def user = new User(‘Bob’)

assert user.name == ‘Name: Bob’

assert user.@name == ‘Bob’

Groovy支持.&方法指针操作符,因为闭包可以被作为一个方法的参数,如果想让一个方法作为另一个方法的参数则可以将一个方法当成一个闭包作为另一个方法的参数。如下:

def list = [‘a’,‘b’,‘c’]

//常规写法

list.each{

println it

}

String printName(name){

println name

}

//方法指针操作符写法

list.each(this.&printName)

Groovy支持将?:三目运算符简化为二目,如下:

displayName = user.name ? user.name : ‘Anonymous’

displayName = user.name ?: ‘Anonymous’

Groovy支持*.展开运算符,一个集合使用展开运算符可以得到一个元素为原集合各个元素执行后面指定方法所得值的集合,如下:

cars = [

new Car(make: ‘Peugeot’, model: ‘508’),

null,

new Car(make: ‘Renault’, model: ‘Clio’)]

assert cars*.make == [‘Peugeot’, null, ‘Renault’]

assert null*.make == null

关于Groovy的其他运算符就不多说,类比Java吧。

【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】

4 程序结构

==========

这里主要讨论Groovy的代码组成结构,具体如下细则。

4-1 包名


包名的定义和作用及含义完全和Java一样,不再介绍,如下:

// defining a package named com.yoursite

package com.yoursite

4-2 Imports引入


常规的imports导包操作和Java一样,如下:

//例1:

import groovy.xml.MarkupBuilder

// using the imported class to create an object

def xml = new MarkupBuilder()

assert xml != null

//例2:

import groovy.xml.*

def markupBuilder = new MarkupBuilder()

assert markupBuilder != null

assert new StreamingMarkupBuilder() != null

//例3:

import static Boolean.FALSE

assert !FALSE

//例4:特殊的,相当于用as取别名

import static Calendar.getInstance as now

assert now().class == Calendar.getInstance().class

不过要特别注意,Groovy与Java类似,已经帮我们默认导入了一些常用的包,所以在我们使用这些包的类时就不用再像上面那样导入了,如下是自动导入的包列表:

import java.lang.*

import java.util.*

import java.io.*

import java.net.*

import groovy.lang.*

import groovy.util.*

import java.math.BigInteger

import java.math.BigDecimal

4-3 脚本与类(脚本的实质)


相对于传统的Java类,一个包含main方法的Groovy类可以如下书写:

class Main {

static void main(String… args) {

println ‘Groovy world!’

}

}

和Java一样,程序会从这个类的main方法开始执行,这是Groovy代码的一种写法,实际上执行Groovy代码完全可以不需要类或main方法,所以更简单的写法如下:

println ‘Groovy world!’

上面这两中写法其实是一样的,具体我们可以通过如下命令进行编译为class文件:

groovyc demo.groovy //编译Groovy源码为class

我们使用反编译工具可以查看到这个demo.groovy类源码如下:

import org.codehaus.groovy.runtime.InvokerHelper

class Main extends Script {

def run() {

println ‘Groovy world!’

}

static void main(String[] args) {

InvokerHelper.runScript(Main, args)

}

}

可以看见,上面我们写的groovy文件编译后的class其实是Java类,该类从Script类派生而来(查阅API);可以发现,每个脚本都会生成一个static main方法,我们执行groovy脚本的实质其实是执行的这个Java类的main方法,脚本源码里所有代码都被放到了run方法中,脚本中定义的方法(该例暂无)都会被定义在Main类中。

通过上面可以发现,Groovy的实质就是Java的class,也就是说他一定会和Java一样存在变量作用域!对哦,前面我们解释变量时竟然没说到这个东东,这里说下吧。看下面例子:

//单个Groovy源码文件,运行会报错找不到num变量

def num = 1

def printNum(){

println num

}

//单个Groovy源码文件,运行会报错找不到num变量

int num = 1

def printNum(){

println num

}

//单个Groovy源码文件,运行OK成功

num = 1

def printNum(){

println num

}

上面的例子可以发现,我们如果想要在Groovy的方法中使用Groovy的变量则不能有修饰符。然而,如果我们想在B.groovy文件访问A.groovy文件的num变量咋办呢,我们可以使用Field注解,具体操作如下:

import groovy.transform.Field;

@Field num = 1

哈哈,这就是Groovy的变量作用域了,如果你想知道上面这些写法为啥出错,很简单,自己动手整成Java源码相信你一定可以看懂为啥鸟。

【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】

5 闭包

========

Groovy的闭包(closure)是一个非常重要的概念,闭包是可以用作方法参数的代码块,Groovy的闭包更象是一个代码块或者方法指针,代码在某处被定义然后在其后的调用处执行。

5-1 语法


定义一个闭包:

{ [closureParameters -> ] statements }

//[closureparameters -> ]是可选的逗号分隔的参数列表,参数类似于方法的参数列表,这些参数可以是类型化或非类型化的。

如下给出几个有效的闭包定义例子:

//最基本的闭包

{ item++ }

//使用->将参数与代码分离

{ -> item++ }

//使用隐含参数it(后面有介绍)

{ println it }

//使用明确的参数it替代

{ it -> println it }

//使用显示的名为参数

{ name -> println name }

//接受两个参数的闭包

{ String x, int y ->

println “hey ${x} the value is ${y}”

} //包含一个参数多个语句的闭包

{ reader ->

def line = reader.readLine()

line.trim()

}

闭包对象:

一个闭包其实就是一个groovy.lang.Closure类型的实例,如下:

//定义一个Closure类型的闭包

def listener = { e -> println “Clicked on $e.source” }

assert listener instanceof Closure

//定义直接指定为Closure类型的闭包

Closure callback = { println ‘Done!’ }

Closure isTextFile = {

File it -> it.name.endsWith(‘.txt’)

}

调运闭包:

其实闭包和C语言的函数指针非常像,我们定义好闭包后调用的方法有如下两种形式:

  • 闭包对象.call(参数)

  • 闭包对象(参数)

  • 如下给出例子:

    def code = { 123 }

    assert code() == 123

    assert code.call() == 123

    def isOdd = { int i-> i%2 == 1 }

    assert isOdd(3) == true

    assert isOdd.call(2) == false

    特别注意,如果闭包没定义参数则默认隐含一个名为it的参数,如下例子:

    def isEven = { it%2 == 0 }

    assert isEven(3) == false

    assert isEven.call(2) == true

    5-2 参数


    普通参数:

    一个闭包的普通参数定义必须遵循如下一些原则:

  • 参数类型可选

  • 参数名字

  • 可选的参数默认值

  • 参数必须用逗号分隔

  • 如下是一些例子:

    def closureWithOneArg = { str -> str.toUpperCase() }

    assert closureWithOneArg(‘groovy’) == ‘GROOVY’

    def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() }

    assert closureWithOneArgAndExplicitType(‘groovy’) == ‘GROOVY’

    def closureWithTwoArgs = { a,b -> a+b }

    assert closureWithTwoArgs(1,2) == 3

    def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a+b }

    assert closureWithTwoArgsAndExplicitTypes(1,2) == 3

    def closureWithTwoArgsAndOptionalTypes = { a, int b -> a+b }

    assert closureWithTwoArgsAndOptionalTypes(1,2) == 3

    def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a+b }

    assert closureWithTwoArgAndDefaultValue(1) == 3

    隐含参数:

    当一个闭包没有显式定义一个参数列表时,闭包总是有一个隐式的it参数。如下:

    def greeting = { “Hello, $it!” }

    assert greeting(‘Patrick’) == ‘Hello, Patrick!’

    上面的类似下面这个例子:

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

    assert greeting(‘Patrick’) == ‘Hello, Patrick!’

    当然啦,如果你想声明一个不接受任何参数的闭包,且必须限定为没有参数的调用,那么你必须将它声明为一个空的参数列表,如下:

    def magicNumber = { -> 42 }

    // this call will fail because the closure doesn’t accept any argument

    magicNumber(11)

    可变长参数:

    Groovy的闭包支持最后一个参数为不定长可变长度的参数,具体用法如下:

    def concat1 = { String… args -> args.join(‘’) }

    assert concat1(‘abc’,‘def’) == ‘abcdef’

    def concat2 = { String[] args -> args.join(‘’) }

    assert concat2(‘abc’, ‘def’) == ‘abcdef’

    def multiConcat = { int n, String… args ->

    args.join(‘’)*n

    }

    assert multiConcat(2, ‘abc’,‘def’) == ‘abcdefabcdef’

    5-3 闭包省略调运


    很多方法的最后一个参数都是一个闭包,我们可以在这样的方法调运时进行略写括弧。比如:

    def debugClosure(int num, String str, Closure closure){

    //dosomething

    }

    debugClosure(1, “groovy”, {

    println"hello groovy!"

    })

    可以看见,当闭包作为闭包或方法的最后一个参数时我们可以将闭包从参数圆括号中提取出来接在最后,如果闭包是唯一的一个参数,则闭包或方法参数所在的圆括号也可以省略;对于有多个闭包参数的,只要是在参数声明最后的,均可以按上述方式省略。

    【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】

    6 GDK(Groovy Development Kit)

    =================================

    Groovy除了可以直接使用Java的JDK以外还有自己的一套GDK,其实也就是对JDK的一些类的二次封装罢了;一样,这是GDK官方API文档,写代码中请自行查阅。

    6-1 I/O操作


    Groovy提供了很多IO操作的方法,你可以使用Java的那写IO方法,但是没有Groovy的GDK提供的简单牛逼。

    读文件操作:

    我们先来看一个例子:

    //读文件打印脚本

    new File(‘/home/temp’, ‘haiku.txt’).eachLine { line ->

    println line

    }

    //读文件打印及打印行号脚本

    new File(baseDir, ‘haiku.txt’).eachLine { line, nb ->

    println “Line $nb: $line”

    }

    可以看见,这是一个读文件打印每行的脚本,eachLine方法是GDK中File的方法,eachLine的参数是一个闭包,这里采用了简写省略括弧。

    当然了,有时候你可能更加喜欢用Reader来操作,使用Reader时即使抛出异常也会自动关闭IO。如下:

    def count = 0, MAXSIZE = 3

    new File(baseDir,“haiku.txt”).withReader { reader ->

    while (reader.readLine()) {

    if (++count > MAXSIZE) {

    throw new RuntimeException(‘Haiku should only have 3 verses’)

    }

    }

    }

    接着我们再看几个关于读文件的操作使用,如下:

    //把读到的文件行内容全部存入List列表中

    def list = new File(baseDir, ‘haiku.txt’).collect {it}

    //把读到的文件行内容全部存入String数组列表中

    def array = new File(baseDir, ‘haiku.txt’) as String[]

    //把读到的文件内容全部转存为byte数组

    byte[] contents = file.bytes

    //把读到的文件转为InputStream,切记此方式需要手动关闭流

    def is = new File(baseDir,‘haiku.txt’).newInputStream()

    // do something …

    is.close()

    //把读到的文件以InputStream闭包操作,此方式不需要手动关闭流

    new File(baseDir,‘haiku.txt’).withInputStream { stream ->

    // do something …

    }

    上面介绍了一些常用的文件读操作,其它的具体参见API和GDK吧。

    写文件操作:

    有了上面的读操作,接下来直接看几个写操作的例子得了,如下:

    //向一个文件以utf-8编码写三行文字

    new File(baseDir,‘haiku.txt’).withWriter(‘utf-8’) { writer ->

    writer.writeLine ‘Into the ancient pond’

    writer.writeLine ‘A frog jumps’

    writer.writeLine ‘Water’s sound!’

    }

    //上面的写法可以直接替换为此写法

    new File(baseDir,‘haiku.txt’) << ‘’'Into the ancient pond

    A frog jumps

    Water’s sound!‘’’

    //直接以byte数组形式写入文件

    file.bytes = [66,22,11]

    //类似上面读操作,可以使用OutputStream进行输出流操作,记得手动关闭

    def os = new File(baseDir,‘data.bin’).newOutputStream()

    // do something …

    os.close()

    //类似上面读操作,可以使用OutputStream闭包进行输出流操作,不用手动关闭

    new File(baseDir,‘data.bin’).withOutputStream { stream ->

    // do something …

    }

    上面介绍了一些常用的文件写操作,其它的具体参见API和GDK吧。

    文件树操作:

    在脚本环境中,遍历一个文件树是很常见的需求,Groovy提供了多种方法来满足这个需求。如下:

    //遍历所有指定路径下文件名打印

    dir.eachFile { file ->

    println file.name

    } //遍历所有指定路径下符合正则匹配的文件名打印

    dir.eachFileMatch(~/.*.txt/) { file ->

    println file.name

    } //深度遍历打印名字

    dir.eachFileRecurse { file ->

    println file.name

    } //深度遍历打印名字,只包含文件类型

    dir.eachFileRecurse(FileType.FILES) { file ->

    println file.name

    } //允许设置特殊标记规则的遍历操作

    dir.traverse { file ->

    if (file.directory && file.name==‘bin’) {

    FileVisitResult.TERMINATE

    } else {

    println file.name

    FileVisitResult.CONTINUE

    }

    }

    执行外部程序:

    Groovy提供一种简单方式来处理执行外部命令行后的输出流操作。如下:

    def process = “ls -l”.execute()

    println “Found text ${process.text}”

    execute方法返回一个java.lang.Process对象,支持in、out、err的信息反馈。在看一个例子,如下:

    def process = “ls -l”.execute()

    process.in.eachLine { line ->

    println line

    }

    上面使用闭包操作打印出执行命令行的输入流信息。

    6-2 有用的工具类操作


    ConfigSlurper配置:

    ConfigSlurper是一个配置管理文件读取工具类,类似于Java的*.properties文件,如下:

    def config = new ConfigSlurper().parse(‘’’

    app.date = new Date()

    app.age = 42

    app {

    name = “Test${42}”

    }

    ‘’')

    assert config.app.date instanceof Date

    assert config.app.age == 42

    assert config.app.name == ‘Test42’

    上面介绍了一些常用的属性配置操作,其它的具体参见API和GDK吧。

    Expando扩展:

    def expando = new Expando()

    expando.toString = { -> ‘John’ }

    expando.say = { String s -> “John says: ${s}” }

    assert expando as String == ‘John’

    assert expando.say(‘Hi’) == ‘John says: Hi’

    上面介绍了一些常用的拓展操作,其它的具体参见API和GDK吧。

    6-2 其他操作


    还有很多其他操作,这里就不一一列举,详情参考官方文档即可,譬如JSON处理、XML解析啥玩意的,自行需求摸索吧。

    【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】

    7 DSL(Domain Specific Languages)领域相关语言

    ==========================================

    这个就不特殊说明了,只在这里提一下,因为我们前边很多地方已经用过它了,加上我们只是干货基础掌握,所以不做深入探讨。

    DSL是一种特定领域的语言(功能领域、业务领域),Groovy是通用的编程语言,所以不是DSL,但是Groovy却对编写全新的DSL提供了很好的支持,这些支持来自于Groovy自身语法的特性,如下:

  • Groovy不需用定义CLASS类就可以直接执行脚本;

  • Groovy语法省略括弧和语句结尾分号等操作;

  • 所以说这个基础入门没必要特别深入理解,简单的前面都用过了,理解DSL作用即可,点到为止,详情参考官方文档。

    【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】

    自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

    深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

    因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

    img

    img

    img

    img

    既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

    由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

    如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

    文末

    对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

    最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

    进阶学习视频

    附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

    《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

    /yanbober]( ) 转载请注明出处。点我开始Android技术交流】**

    自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

    深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

    因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

    [外链图片转存中…(img-33suSsqT-1712276997485)]

    [外链图片转存中…(img-HIBU9BTj-1712276997485)]

    [外链图片转存中…(img-5PKxefHN-1712276997486)]

    [外链图片转存中…(img-Za4hR8PL-1712276997486)]

    [外链图片转存中…(img-BgvOxGiR-1712276997486)]

    既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

    由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

    如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

    文末

    对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

    最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

    进阶学习视频

    [外链图片转存中…(img-zlYoYSKn-1712276997487)]

    附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

    [外链图片转存中…(img-Zch4hzhl-1712276997487)]

    《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

    作者:普通网友

    物联沃分享整理
    物联沃-IOTWORD物联网 » 全面攻略Groovy脚本基础知识

    发表评论