前言

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): Sequence {
    val files = ArrayList()
    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 = 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()
        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 allNamesusedNames = 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

文章来源于互联网:Kotlin 50行代码实现清除Android项目中不用的图片和资源

发表评论