概要
今日の多くのオープンソース フレームワークでは、ソース コード内にアノテーション プロセッサの影がよく見られます。たとえば、私たちがよく知っている Ali の ARouter、ButterKnif、Android 開発における代替の findViewById アーティファクト、EventBus などはすべてアノテーションを使用しています。デバイス APT テクノロジを処理するための、次の図は、ARouter のプロジェクト構造図の部分的なスクリーンショットです。
図中の赤丸はARouterアノテーションプロセッサ技術の実装モジュールです。アノテーション プロセッサを使用するもう 1 つの利点は、一部の関数のリフレクションによって引き起こされる損失の問題を解決できることです。注意,这里不是说注解处理器的技术是取代反射的哈
実際、プログラムの実行中にリフレクションを使用してオブジェクトを動的に取得し、このオブジェクトの対応するメソッドを操作して、実装したい機能を完成させます。しかし、このプロセスには時間がかかります。アノテーションプロセッサは、実現したい機能に対応するコードをコンパイル時に生成するもので、リフレクション技術からアノテーションプロセッサ技術の応用までのEventBusの実装が代表例です。次に、この記事では、アノテーション プロセッサとは何か、またそれを使用して開発効率を効率的に向上させる方法を紹介します。
1. アノテーションプロセッサAPTとは
Annotation Process Tool (アノテーション プロセス ツール) は、その名の通り、アノテーションを処理するためのツールです。私たちが普段書く冗長なコードを大幅に最適化することができます。代表的なものは、Android 開発者によってよく書かれる findViewById です。基本的には同じです。はい、手動で書くのは手間がかかるだけでなく、エラーも発生しやすくなります。アノテーション プロセッサ テクノロジを使用して最適化した後は、必要なコードは 2 行だけです。
@DIView(value = R.id.tv_text)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
上記のコードと同様、@DIView(value = R.id.tv_text)
これはアノテーション プロセッサの一部です。このアノテーションは呼び出し元に提供され、その後、キー パラメータ値がカスタム アノテーションに渡されます。アノテーション プロセッサがキー パラメータ値を取得した後、非常に反復的な処理が行われます。コードは均一に生成され、最終的にアプリケーションにパッケージ化されるため、APT テクノロジを使用すると、カスタム アノテーションがコンパイル中に有効になるように設定されます。たとえば、ARounter のカスタム アノテーション Route の有効範囲はコンパイル中に有効です。@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
/**
* Path of route
*/
String path();
/**
* Used to merger routes, the group name MUST BE USE THE COMMON WORDS !!!
*/
String group() default "";
/**
* Name of route, used to generate javadoc.
*/
String name() default "";
/**
* Extra data, can be set by user.
* Ps. U should use the integer num sign the switch, by bits. 10001010101010
*/
int extras() default Integer.MIN_VALUE;
/**
* The priority of route.
*/
int priority() default -1;
}
其实很好理解,因为我们使用APT技术主要是为我们在编译期间生成一些我们不想写的重复性代码,当代码生成完后,这些注解也就完成了它们的工作。除非这些注解在代码运行的时候还需要反射使用,否则我们都没有必要将这些注解范围定义成运行时有效。
至此,相信读者已经明白注解处理器是做啥的了,用一句话概括就是,注解处理器是处理我们自定义注解的工具,它帮助我们处理生成那些重复性比较高的代码,让我们不用去关心和处理那些重复的逻辑。
2.应用场景
那么注解处理器的应用场景主要有哪些呢,其实注解处理器是为了解放开发者的,它是一个面向开发者的工具,它的主要应用场景就是做框架的开发,例如阿里的ARouter路由框架,网络请求框架Retrofit,事件总线EventBus等,所以APT注解处理器技术是一个简化我们开发的工具,我们开发框架的时候,可以提取出框架中的很多逻辑差不多,但又大量重复的内容,使用注解处理器去优化。对注解处理器感兴趣的读者,建议去阅读下上面提到的几个框架源码,写的特别好。
3.如何使用
接下来,到了最关键的时刻了,说一千道一万,不会使用就完蛋,接下来利用我在视频网站上学到的一个类似黄油刀的例子,来介绍APT技术的使用方法。我们只实现一个功能,就是使用我们定义的注解去注解Android的View,然后直接使用这个View,如下所示:
@DIActivity
public class MainActivity extends AppCompatActivity {
@DIView(value = R.id.tv_text)
TextView textView;
@DIView(value = R.id.tv_text_1)
TextView textView1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// bindView方法其实就是取代findViewById的替代
DIMainActivity.bindView(this);
textView.setText("hello apt!!!!");
textView1.setText("hello apt again!!!!");
textView1.setTextSize(15);
}
}
我们使用一个DIActivity注解我们的类,然后使用@DIView(value = R.id.tv_text) 注解我们声明的View,然后直接在程序中使用被我们注解的属性。然后框架就能帮我们实现这个对应的findViewByID 的逻辑。接下来是实现这个功能的步骤:
3.1 创建注解API模块
这里我们需要新建一个Java Library模块,记住一定是Java Library,这个模块主要是用于做自定义注解的定义,给注解处理器和调用者使用。
然后定义好我们需要使用到的自定义注解
DIActivity アノテーションは、使用するアクティビティで使用され、生成されたコードによって作成されたオブジェクトを、使用するアクティビティに割り当てるために使用されます。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {
}
DIView は、使用したい View で使用されます。呼び出し元は、このアノテーションを使用して View の ID 値を渡し、View オブジェクトを取得するだけで済みます。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface DIView {
int value() default 0;
}
3.2 アノテーションプロセッサモジュールの作成
カスタム アノテーション クラスを作成したら、次にアノテーション プロセッサ モジュールを導入します。フレームワークの主な作業はアノテーション プロセッサで完了します。また、定義したアノテーションを処理するための新しい Java ライブラリ モジュールも作成します。以下に示すように
注意する必要があるのは、このモジュールでは、複雑で退屈な作業を完了するために、対応するライブラリの依存関係を導入する必要があるということです。次のライブラリを導入する必要があります。
implementation project(path: ':annotation') // 我们定义的注解模块,因为要处理注解,
// 所以必须依赖上
// auto-service 是Google提供的,辅助我们开发注解处理器
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
// 这个是一个代码生成框架,利用它可以优雅的生成我们想要的代码
implementation 'com.squareup:javapoet:1.10.0'
注意:引入com.google.auto.service:auto-service:1.0-rc7这个依赖时使用的是annotationProcessor ,需要注意,不然不会报错,但是也无法生成我们想要的代码
構成が完了したら、開発を開始できます。前のアノテーションを処理するコードは次のとおりです。
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class DIProcessor extends AbstractProcessor {
private Elements elementUtils;
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(DIActivity.class.getCanonicalName());
}
@Override
public boolean process(Set<? extends TypeElement> set,
RoundEnvironment roundEnvironment)
{
Set<? extends Element> elements =
roundEnvironment.getElementsAnnotatedWith(DIActivity.class);
for (Element element:elements){
// 判断是否为class
TypeElement typeElement = (TypeElement) element;
List<? extends Element> allMembers =
elementUtils.getAllMembers(typeElement);
MethodSpec.Builder bindViewMethodSpecBuilder =
MethodSpec.methodBuilder("bindView")
.addModifiers(Modifier.PUBLIC,Modifier.STATIC)
.returns(TypeName.VOID)
.addParameter(ClassName.get(typeElement.asType()),"activity");
for(Element item :allMembers){
DIView diView = item.getAnnotation(DIView.class);
if (diView == null) {
continue;
}
bindViewMethodSpecBuilder.addStatement(String.format("activity.%s = (%s)"
+"activity.findViewById(%s)",item.getSimpleName(),
ClassName.get(item.asType()).toString(),diView.value()+""));
}
TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
.superclass(TypeName.get(typeElement.asType()))
.addModifiers(Modifier.PUBLIC,Modifier.FINAL)
.addMethod(bindViewMethodSpecBuilder.build())
.build();
JavaFile javaFile =
JavaFile.builder(getPackageName(typeElement),typeSpec).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return false;
}
private String getPackageName(TypeElement typeElement) {
return elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
elementUtils = processingEnv.getElementUtils();
super.init(processingEnv);
}
}
上記のコードは非常にシンプルで、大まかに言うとツールクラスを使ってアノテーション情報を取得し、必要なコードを生成し、最後にファイルに書き込むというものです。対応する関数を使用したい場合は、プログラム内で直接呼び出すことができます。上記のコードには次のコード ブロックが含まれています。
TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
.superclass(TypeName.get(typeElement.asType()))
.addModifiers(Modifier.PUBLIC,Modifier.FINAL)
.addMethod(bindViewMethodSpecBuilder.build())
.build();
com.squareup:javapoet:1.10.0
オブジェクト指向の方法でコードを生成するのはフレームワークの機能ですが、そのようなフレームワークがない場合は、文字列を使用して生成したいクラスを分割する必要があります。たとえば、EventBus の注釈プロセッサは文字のスプライシングを使用します。
3.3 注釈の使用
次のステップは、アノテーションを使用することです。アノテーションを使用する前に、関連する依存関係を導入する必要があります。まず、カスタム アノテーションを使用する必要があるため、アノテーション モジュールの依存関係を導入する必要があります。次に、アノテーションに依存する必要があります。以下に示すように、プロセッサ モジュール。
implementation project(':annotation')
annotationProcessor project(':annotation-processor')
注意:引用注解处理器模块时要用:annotationProcessor project(':annotation-processor'),是annotationProcessor 不是Implementation,如果使用错误会导致无法生成我们想要的目标代码
次のステップは、注釈を使用することです。
@DIActivity
public class MainActivity extends AppCompatActivity {
@DIView(value = R.id.tv_text)
TextView textView;
@DIView(value = R.id.tv_text_1)
TextView textView1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// bindView方法其实就是取代findViewById的替代
DIMainActivity.bindView(this);
textView.setText("hello apt!!!!");
textView1.setText("hello apt again!!!!");
textView1.setTextSize(15);
}
}
コードをコンパイルすると、アノテーション プロセッサで結合したクラスが下図のパスに生成され、クラスの内容を見ると、フレームワークが何をするのかを実際に理解できます。生成したいクラスが生成されない場合は、読者自身のコードにエラーがないか、依存関係の導入によってエラーが発生するかどうかを確認することをお勧めします。
この時点で、アノテーション プロセッサ APT の内容は完成しましたが、この技術は現在多くのフレームワークで使用されており、プロジェクトのソース コードは比較的単純であるため、読者はまだこの内容を理解する必要があります。必要な場合は、コメント領域にメッセージを残して電子メール アドレスを残し、読者は手動で実装することをお勧めします。