[Android] 使用lint 检查kotlin 未捕获的异常 您所在的位置:网站首页 callback函数中可以通过throw抛出异常 [Android] 使用lint 检查kotlin 未捕获的异常

[Android] 使用lint 检查kotlin 未捕获的异常

2023-06-22 11:26| 来源: 网络整理| 查看: 265

本人所有文章禁止任何形式的转载

前言

在java 中如果一个方法抛出了一个异常,任何调用他的地方要么去try-catch,要么继续抛出异常。 这种逻辑非常麻烦,明明只需要最开始的方法try-catch 就好了。

然后到了kotlin,检查直接没了。这会给我们惹上麻烦,如果漏掉了一个没有进行try-catch 的话。

@Throws(IOException::class) fun throwException() = Unit /** * 这里应该掠过检查 */ fun middle() = throwException() /** * 因为进行了try catch,所以这里没有错误 */ fun hello() { try { middle() } catch (_: IOException) {} } /** * 需要提供一个错误 */ fun test() = middle()

上面是一个示例代码,我们后续就通过这段代码做测试。正如doc 中所说,给middle 也提供一个错误是没有必要的,只要hello 进行了try-catch 就能够保证代码是能够正确运行的。而test 没有进行try-catch,所以需要提供一个错误。

所以这里我要使用Lint 检查未处理的异常。命名为Yong,取自“庸人自扰”的“庸”。

Lint

虽然这个Lint 叫作Android Lint,但是其他java 项目应该也是能用的。API 并没有使用到Android SDK,包名都是com.android.tools.lint。我们需要两个module, 一个是普通kotlin module,完成代码检查,另一个是android module,然后引入前者,再发布到一个aar 中,最终在app 模块中使用。

基本流程;我们需要实现一个Detector 完成代码检查,在出现错误的时候通过JavaContext#report 报告一个Issue。

interface Scanner { fun test() } //所有scanner 的空实现 abstract class Detector { fun test() { println("from Detector") } } class MyDetector : Detector(), Scanner { }

Detector 只是为所有的接口提供了默认实现,所以说Scanner 才是真正起作用的。同时Scanner 有多个,比如XmlScanner,SourceCodeScanner。我们要用的是UastScanner,它和SourceCodeScanner 没有什么不同,只是按照doc 所说是为了兼容性,我们暂时按照demo 中的样子也使用这个。

谷歌提供的demo 项目android-custom-lint-rules

Uast 意指“通用语法树”,与PSI 不同,后者在不同语言上结构不同,无法通用。

创建

创建一个checks 模块,导入com.android.tools.lint 依赖。

实现我们的Detector,同时选择我们的Scanner。

实现我们的IssueRegistry。

在com.android.tools.lint.client.api.IssueRegistry 中注册我们的IssueRegistry。然后Issue 会注册我们的Detector。

创建一个library Android module,导入checks,同时发布。

就像这样:

dependencies { implementation project(':checks') lintPublish project(':checks') }

然后在app module中导入

implementation project(':library') 实现

经过上面的处理,我们可以真正的实现了。

Lint 并不会告诉我们语法树的根节点在哪,好在我们的目标明确---我们主要面对的是Android 项目,所以Android 项目的语法树的根节点就是“四大组件“(姑且这个认为,否则也没有别的更好的办法)。并且入口函数就是生命周期函数,如果不是生命周期函数,即使抛出了也会直接略过。

override fun getApplicableUastTypes(): List { return listOf(UClass::class.java) } override fun createUastHandler(context: JavaContext): UElementHandler { return object : UElementHandler() { override fun visitClass(node: UClass) { context.log(null,"element-handler visitClass ${node.name}") } } }

这样我们就能获取所有的Class,下一步就是从这些类中查找出Activity

val isActivity = node.supers.any { it.qualifiedName == "androidx.appcompat.app.AppCompatActivity" }

然后获取所有的Method。

node.methods.filter { it.findSuperMethods().isNotEmpty() }.forEach { it.accept(visitor) } //...... val visitor = object : AbstractUastVisitor() { override fun visitMethod(node: UMethod): Boolean { context.log(null, "\tvisitMethod ${node.name}") return super.visitMethod(node) } }

确保能够访问所有的method 之后,我们需要构建一个方法调用的树。

树的结构:一个root 节点当作指针。第二层是Activity,第三层是Activity 的生命周期函数。后面就是方法调用的树。

原则:

从根节点到叶节点同样的方法只有一个,防止递归调用 使用特殊的key 作为方法的标识,因为PsiElement 并没有重载hashCode 方法。(file 全路径 + class 全限定名称 + function signature) method 节点包含:n+1 个节点。n = try catch 块的数量。1 是剩余所有的内容。包含剩余的方法。 强烈依赖@Throws,毕竟这是一个静态代码检查。比如下面这样。 fun test(i: Int) { if (i == 0) { throw Exception("is 0") } } fun hello() { test(1) }

像这种情况,根本不会发生崩溃,自然也没有必要抛出错误,不处理也无所谓。所以把这个判断的责任交给程序员,而不是一个“不太聪明的”静态代码检查。不过,在没有使用@Throws 的情况下,还是会查找对应的throw 表达式的。 并且这种强烈依赖还会在遇到@Throws 时停止继续遍历method 中的内容,也就是说在树中是一个叶节点。

补充:

@Throws 和java 中的方法后面的定义的thorw 关键字和throw 表达式具有同样的作用。 retrofit 等网络请求库的注解等同于@Throws(IOException)

都是些常规代码,没啥可讲的。 可以点击这里查看所有代码

问题

当前存在个问题,当我遍历method 中的function call 表达式时,无法获知他所属的class,导致我不得不调用resolve 方法,按照文档提示这个方法非常耗时,所以如果你的库比较大,可能会非常耗时。

抽象类,接口当前还没有处理。如果在声明接口方法的地方使用了@Throws 是没有问题,如果没有,需要找到所有的实现类,检查其中是否含有未捕获的异常。

使用

发布在了*jitpack。

dependencies { lintChecks 'com.github.storytellerF:Yong:c5bb4aae20' }

一定要注意这里需要使用lintChecks,否则不能使用。

然后执行

sh gradlew lint

Root ----Activity(MainActivity) --------Method(onResume) java.io.IOException ------------Method(test) java.io.IOException ----------------Method(middle) java.io.IOException --------------------Method(throwException) java.io.IOException ----------------Method(test) java.io.IOException ----------------Method(middle) java.io.IOException ---------------- Method(throwException) java.io.IOException ----------------Method(throwException) java.io.IOException

实际的结果不包含短线,是空格。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有