Explication détaillée des annotations au moment de la compilation et de la mise en œuvre de ButterKnife

PS: Les gens sont une sorte de créatures qui sont très disposées à accepter l'auto-suggestion. Si vous vous donnez un signal négatif, vous deviendrez facilement décadent. Si vous vous donnez un signal positif, vous deviendrez également positif.

Aujourd'hui, jetez un œil aux connaissances pertinentes sur les annotations au moment de la compilation. Je pense qu'après une pratique manuelle, vous comprendrez plus facilement les frameworks tels que Dagger, ARouter, ButterKnife qui utilisent des annotations au moment de la compilation, et il sera plus facile de comprendre son implémentation du code source interne. Le contenu est le suivant:

  1. Annotations à la compilation et à l'exécution
  2. Processeur d'annotation APT
  3. RésuméProcesseur
  4. Élément 和 Éléments
  5. Processeur d'annotations personnalisé
  6. Utiliser un processeur d'annotations personnalisé

Annotations à la compilation et à l'exécution

Comprenez d'abord la différence entre le temps de compilation et le temps d'exécution:

  1. Temps de compilation: fait référence au processus par lequel le compilateur traduit le code source en code qui peut être reconnu par la machine. En Java, il s'agit du processus de compilation du code source Java dans un fichier bytecode reconnu par la JVM.
  2. Runtime: fait référence au processus d'allocation de mémoire par JVM et d'interprétation et d'exécution des fichiers bytecode.

Les @Retentionnotes de décision de méta-annotation sont toujours en fonctionnement, elles peuvent être configurées au moment de la compilation. La stratégie est la suivante:

public enum RetentionPolicy {
    
    
    SOURCE,  //在编译时会被丢弃,仅仅在源码中存在
    CLASS,   //默认策略,运行时就会被丢弃,仅仅在 class 文件中
    RUNTIME  //编译时会将注解信息记录到class文件,运行时任然保留,可以通过反射获取注解信息
}

Les annotations à la compilation et les annotations à l'exécution éliminent les différences ci-dessus, et les méthodes d'implémentation sont également différentes. Les annotations à la compilation sont généralement implémentées via le processeur d'annotations (APT) et les annotations à l'exécution sont généralement implémentées par réflexion.

Pour les commentaires et la réflexion, vous pouvez vous référer aux deux articles suivants:

Qu'est-ce que l'APT

APT (Annotation Processing Tool) est un outil fourni par javac qui peut traiter les annotations. Il est utilisé pour analyser et traiter les annotations au moment de la compilation. En termes simples, vous pouvez obtenir les informations sur les annotations et l'emplacement des annotations via APT, et vous pouvez les utiliser. Les informations sont générées par le compilateur dans le code. L'annotation au moment de la compilation consiste à utiliser APT pour générer du code via des informations d'annotation afin de compléter certaines fonctions. Les représentants typiques incluent ButterKnife, Dagger, ARouter, etc.

RésuméProcesseur

AbstractProcessorLa réalisation Processorest un processeur d'annotation de classe abstraite pour réaliser des processeurs d'annotation nécessitant AbstractProcessorune extension de l' héritage , la méthode principale est la suivante:

  • INIT: Initialisation du processeur, disponible à partir du paramètre d' ProcessingEnvironmentacquérir des outils Elements, Types, Fileret Messageranalogues;
  • getSupportedSourceVersion: renvoie la version Java utilisée;
  • getSupportedAnnotationTypes: renvoie les noms de toutes les annotations à traiter;
  • processus: Obtenez toutes les annotations spécifiées pour le traitement;

La méthode de processus peut être exécutée plusieurs fois pendant le processus en cours, jusqu'à ce qu'aucune autre classe ne soit générée.

Élément 和 Éléments

ElementXML est similaire à la balise, en Java Elementreprésentent des éléments de programme, tels que des classes, des membres, des méthodes, chaque Elementexpression exprimée ne montre qu'une certaine structure de Elementla classe d'objet d'opération, use ou getKind visiteur (méthode de détermination plus spécifique ElementLes sous-catégories de traitement sont comme suit:

  • Élément exécutable
  • PackageElement
  • Paramétrable
  • Qualifié
  • Élément de type
  • TypeParamètreÉlément
  • VariableElement

La structure d'élément ci-dessus correspond à la structure de code suivante:

// PackageElement
package manu.com.compiler;

// TypeElement
public class ElementSample {
    
    
    // VariableElement
    private int a;
    // VariableElement
    private Object other;

    // ExecutableElement
    public ElementSample() {
    
    
    }
    // 方法参数VariableElement
    public void setA(int newA) {
    
    
    }
    // TypeParameterElement表示参数化类型,用在泛型参数中
}

ElementsNous traitons des Elementoutils pour ne fournir qu'une interface, en particulier la plate-forme Java.

Processeur d'annotations de compilation personnalisé

Ce qui suit utilise APT pour obtenir un commentaire @Bindpour remplacer les notes pour imiter le ButterKnife @BindView, Case Project est structuré comme suit:

APTE

Module d'annotation Api défini dans @Bindce qui suit:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface Bind {
    
    
    int value();
}

Le module de compilateur Java est un module, le service automatique Google introduit pour générer le fichier approprié dans META-INFO, javapoet plus pratique pour créer des fichiers Java, le processeur d'annotation personnalisé BindProcessorcomme suit:

