前言
Google 在I/O大会上宣布了官方支持kotlin作为android的开发语言。因此kotlin一下子进入了android开发同学们的实现,其实我很早就知道这门语言,也看了别人使用这门语言做android开发,但是由于懒,从来也没有看过。正好借此机会,大家都在趁热乎劲学习,我也翻开了官方文档看了看,因为我学习一门语言的方法就是简单浏览一下语法,然后直接开始写一个简单的程序,这样子在实现想要的功能的同时也就简单熟悉了一些常用的语法,于是就有了第一个练习的程序,也就有了这个分享。
Kotlin优点
Kotlin 的语法优点还是很多的。跟scala其实挺像的。如果写过scala,写这个会觉得挺顺手的。
- 集合链式调用,像rxjava一样,有很多的操作符,写起来很舒服,语法糖比较多,看起来逻辑也很清晰易读。
- 代码很精简,很多可能在java中很复杂的代码,在这里面就很精简,看起来很舒服。
例如我想把android项目中的一个源文件读出来,但是想把注释掉的行排除掉,然后把整个文件转化成一个String,并且用换行符连接起来,那么简单的一行代码的函数就可以搞定:
fun fileToString(fn: File) = fn.readLines().filter { l -> !l.trim().startsWith("//") }.asSequence().joinToString("\n")
不必像Java一样还得写好些输入输出流,读取再筛选再拼接。虽然Java经过封装也可以做到,但是Kotlin直接就可以做这些操作非常的简单直观。这就是上面两个优点
- 兼容java,这个就是你以前在java中用的现在还是可以用,只是写法稍微有一单改动。
举个例子,我想拿到某个文件下所有以某些xml,png,jpg
啊这些结尾的文件的集合,那么给一个drawables
的集合作为extensions
参数给allFiles()这个函数,java中常用的ArrayList
我们就直接用就可以了,减少了很多不适应的成本。
val drawables = sequenceOf("png", "jpg", "xml")
fun allFiles(f: File, extensions: Sequence<String>): Sequence<File> {
val files = ArrayList<File>()
if (f.isDirectory) {
f.listFiles().map { f -> files.addAll(allFiles(f, extensions)) }
} else if (extensions.any { ex -> f.name.endsWith("." + ex) }) {
files.add(f)
}
return files.asSequence()
}
我体验到的大概就是这么多吧。其实也算不上什么新的优点吧,毕竟很多其他语言也是这样的,可能对于平时写java的同学来说确实感觉一下子清新了很多,因为java确实有很多繁杂的代码。像我这样的也只能有一点语法层面的感觉,毕竟我也是刚刚看了一会kotlin这个语言,没有更多细致的了解。
实现
下面简单讲一下如何实现这个功能的,虽然As貌似有这个功能。但是我只是为了练习一下kotlin,也分享一下。
流程很简单:
- 1.扫描项目中所有的资源文件。
也就是-hdpi", "-mdpi", "-xhdpi", "-xxhdpi", "-xxxhdpi"
这几个文件夹和xml
资源放的文件夹中的png,jpg,xml这3种会在android项目中用到的资源文件。然后获取他们的名字,放到一个Set
里面,因为Set
集合中value不可重复,每个文件夹下可能有相同名字的资源文件,所以放到Set里去重。
fun getFileName(f: File): String {
return f.name.replace(".9.png", "").replace(".png", "").replace(".jpg", "").replace(".xml", "")
}
val drawables = sequenceOf("png", "jpg", "xml")
val resDirs = sequenceOf("-hdpi", "-mdpi", "-xhdpi", "-xxhdpi", "-xxxhdpi").map { s -> "app/src/main/res/mipmap$s" }.asSequence()
fun reses(): Sequence<File> = resDirs.map { str -> allFiles(File(str), drawables) }.flatten()
fun allNames() = reses().map { file -> getFileName(file) }.toSet()
这个功能实现很简单,就是扫描resDirs这几个文件夹通过allFiles()
这个方法获取文件夹下面所有的以drawables
这个集合中的字符结尾的文件。然后通过getFileName
获取到文件的名字,放到Set中
为什么要名字?
因为我们在代码中都是写R.mipmap.xxx
或者@mipmap/xxx
这样的,我们要拿名字去和代码中用到的名字比较匹配。
通过上面的代码我们就拿到了项目中allNames
- 2.扫描项目中所有的代码。
也就是.kt .java .xml
这3中文件中用到R.mipmap.xxx
或者@mipmap/xxx
的地方,解析出所有这些东西,就知道那些名字在项目中是出现过的。
val pattern = """@mipmap\/[a-zA-Z0-9_]+[^a-zA-Z0-9_]|R\.mipmap\.[A-Za-z0-9_]+[^a-zA-Z0-9_]""".toPattern()
val codes = sequenceOf("kt", "java", "xml")
val projects = sequenceOf("app")
val usedNames = projects.map { p ->
allFiles(File(p + "/src"), codes).map { f -> fileToString(f) }.map { str ->
val names = ArrayList<String>()
val m = pattern.matcher(str)
while (m.find()) {
names.add(m.toMatchResult().group().dropLast(1).replace("@mipmap/", "").replace("R.mipmap.", ""))
}
names.asSequence()
}.flatten()
}.flatten().toSet()
如果你还有其他的module,可以加到projects
集合里。这个逻辑也挺简单的。跟上面一样,首先通过allFiles()
扫描src
目录下面所有以codes
集合中字符串结尾的文件,然后通过最上面提到的fileToString
方法把每个文件转化成String
,通过正则匹配找出其中用到R.mipmap.xxx
或者@mipmap/xxx
的地方,再取出名字放到Set里面
。这样我们就拿到了项目中用到过的所有的资源的名字usedNames
- 3
allNames
-usedNames
=unusedNames
这个道理就很简单了。
val unusedNames = allNames().filter { d -> !usedNames.contains(d) }.toSet()
- 4 遍历资源文件夹,找到和
unusedNames
相同名字的文件,删除。
fun cleanDir(f: File) {
if (f.isDirectory) {
f.listFiles().forEach { f -> cleanDir(f) }
} else {
if (unusedNames.map { s -> getFileName(f) == s }.toSet().contains(true)) {
f.delete()
}
}
}
fun clean() = resDirs.map { s -> File(s) }.forEach { f -> cleanDir(f) }
总结
短短50行代码,很适合练习的一个小程序,我花了大概几个小时,在没写过kotlin的情况下写出来的,过程中感受了kotlin语法,知道了一些大概的kotlin编程的方式,也用了这些语法糖。虽然我感觉还是没有scala简单,但是还是值得一试。希望给同样是新手的你一点小的参考。
新手,很多不正之处,还望指点。
所有代码的地址:https://github.com/kingty/kotlin-script-demo/blob/master/clear.kts