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

  1. For official release packages, debug and verbose logs are automatically not displayed.
  2. Have more useful information, including application names, log file and line information, timestamps, threads, and so on.
  3. 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.
  4. You can override the write location and format of the log.