The Bug that can’t be changed, the pretense that can’t be written. The public account Yang Zhengyou now focuses on audio and video and APM, covering various fields of knowledge; Only do the whole network the most Geek public number, welcome your attention!
preface
We generate Bean objects via AndroidStudio with annotations that automatically generate getter/setter methods, equals(), and hashCode() methods, where classes (or interfaces) conform to camel nomenclature and are capitalized. Methods should follow camel nomenclature, lowercase, and class or instance variables should follow camel nomenclature, lowercase. Constants must be all uppercase letters or underscores, and the first character must not be an underscore, otherwise the compiler will warn
So: how does the compiler resolve these noncanonical naming conventions? We have to mention a very important bytecode staking technique CALLED AST. What is AST?
1. AST concepts
AST is short for Abstract Syntax Tree, which is the result of the compiler’s processing of the first step of the code. It is a Tree representation of the source code. Each element of the source code maps to a node or subsection tree
Java compilation process
Before getting to know the AST, understand the entire Java compilation process: there are three phases
2.1 Stage 1
All source files are parsed into a syntax tree
2.2 Stage 2
Call the annotation handler, the APT module. If the annotation processor generates a new source file, the new source file is also compiled
2.3 Stage 3
Syntax trees are parsed into class files
3. AST principles
What the compiler does to the code is something like this
JavaTXT -> Lexical analysis -> Generate AST -> Semantic analysis -> compile bytecode
With the operation AST, you can achieve the function of modifying source code
Iv. Code implementation level APT + AST
- 4.1 Get all Elements by AnnotationProcessor’s process method
- 4.2 Customize the TreeTranslator. Check methods in visitMethodDef
- 4.3 If it is the target method, insert the code through the RELEVANT API of AST framework
5. AST defects
- 5.1 Lambda is not supported
- 5.2 APT cannot scan other moudles, and AST cannot process other Moudles
Vi. AST application scenarios: code specification inspection
- 6.1 Non-null determination of object calls
- 6.2 Write our specific syntax rules and modify or optimize the code that does not conform to the rules
- 6.3 Add, delete, modify and check
7. AST advantages
AST operations are at the compiler level and have no impact on program execution, making them more efficient than other AOP operations
Common AST apis
- Abstract inner class, internal definition of access to a variety of grammar node methods, after obtaining the corresponding grammar node we can add and delete or modify the grammar node statements;
- Visitor subclasses include TreeScanner (scans all syntax nodes) and TreeTranslator (scans nodes and can convert syntax nodes to another syntax node)
9. Application of AST in Android
Android Lint is a static code checking tool provided by Google for Android developers. The internal base has wrapped a layer for our AST, and using Lint to scan and inspect Android engineering code can find potential problems in code and alert programmers to fix them early. So that the code doesn’t look so bad, today I’ll show you how to build a Lint tool for your own enterprise project
X. Development steps
Requirements:
- Log output
Disable Log and system. out logs to prevent some private data from being leaked
- Toast
Prohibit direct use of system Toast to ensure crash probability on styles and lower versions of Room
- File naming detection
Resource files (layout, Drawble, Anim, color, DIME, style, string) must be named after module
- Thread detection
Avoid creating new threads yourself
- Serialization detection
10.1 Creating a Java Project and configuring Gradle
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs'.include: ['*.jar'])
// lint-API: an official API. The API is not the final version and may change at any time
compileOnly 'com. Android. Tools. Lint: lint - API: 27.1.0'
// lint-checks: checks already exist
compileOnly 'com. Android. Tools. Lint: lint - checks: 27.1.0'
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = "8"
targetCompatibility = "8"
Copy the code
10.2 create the Detector
Detector is responsible for scanning the code, finding problems and reporting them
10.2.1 CHECKING the Id Type
/** * Id Id can only be defined as long, cannot be defined as int, char, or short ** @since 1.0 */
public class IdCheckDetector extends Detector implements Detector.UastScanner {
private static final String REGEX = ".*id";
public static final Issue ISSUE = Issue.create(
"IdDefinedError"."Id of number type should be defined long not int."."Please change the Id to long!",
Category.CORRECTNESS, 9, Severity.ERROR,
new Implementation(IdCheckDetector.class, Scope.JAVA_FILE_SCOPE)
);
@Nullable
@Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Arrays.asList(UClass.class);
}
@Nullable
@Override
public UElementHandler createUastHandler(@NotNull JavaContext context) {
return new UElementHandler() {
@Override
public void visitClass(@NotNull UClass node) {
// Current class check
check(node, context);
UClass[] innerClasses = node.getInnerClasses();
for (UClass uClass : innerClasses) {
// Internal class check
check(uClass, context);
}
super.visitClass(node); }}; }private void check(@NotNull UClass node, @NotNull JavaContext context) {
for (UField field : node.getFields()) {
String name = field.getName();
if(name ! =null && name.toLowerCase().matches(REGEX)
&& (field.getType() == PsiType.INT || field.getType() == PsiType.CHAR || field.getType() == PsiType.SHORT)) {
context.report(ISSUE, context.getLocation(field), "Id of number type should be defined long not int."); }}}}Copy the code
10.2.2 message. Obtain () to check
public class MessageObtainDetector extends Detector implements Detector.UastScanner {
private static final Class<? extends Detector> DETECTOR_CLASS = MessageObtainDetector.class;
private static final EnumSet<Scope> DETECTOR_SCOPE = Scope.JAVA_FILE_SCOPE;
public static final Issue ISSUE = Issue.create(
"MessageObtainUseError"."Direct new Message() is not recommended".It is recommended to call {handler.obtainMessage} or {message.obtain ()} to Obtain the cached Message.,
Category.PERFORMANCE,
9,
Severity.WARNING,
new Implementation(DETECTOR_CLASS, DETECTOR_SCOPE)
);
@Nullable
@Override
public List<String> getApplicableConstructorTypes(a) {
return Collections.singletonList("android.os.Message");
}
@Override
public void visitConstructor(JavaContext context, UCallExpression node, PsiMethod constructor) {
context.report(ISSUE, node, context.getLocation(node), It is recommended to call {handler.obtainMessage} or {message.obtain ()} to Obtain the cached Message.); }}Copy the code
10.2.3 Avoid Creating Threads by Yourself
public class MkThreadDetector extends Detector implements Detector.UastScanner {
public static final Issue ISSUE = Issue.create(
"New Thread"."Avoid creating your own Thread"."Do not call new Thread() directly. Use MkThreadManager instead.",
Category.PERFORMANCE, 5, Severity.ERROR,
new Implementation(NewThreadDetector.class, Scope.JAVA_FILE_SCOPE));
@Override
public List<String> getApplicableConstructorTypes(a) {
return Collections.singletonList("java.lang.Thread");
}
@Override
public void visitConstructor(JavaContext context, UCallExpression node, PsiMethod constructor) {
context.report(ISSUE, node, context.getLocation(node), "Do not call new Thread() directly. Use MkThreadManager instead."); }}Copy the code
10.2.4 Serialization internal Class check
public class MkSerializableDetector extends Detector implements Detector.UastScanner {
private static final String CLASS_SERIALIZABLE = "java.io.Serializable";
public static final Issue ISSUE = Issue.create(
"InnerClassSerializable"."Inner class needs to implement Serializable interface"."Inner class needs to implement Serializable interface",
Category.SECURITY, 5, Severity.ERROR,
new Implementation(SerializableDetector.class, Scope.JAVA_FILE_SCOPE));
@Nullable
@Override
public List<String> applicableSuperClasses(a) {
return Collections.singletonList(CLASS_SERIALIZABLE);
}
/** * Calls this method */ when the list specified by applicableSuperClasses() is scanned
@Override
public void visitClass(JavaContext context, UClass declaration) {
if (declaration instanceof UAnonymousClass) {
return;
}
sortClass(context, declaration);
}
private void sortClass(JavaContext context, UClass declaration) {
for (UClass uClass : declaration.getInnerClasses()) {
sortClass(context, uClass);
// Check whether Serializable is inherited and prompt
boolean hasImpled = false;
for (PsiClassType psiClassType : uClass.getImplementsListTypes()) {
if (CLASS_SERIALIZABLE.equals(psiClassType.getCanonicalText())) {
hasImpled = true;
break; }}if(! hasImpled) { context.report(ISSUE, uClass.getNameIdentifier(), context.getLocation(uClass.getNameIdentifier()), String.format("Inner class '%1$s' needs to implement Serializable interface", uClass.getName())); }}}}Copy the code
10.2.5 Disabling System Log/ system. out Logs
public class MkLogDetector extends Detector implements Detector.UastScanner {
private static final String SYSTEM_SERIALIZABLE = "System.out.println";
public static final Issue ISSUE = Issue.create(
"LogUse"."Disallow Log/ system.out.println"."MKLog is recommended to prevent printing logs in the official package.",
Category.SECURITY, 5, Severity.ERROR,
new Implementation(LogDetector.class, Scope.JAVA_FILE_SCOPE));
@Nullable
@Override
public List<String> getApplicableConstructorTypes(a) {
return Collections.singletonList(SYSTEM_SERIALIZABLE);
}
@Override
public List<String> getApplicableMethodNames(a) {
// The name of the method to test
return Arrays.asList("v"."d"."i"."w"."e"."wtf");
}
@Override
public void visitConstructor(JavaContext context, UCallExpression node, PsiMethod constructor) {
context.report(ISSUE, node, context.getLocation(node), "Please use MkLog instead of system.out.println"); }}Copy the code
10.3 Create Android Library project mk_lintrules
It is mainly used to rely on lint.jar, which is packaged as an AAR and uploaded to Maven
Gradle configured in
dependencies {
lintChecks project(':mk_lint')}Copy the code
10.4 IssueRegistry, which provides a list of issues to be detected
/** * Created by woodbox on 2020-10-15 **@since1.0 * /
public class IssuesRegister extends IssueRegistry {
@NotNull
@Override
public List<Issue> getIssues(a) {
return new ArrayList<Issue>() {{
add(NewThreadDetector.ISSUE);
add(MessageObtainDetector.ISSUE);
add(SerializableDetector.ISSUE);
add(IdCheckDetector.ISSUE);
add(LogDetector.ISSUE);
}};
}
@Override
public int getApi(a) {
return ApiKt.CURRENT_API;
}
@Override
public int getMinApi(a) {
/ / compatible with 3.1
return 1; }}Copy the code
Return the List of issues that need to be examined in the getIssues() method.
Declare the lint-registry property in build.grade
jar {
manifest {
attributes("Lint-Registry-v2": "com.github.microkibaco.mk_lint.IssuesRegister")}}Copy the code
The coding part of the custom Lint is done.
11. Advantages of Lint
- For official release packages, debug and verbose logs are automatically not displayed.
- Have more useful information, including application names, log file and line information, timestamps, threads, and so on.
- Because of variable parameters, Log performance is higher than Log when disabled. Because the most verbose logs tend to be debug or verbose logs, this can improve performance slightly.
- You can override the write location and format of the log.