Android APT シリーズ (3): APT テクノロジーの探求

APT の概要

APTとは何ですか?

APT の正式名は Annotation Processing Tool で、翻訳すると注釈プロセッサとなります。APT の公式紹介文の引用: APT は注釈を処理するためのツールであり、ソース コード ファイルを検出してその中の注釈を見つけ、追加の処理に注釈を使用します。
APT は何のためにあるのでしょうか?

APT は、コンパイル中のコンパイル段階でアノテーションに従ってコードを自動的に生成し、使用を簡素化します。APT テクノロジーは、ButterKnife、Retrofit、Arouter、EventBus などの多くの一般的なフレームワークで使用されています。APT
プロジェクト
1)、APT プロジェクトの作成

一般に、APT の一般的な実装プロセスは次のとおりです。
1. アノテーションを書き込むための Java モジュールを作成します。
2. アノテーション情報を読み取るための Java モジュールを作成し、指定されたルールに従って対応するクラス ファイルを生成します
。 3. 生成されたクラスを取得する Android モジュールを作成します。リフレクションを通じて、適切なカプセル化を実行し、呼び出しのために上位層に提供します。下の図に示すように
:
ここに画像の説明を挿入これは私の APT プロジェクトです。モジュールの名前は任意に選択できます。上で述べたルールに従ってください。

2)、モジュールの依存関係

プロジェクトの作成後、各モジュール間の依存関係を明確にする必要があります。

1. apt-processor は apt-annotation のアノテーションを読み取る必要があるため、apt-processor は apt-annotation に依存する必要があります。

//apt-processor 的 build.gradle 文件
dependencies {
    
    
    implementation project(path: ':apt-annotation')
}

2. アプリは呼び出し層として使用され、上記の 3 つのモジュールはすべて依存する必要があります。

//app 的 build.gradle 文件
dependencies {
    
    
    //...
    implementation project(path: ':apt-api')
    implementation project(path: ':apt-annotation')
    annotationProcessor project(path: ':apt-processor')
}

APT プロジェクトを構成した後、各モジュールに特定のコードを記述し、
apt-annotation アノテーションを記述できます。

このモジュールの処理は比較的単純で、対応するカスタム アノテーションを記述するだけです。次のように書きました。

@Inherited
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({
    
    ElementType.TYPE,ElementType.METHOD})
public @interface AptAnnotation {
    
    
    String desc() default "";
}

apt-processor はコードを自動的に生成します

このモジュールは比較的複雑で、以下の 3 つのステップに分かれています。
1. アノテーション プロセッサの宣言
2. アノテーション プロセッサの登録
3. アノテーション プロセッサの生成クラス ファイル
1) アノテーション プロセッサの宣言

1. 新しいクラスを作成し、好みに応じてクラス名を選択し、javax.annotation.processing パッケージの下にある AbstractProcessor クラスを継承し、その抽象メソッドを実装します。

public class AptAnnotationProcessor extends AbstractProcessor {
    
    
  
