add Custom Lint

master
Omooo 5 years ago
parent 06bbe0470d
commit 4f27882145
  1. 180
      blogs/Android/自定义 Lint.md
  2. 9
      blogs/computer_network/computer_systems/计算机系统漫游.md
  3. 86
      blogs/computer_network/深入理解计算机系统/计算机系统漫游.md

@ -0,0 +1,180 @@
---
自定义 Lint
---
![](https://i.loli.net/2019/07/09/5d243bc10f1aa59752.png)
#### 前言
首先,先要仔细想想,什么时候需要用到自定义 Lint 呢?
这就要发挥你的想象力了,我最初是想推 webp,用以替换掉 png,这样就需要开发者每次自觉的将 png 转化为 webp,但是又不可能结对编程,监督他进行转化,所以自定义 Lint 就派上用场了。
以下是项目中的自定义 Lint 实现效果:
![](https://i.loli.net/2019/07/09/5d243d756b7ea70892.png)
以上 Detector 都有什么用呢?
ToastDetector:Android 自带的 Lint 规则,用于提示 Toast 忘记调用 show 方法,在 BuiltinIssueRegistry 类可以查看内置的所有的 Detector,这也是我实现自定义 Lint 的主要参考依据。
SampleCodeDetector:Google Sample 中的 Demo,用于检测文本表达式中是否包含特定的字符串。
PngDetector:用于检测所有 layout 或 java 文件中引用的 png 资源,提示使用 webp。
LogDetector:用于检测使用 Log 类的 i、d、e 等日志输出的方法,提示使用统一的日志工具类。严重程度为 Error,所以默认情况下,会中断编译流程。如果严重程度为 Warning,仅仅是报黄警告。
ThreadDetector:用于检测直接通过 new Thread 创建线程,提示应该使用统一线程池。
**源码地址:**[https://github.com/Omooo/CustomLint](https://github.com/Omooo/CustomLint)
#### 正文
先来看一个简单的自定义 Lint 是怎么样一步一步写出来的,该例子实际上来自 [https://github.com/googlesamples/android-custom-lint-rules](https://github.com/googlesamples/android-custom-lint-rules) ,该 Rep 就一个 SampleCodeDetector,也就是上文中所说的。
自定义 Lint 一共可以分为四步:
##### 第一步:创建 java library 工程
在 build.gradle 文件里添加依赖:
```groovy
compileOnly "com.android.tools.lint:lint-api:26.4.1"
compileOnly "com.android.tools.lint:lint-checks:26.4.1"
```
##### 第二步:创建 Detector
```java
public class SampleDetector extends Detector implements Detector.UastScanner {
//第一步:定义 ISSUE
public static final Issue ISSUE = Issue.create(
"ShortUniqueId", //唯一 ID
"Lint Mentions", //简单描述
"Blah blah blah.", //详细描述
Category.CORRECTNESS, //问题种类(正确性、安全性等)
6, //权重
Severity.WARNING, //问题严重程度(忽略、警告、错误)
new Implementation( //实现,包括处理实例和作用域
SampleCodeDetector.class,
Scope.JAVA_FILE_SCOPE));
//第二步:定义检测类型以及处理逻辑
//检测类型包括文本表达式、调用相关表达式等
@Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.singletonList(ULiteralExpression.class);
}
@Override
public UElementHandler createUastHandler(@NotNull JavaContext context) {
return new UElementHandler() {
@Override
public void visitLiteralExpression(@NotNull ULiteralExpression expression) {
String string = UastLiteralUtils.getValueIfStringLiteral(expression);
if (string == null) {
return;
}
if (string.contains("Omooo") && string.matches(".*\\bOmooo\\b.*")) {
//第三步:符合条件,上报 ISSUE
context.report(ISSUE, expression, context.getLocation(expression),
"This code mentions `Omooo`");
}
}
};
}
}
```
Lint API 中内置了很多 Scanner:
| Scanner 类型 | Desc |
| --------------------- | ------------------------ |
| UastScanner | 扫描 Java、Kotlin 源文件 |
| XmlScanner | 扫描 XML 文件 |
| ResourceFolderScanner | 扫描资源文件夹 |
| ClassScanner | 扫描 Class 文件 |
| BinaryResourceScanner | 扫描二进制资源文件 |
更多请参考:[https://static.javadoc.io/com.android.tools.lint/lint-api/25.3.0/com/android/tools/lint/detector/api/package-summary.html](https://static.javadoc.io/com.android.tools.lint/lint-api/25.3.0/com/android/tools/lint/detector/api/package-summary.html)
注意:
这里需要注意的一点是,如果对应的 ISSUE 严重程度为错误(Severity.ERROR),那么在默认情况下,会中断编译流程,当然,你也可以配置 LintOptions 来抑制 Lint 错误。
##### 第三步:注册 Detector
```java
public class CustomIssueRegistry extends IssueRegistry {
@NotNull
@Override
public List<Issue> getIssues() {
return Arrays.asList(
SampleCodeDetector.ISSUE);
}
@Override
public int getApi() {
return ApiKt.CURRENT_API;
}
}
```
这里可以注册多个 Detector,目前最新版本的 Lint 内置了 360 种 Detector,都在 BuiltinIssueRegistry 类中,可以作为我们编写自定义 Lint 的最佳参考案例。
##### 第四步:引入自定义 Lint
首先需要在 lint_library 中的 build.gradle 文件中添加,完整代码为:
```groovy
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly "com.android.tools.lint:lint-api:26.4.1"
compileOnly "com.android.tools.lint:lint-checks:26.4.1"
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
jar {
manifest {
attributes("Lint-Registry-v2": "top.omooo.lint_library.CustomIssueRegistry")
}
}
```
然后就可以在 app module 中的 build.gradle 中引入并使用了:
```groovy
dependencies {
//...
lintChecks project(":lint_library")
}
```
#### Lint 进阶
以上,一个简单的自定义 Lint 就写完了,但是有点意犹未尽的感觉。这时候就需要你发挥想象力,想想自己需要什么。你可以参考我给的源码,或者参考 Android 内置的 Lint 源码,看看它们能做什么。
这一小节很重要,但是我并不会给你讲如何去实现某某功能,自己看源码学习,因为真的不难哇。
#### 最后
如果你很懒,很烦每次都敲一遍 ./gradlew lint 去查看 Lint 输出,那么可以把执行 Lint 任务挂载在每次安装 Debug 包之前,即:
```groovy
/**
* 在执行 assembleDebug Task 之前挂载 lintDebug
*/
project.afterEvaluate {
def assembleDebugTask = project.tasks.find { it.name == 'assembleDebug' }
def lintTask = project.tasks.find { it.name == 'lintDebug' }
assembleDebugTask.dependsOn(lintTask)
}
```

@ -1,9 +0,0 @@
---
计算机系统漫游
---
#### 目录
1. 思维导图
2.

@ -0,0 +1,86 @@
---
计算机系统漫游
---
#### 目录
1. 思维导图
2. 前言
3. 信息就是位 + 上下文
4. 处理器读并解释存储在内存中的指令
#### 思维导图
#### 前言
hello.c :
```c
#include <stdio.h>
int main(){
printf("hello,world\n")
return 0;
}
```
#### 信息就是位 + 上下文
hello 程序的生命周期是从一个源文件开始的,即程序员通过编辑器创建并保存的文本文件,文件名是 hello.c。源程序实际上就是一个由 0 和 1 组成的位(又称为比特)序列,8 个位被组织成一组,成为字节。每个字节表示程序中的某些文本字符。
大部分的现代计算机系统都使用 ASCII 标准来表示文本字符,这种方式实际上就是用一个唯一的单字节大小的整数值来表示每个字符。
hello.c 程序是以字节序列的方式存储在文件中的。每个字节都有一个整数值,对应于某些字符。例如,第一个字节的整数值是 35,它对应的就是字符 "#"。第二个字节的整数值为 105,它对应的字符是 "i",以此类推。像 hello.c 这样只由 ASCII 字符构成的文件称为文本文件,所有其他文件都称为二进制文件。
hello.c 的表示方法说明了一个基本思想:系统中所有的信息 --- 包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据,都是由一串比特表示的。区别不同数据对象的唯一方法是我们读到这些数据对象的上下文。比如,在不同的上下文中,一个同样的字节序列可能表示一个整数、浮点数、字符串或者机器指令。
#### 程序被其他程序翻译成不同的格式
hello 程序的生命周期是从一个高级 C 语言程序开始的,因为这种形式能够被人读懂。然鹅,为了在系统上运行 hello.c 程序,每条 C 语句都必须被其他程序转化为一系列的低级机器语言指令。然后这些指令按照一种称为可执行目标程序的格式打好包,并以二进制磁盘文件的形式存放起来。目标程序也称为可执行目标文件。
在 Unix 系统上,从源文件到目标文件的转化是由编译器驱动程序完成的:
```
linux> gcc -o hello hello.c
```
在这里,GCC 编译器驱动程序读取源程序文件 hello.c,并把它翻译成一个可执行目标文件 hello。这个翻译过程可分为四个阶段完成,执行这四个阶段的程序(预处理器、编译器、汇编器和链接器)一起构成了编译系统。
![](https://i.loli.net/2019/07/04/5d1d97f043f8b11023.png)
* 预处理阶段
预处理器(cpp)根据以字符 # 开头的命令,修改原始的 C 程序。比如 hello.c 中第 1 行的 #include \<stdio.h> 命令告诉预处理器读取系统头文件 stdio.h 的内容,并把它直接插入程序文本中。结果就得到了另一个 C 程序,通常是以 .i 作为文件扩展名。
* 编译阶段
编译器(ccl)将文本文件 hello.i 翻译成文本文件 hello.s,它包含了一个汇编语言程序。编译后的每条语句都以一种文本格式描述了一条低级机器语言指令。汇编语言是非常有用的,因为它为不同高级语言的不同编译器提供了通用的输出语言。
* 汇编阶段
接下来,汇编器(as)将 hello.s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件 hello.o 中。hello.o 文件是一个二进制文件,它包含了 17 个字节是函数 main 的指令编码。如果我们在文本编辑器中打开 hello.o 文件,将看到一堆乱码。
* 链接阶段
hello 程序调用了 printf 函数,它是每个 C 编译器都提供的标准 C 库中的一个函数。printf 函数存在于一个名为 printf.o 的单独的预编译好的目标文件中,而这个文件必须以某种方式合并到我们的 hello.o 程序中。链接器(ld)就负责处理这种合并。结果就得到 hello 文件,它是一个可执行文件,可以被加载到内存中,由系统执行。
#### 处理器读并解释存储在内存中的指令
##### 系统的硬件组成
1. 总线
贯穿整个系统的是一组电子管道,称为总线,它携带信息字节并负责在各个部件间传递。通常总线被设计成传送定长的字节快,也称为字。字中的字节数是一个基本的系统参数,各个系统中都不尽相同。现在的大多数机器字长要么是 4 个字节(32位),要么是 8 个字节(64位)。
2. I/O 设备
I/O(输入/输出)设备是系统与外部世界的联系通道。我们的示例系统包括四个 I/O 设备:作为用户输入的键盘和鼠标,作为用户输出的显示器,以及用于长期存储数据和程序的磁盘。最开始,可执行程序 hello 就存放在磁盘上。
每个 I/O 设备都通过一个控制器或适配器与 I/O 总线相连。控制器和适配器之间的区别主要在于它们的封装方式。控制器是 I/O 设备本身或者系统的主板上的芯片组。而适配器则是一块插在主板插槽上的卡。无论如何,它们的功能都是在 I/O 总线和 I/O 设备之间传递信息。
3. 主存
主存是一个临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。
Loading…
Cancel
Save