// 用来生成META-INF/services/javax.annotation.processing.Processor文件
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
// 用于创建Java文件
implementation 'com.squareup:javapoet:1.12.1'
/**
 * BindProcessor
 */
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor {
    
    
    private Elements mElements;
    private Filer mFiler;
    private Messager mMessager;

    // 存储某个类下面对应的BindModel
    private Map<TypeElement, List<BindModel>> mTypeElementMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
    
    
        super.init(processingEnvironment);
        mMessager = processingEnvironment.getMessager();
        print("init");
        // 初始化Processor

        mElements = processingEnvironment.getElementUtils();
        mFiler = processingEnvironment.getFiler();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
    
    
        print("getSupportedSourceVersion");
        // 返回使用的Java版本
        return SourceVersion.RELEASE_8;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
    
    
        print("getSupportedAnnotationTypes");
        // 返回要处理的的所有的注解名称
        Set<String> set = new HashSet<>();
        set.add(Bind.class.getCanonicalName());
        set.add(OnClick.class.getCanonicalName());
        return set;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    
    
        print("process");
        mTypeElementMap.clear();
        // 获取指定Class类型的Element
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Bind.class);
        // 遍历将符合条件的Element存储起来
        for (Element element : elements) {
    
    
            // 获取Element对应类的全限定类名
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            print("process typeElement name:"+typeElement.getSimpleName());
            List<BindModel> modelList = mTypeElementMap.get(typeElement);
            if (modelList == null) {
    
    
                modelList = new ArrayList<>();
            }
            modelList.add(new BindModel(element));
            mTypeElementMap.put(typeElement, modelList);
        }

        print("process mTypeElementMap size:" + mTypeElementMap.size());

        // Java文件生成
        mTypeElementMap.forEach((typeElement, bindModels) -> {
    
    
            print("process bindModels size:" + bindModels.size());
            // 获取包名
            String packageName = mElements.getPackageOf(typeElement).getQualifiedName().toString();
            // 生成Java文件的文件名
            String className = typeElement.getSimpleName().toString();
            String newClassName = className + "_ViewBind";

            // MethodSpec
            MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(ClassName.bestGuess(className), "target");
            bindModels.forEach(model -> {
    
    
                constructorBuilder.addStatement("target.$L=($L)target.findViewById($L)",
                        model.getViewFieldName(), model.getViewFieldType(), model.getResId());
            });
            // typeSpec
            TypeSpec typeSpec = TypeSpec.classBuilder(newClassName)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(constructorBuilder.build())
                    .build();
            // JavaFile
            JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
                    .addFileComment("AUTO Create")
                    .build();

            try {
    
    
                javaFile.writeTo(mFiler);
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        });

        return true;
    }

    private void print(String message) {
    
    
        if (mMessager == null) return;
        mMessager.printMessage(Diagnostic.Kind.NOTE, message);
    }
}

Qui BindModelsont @Binddes informations d'encapsulation simples de commentaire , comme suit:

/**
 * BindModel
 */
public class BindModel {
    // 成员变量Element
    private VariableElement mViewFieldElement;
    // 成员变量类型
    private TypeMirror mViewFieldType;
    // View的资源Id
    private int mResId;

    public BindModel(Element element) {
        // 校验Element是否是成员变量
        if (element.getKind() != ElementKind.FIELD) {
            throw new IllegalArgumentException("element is not FIELD");
        }
        // 成员变量Element
        mViewFieldElement = (VariableElement) element;
        // 成员变量类型
        mViewFieldType = element.asType();
        // 获取注解的值
        Bind bind = mViewFieldElement.getAnnotation(Bind.class);
        mResId = bind.value();
    }

    public int getResId(){
        return mResId;
    }

    public String getViewFieldName(){
        return mViewFieldElement.getSimpleName().toString();
    }

    public TypeMirror getViewFieldType(){
        return mViewFieldType;
    }
}

Créez le fichier à générer dans le module de liaison

/**
 * 初始化
 */
public class BindKnife {
    
    
    public static void bind(Activity activity) {
    
    
        // 获取activity的全限定类名
        String name = activity.getClass().getName();
        try {
    
    
            // 反射创建并注入Activity
            Class<?> clazz = Class.forName(name + "_ViewBind");
            clazz.getConstructor(activity.getClass()).newInstance(activity);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

Bien sûr, ButterKnife doit mettre en cache les objets créés et ne créera pas de nouveaux objets à chaque fois. Bien que la réflexion soit également utilisée, le test de réflexion est beaucoup plus réduit par rapport aux annotations d'exécution, de sorte que les performances sont également meilleures que les annotations d'exécution. Encore mieux, cela peut être considérée comme la différence entre les annotations à la compilation et les annotations à l'exécution.

Utiliser un processeur d'annotations personnalisé

L'utilisation est similaire à ButterKnife, comme suit:

public class MainActivity extends AppCompatActivity{
    
    
   
    @Bind(R.id.tvData)
    TextView tvData;

    @Override
     public void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        BindKnife.bind(this);
        tvData.setText("data");
    }
}

Comprendre les annotations au moment de la compilation peut ne pas créer immédiatement des roues, mais cela est très utile lorsque vous examinez d'autres frameworks, et il existe une autre façon de résoudre les problèmes.

Insérez la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/jzman/article/details/111350272
conseillé
Classement