    /**
     * 编写生成 Java 类的相关逻辑
     *
     * @param set              支持处理的注解集合
     * @param roundEnvironment 通过该对象查找指定注解下的节点信息
     * @return true: 表示注解已处理,后续注解处理器无需再处理它们;false: 表示注解未处理,可能要求后续注解处理器处理
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    
    
        return false;
    }
}

最初のパラメータの TypeElement に注目してください。これは Element の知識に関係します。簡単に紹介しましょう: 要素の概要
実際
、Java ソース ファイルは構造言語であり、ソース コードの各部分は特定の種類の要素に対応します。パッケージ、クラス、フィールド、メソッドなど:

package com.dream;         // PackageElement:包元素

public class Main<T> {
    
         // TypeElement:类元素; 其中 <T> 属于 TypeParameterElement 泛型元素

    private int x;         // VariableElement:变量、枚举、方法参数元素

    public Main() {
    
            // ExecuteableElement:构造函数、方法元素
    }
}

JavaのElementはインターフェースであり、ソースコードは次のとおりです。

public interface Element extends javax.lang.model.AnnotatedConstruct {
    
    
    // 获取元素的类型,实际的对象类型
    TypeMirror asType();
    // 获取Element的类型,判断是哪种Element
    ElementKind getKind();
    // 获取修饰符,如public static final等关键字
    Set<Modifier> getModifiers();
    // 获取类名
    Name getSimpleName();
    // 返回包含该节点的父节点,与getEnclosedElements()方法相反
    Element getEnclosingElement();
    // 返回该节点下直接包含的子节点,例如包节点下包含的类节点
    List<? extends Element> getEnclosedElements();

    @Override
    boolean equals(Object obj);
  
    @Override
    int hashCode();
  
    @Override
    List<? extends AnnotationMirror> getAnnotationMirrors();
  
    //获取注解
    @Override
    <A extends Annotation> A getAnnotation(Class<A> annotationType);
  
    <R, P> R accept(ElementVisitor<R, P> v, P p);
}

上記の情報の一部は Element を通じて取得できます (コメント付きのものが一般的に使用されます)
Element から派生した拡張クラスは 5 種類あります:
1. PackageElement はパッケージ プログラム要素を表します
2. TypeElement はクラスまたはインターフェイス プログラム要素を表します
3. TypeParameterElement はジェネリック要素を表します
4、VariableElement はフィールド、列挙型定数、メソッドまたはコンストラクター パラメーター、ローカル変数または例外パラメーターを表します
5、ExecutableElement はクラスまたはインターフェイス メソッド、コンストラクターまたは初期化子 (静的またはインスタンス) を表します
。 TypeElement は複数の要素を表す場合があります。たとえば、TypeElement はクラスまたはインターフェイスを表します。このとき、element.getKind() によって区別できます。

Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(AptAnnotation.class);
for (Element element : elements) {
    
    
    if (element.getKind() == ElementKind.CLASS) {
    
    
        // 如果元素是类

    } else if (element.getKind() == ElementKind.INTERFACE) {
    
    
        // 如果元素是接口

    }
}

ElementKind は、次のように多くの値を持つ列挙クラスです。

PACKAGE //表示包
ENUM //表示枚举
CLASS //表示类
ANNOTATION_TYPE //表示注解
INTERFACE //表示接口
ENUM_CONSTANT //表示枚举常量
FIELD //表示字段
PARAMETER //表示参数
LOCAL_VARIABLE //表示本地变量
EXCEPTION_PARAMETER //表示异常参数
METHOD //表示方法
CONSTRUCTOR //表示构造函数
OTHER //表示其他

これはElementの紹介です。下から見てみましょう

2. 書き換えメソッドの解釈
実装する必要がある抽象メソッドに加えて、一般的に使用される他の 4 つのメソッドも次のように書き換えることができます。

public class AptAnnotationProcessor extends AbstractProcessor {
    
    
    //...
  
    /** 
     * 节点工具类(类、函数、属性都是节点)
     */
    private Elements mElementUtils;

    /** 
     * 类信息工具类
     */
    private Types mTypeUtils;

    /**
     * 文件生成器
     */
    private Filer mFiler;

    /**
     * 日志信息打印器
     */
    private Messager mMessager;
  
    /**
     * 做一些初始化的工作
     * 
     * @param processingEnvironment 这个参数提供了若干工具类,供编写生成 Java 类时所使用
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
    
    
        super.init(processingEnv);
        mElementUtils = processingEnv.getElementUtils();
        mTypeUtils = processingEnv.getTypeUtils();
        mFiler = processingEnv.getFiler();
        mMessager = processingEnv.getMessager();
    }
  
    /**
     * 接收外来传入的参数,最常用的形式就是在 build.gradle 脚本文件里的 javaCompileOptions 的配置
     *
     * @return 属性的 Key 集合
     */
    @Override
    public Set<String> getSupportedOptions() {
    
    
        return super.getSupportedOptions();
    }

    /**
     * 当前注解处理器支持的注解集合,如果支持,就会调用 process 方法
     *
     * @return 支持的注解集合
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
    
    
        return super.getSupportedAnnotationTypes();
    }
        
    /**
     * 编译当前注解处理器的 JDK 版本
     *
     * @return JDK 版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
    
    
        return super.getSupportedSourceVersion();
    }
}

注: アノテーションを使用して、getSupportedAnnotationTypes()、getSupportedSourceVersion()、および getSupportedOptions() の 3 つのメソッドを提供することもできます。

@SupportedOptions("MODULE_NAME")
@SupportedAnnotationTypes("com.dream.apt_annotation.AptAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AptAnnotationProcessor extends AbstractProcessor {
    
    
    //...
}

2)、アノテーションプロセッサの登録

アノテーション プロセッサが宣言され、次のステップはそれを登録することです。登録には 2 つの方法があります:
1. 手動登録
2. 自動登録
手動登録は面倒でエラーが発生しやすくなります。推奨されないため、ここでは説明しません。自動登録を主に見る
自動登録

1. まず、次の依存関係をモジュール apt-processor の下の build.gradle ファイルにインポートする必要があります。

implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

注: これら 2 つの文を追加する必要があります。追加しないと登録は成功しません。前にピットを踏みました。

2. @AutoService(Processor.class) をアノテーション プロセッサに追加して登録を完了します

@AutoService(Processor.class)
public class AptAnnotationProcessor extends AbstractProcessor {
    
    
    //...
}

3) アノテーションプロセッサがクラスファイルを生成

登録が完了したら、Java クラス ファイルを生成するコードを正式に記述することができます。生成方法は 2 つあります。 1.
従来のファイルの記述方法
2. javapoet フレームワークを使用した記述方法 1 は
比較的厳格ですが、各文字は書き留める必要がありますが、使用はお勧めできませんので、ここでは説明しません。ここでは主に、javapoet フレームワークを通じて Java クラス ファイルを生成するjavapoet メソッドについて説明します。

このメソッドは、オブジェクト指向コーディングのスタイルにより一致しています。Javapoet に詳しくない友人は、github にアクセスしてポータルの波を学ぶことができます。ここでは、一般的に使用されるクラスのいくつかを紹介します: TypeSpec: クラスの生成に使用されます。インターフェイス、
列挙型 オブジェクトのクラス MethodSpec
: メソッド オブジェクトの生成に使用されるクラス
ParameterSpec: パラメーター オブジェクトの生成に使用されるクラス
AnnotationSpec: 注釈オブジェクトの生成に使用されるクラスFieldSpec: 生成されるメンバー変数ClassName
の構成に使用されるクラス
: パッケージ名とクラス名 Object によって生成され、JavaPoet で Class を指定するのと同じです
ParameterizedTypeName: MainClass および IncludeClass を通じてジェネリックスを含むクラスを生成します
JavaFile: 生成された Java ファイルの出力クラスを制御します
1. Javapoet フレームワークの依存関係をインポートします

implementation 'com.squareup:javapoet:1.13.0'

2. 指定されたコードテンプレートに従って Java クラスファイルを生成します

たとえば、アプリの build.gradle で次の構成を作成しました。

android {
    
    
    //...
    defaultConfig {
    
    
        //...
        javaCompileOptions {
    
    
            annotationProcessorOptions {
    
    
                arguments = [MODULE_NAME: project.getName()]
            }
        }
    }
}

次の注釈が MainActivity の下に作成されます。

@AptAnnotation(desc = "我是 MainActivity 上面的注解")
public class MainActivity extends AppCompatActivity {
    
    

    @AptAnnotation(desc = "我是 onCreate 上面的注解")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyAptApi.init();
    }
}@AptAnnotation(desc = "我是 MainActivity 上面的注解")
public class MainActivity extends AppCompatActivity {
    
    

    @AptAnnotation(desc = "我是 onCreate 上面的注解")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyAptApi.init();
    }
}

生成したいコードは次のとおりです。

public class HelloWorld {
    
    
  public void test(String param) {
    
    
    System.out.println("模块: apt-app");
    System.out.println("节点: MainActivity  描述: 我是 MainActivity 上面的注解");
    System.out.println("节点: onCreate  描述: 我是 onCreate 上面的注解");
  }
}

では実際にやってみましょう:

@AutoService(Processor.class)
@SupportedOptions("MODULE_NAME")
@SupportedAnnotationTypes("com.dream.apt_annotation.AptAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AptAnnotationProcessor extends AbstractProcessor {
    
    
        
    //文件生成器
    Filer filer;
    //模块名
    private String mModuleName;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
    
    
        super.init(processingEnvironment);
        //初始化文件生成器
        filer = processingEnvironment.getFiler();
        //通过 key 获取 build.gradle 中对应的 value
        mModuleName = processingEnv.getOptions().get("MODULE_NAME");
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    
    
        if (set == null || set.isEmpty()) {
    
    
            return false;
        }
                
        //获取当前注解下的节点信息
        Set<? extends Element> rootElements = roundEnvironment.getElementsAnnotatedWith(AptAnnotation.class);

        // 构建 test 函数
        MethodSpec.Builder builder = MethodSpec.methodBuilder("test")
                .addModifiers(Modifier.PUBLIC) // 指定方法修饰符
                .returns(void.class) // 指定返回类型
                .addParameter(String.class, "param"); // 添加参数
        builder.addStatement("$T.out.println($S)", System.class, "模块: " + mModuleName);

        if (rootElements != null && !rootElements.isEmpty()) {
    
    
            for (Element element : rootElements) {
    
    
                //当前节点名称
                String elementName = element.getSimpleName().toString();
                //当前节点下注解的属性
                String desc = element.getAnnotation(AptAnnotation.class).desc();
                // 构建方法体
                builder.addStatement("$T.out.println($S)", System.class, 
                                     "节点: " + elementName + "  " + "描述: " + desc);
            }
        }
        MethodSpec main =builder.build();

        // 构建 HelloWorld 类
        TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                .addModifiers(Modifier.PUBLIC) // 指定类修饰符
                .addMethod(main) // 添加方法
                .build();

        // 指定包路径,构建文件体
        JavaFile javaFile = JavaFile.builder("com.dream.aptdemo", helloWorld).build();
        try {
    
    
            // 创建文件
            javaFile.writeTo(filer);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }

        return true;
    }
}

上記の手順の後、アプリを実行して上記のスクリーンショットのコードを生成できます。生成されたコードを使用する最終ステップがまだ残っています。

注: Gradle のバージョンが異なると生成されるクラス ファイルの場所は異なる場合があります。私の Gradle バージョンは 6.7.1 で、生成されたクラス ファイルは次の場所にあります: Gradle の一部の下位バージョンで生成されたクラス ファイルは、ディレクトリ /
ここに画像の説明を挿入ビルド/生成/ソース ダウン

ビジネス機能を完了するためのコードを生成するための apt-api 呼び出し

このモジュールの操作は比較的単純で、生成されたクラスをリフレクションを通じて取得し、それをカプセル化してそれに応じて使用します。

public class MyAptApi {
    
    

    @SuppressWarnings("all")
    public static void init() {
    
    
        try {
    
    
            Class c = Class.forName("com.dream.aptdemo.HelloWorld");
            Constructor declaredConstructor = c.getDeclaredConstructor();
            Object o = declaredConstructor.newInstance();
            Method test = c.getDeclaredMethod("test", String.class);
            test.invoke(o, "");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

次に、MainActivity の oncreate メソッドでそれを呼び出します。

@AptAnnotation(desc = "我是 MainActivity 上面的注解")
public class MainActivity extends AppCompatActivity {
    
    
  
    @AptAnnotation(desc = "我是 onCreate 上面的注解")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyAptApi.init();
    }
}

//結果の印刷
モジュール: app
ノード: MainActivity 説明: 私は MainActivity の上のアノテーションですノード: onCreate説明
: 私は onCreate の上のアノテーションです

この記事の重要なポイント:
1. APT プロジェクトで作成する必要があるさまざまなタイプのモジュールとモジュール間の依存関係
2. Java ソース ファイルは実際には構造言語であり、ソース コードの各部分は特定のモジュールの作成に対応しています。要素のタイプ
3. 自動サービスを使用して、アノテーション プロセッサを自動的に登録します。
4. javapoet フレームワークを使用して、生成された Java クラス ファイルを書き込みます。
5. 生成されたクラスの機能を、リフレクションと適切なカプセル化転送を通じて上位層に提供します。

おすすめ

転載: blog.csdn.net/qq_24252589/article/details/131403778