aperçu
Dans de nombreux frameworks open source aujourd'hui, nous pouvons souvent voir l'ombre des processeurs d'annotation dans le code source, tels que l'ARouter d'Ali que nous connaissons, ButterKnif, un artefact alternatif findViewById dans le développement Android, et EventBus, etc., tous utilisent l'annotation traitement de la technologie Device APT, la figure suivante est une capture d'écran partielle du schéma de structure de projet d'ARouter :
Le cercle rouge sur la figure est le module d'implémentation de la technologie de processeur d'annotation ARouter. Un autre avantage de l'utilisation du processeur d'annotations est qu'il peut résoudre le problème de perte causé par la réflexion de certaines fonctions. 注意,这里不是说注解处理器的技术是取代反射的哈
En fait, nous utilisons la réflexion pour obtenir dynamiquement un objet pendant l'exécution du programme, puis opérons la méthode correspondante de cet objet pour compléter ce que l'on veut. fonction à implémenter. Mais ce processus prend du temps. Le processeur d'annotations doit générer le code correspondant à la fonction que nous voulons réaliser au moment de la compilation.Un exemple typique est l'implémentation d'EventBus de la technologie de réflexion à l'application de la technologie du processeur d'annotations. Ensuite, cet article présentera ce qu'est un processeur d'annotation et comment l'utiliser pour améliorer élégamment l'efficacité de notre développement.
1. Qu'est-ce que le processeur d'annotation APT
Annotation Process Tool (Annotation Process Tool), comme son nom l'indique, est un outil de traitement des annotations. Il peut grandement optimiser le code redondant que nous écrivons habituellement. Le plus typique est findViewById, qui est souvent écrit par les développeurs Android. Ce type de code est fondamentalement la même. Oui, l'écriture manuelle est non seulement excessive, mais également sujette aux erreurs. Après optimisation à l'aide de la technologie de processeur d'annotations, seules deux lignes de code sont nécessaires :
@DIView(value = R.id.tv_text)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
Comme dans le code ci-dessus, @DIView(value = R.id.tv_text)
il fait partie du processeur d'annotations. Cette annotation est fournie à l'appelant, puis la valeur du paramètre clé est transmise dans l'annotation personnalisée. Une fois que le processeur d'annotations a obtenu la valeur du paramètre clé, il sera très répétitif les codes sont générés de manière uniforme et finalement intégrés à l'application. Ainsi, lorsque nous utilisons la technologie APT, les annotations personnalisées sont définies pour être valides lors de la compilation. Par exemple, la plage valide de l'annotation personnalisée Route d'ARounter est valide lors de la compilation :@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,这个模块主要是用于做自定义注解的定义,给注解处理器和调用者使用。
然后定义好我们需要使用到的自定义注解
L'annotation DIActivity est utilisée sur l'activité que nous voulons utiliser, et nous l'utilisons pour affecter l'objet créé par le code généré à l'activité que nous utilisons.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {
}
DIView est utilisé sur la vue que nous voulons utiliser. L'appelant n'a besoin d'utiliser cette annotation que pour transmettre la valeur d'ID de la vue afin d'obtenir un objet View
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface DIView {
int value() default 0;
}
3.2 Créer un module de processeur d'annotations
Après avoir créé la classe d'annotation personnalisée, nous présenterons ensuite le module de processeur d'annotations. Le travail principal du framework est terminé dans le processeur d'annotations. Nous créons également un nouveau module de bibliothèque Java pour traiter les annotations que nous définissons. Comme indiqué ci-dessous
Ce à quoi nous devons prêter attention, c'est que dans ce module, nous devons introduire les dépendances de bibliothèque correspondantes pour nous aider à effectuer un travail compliqué et fastidieux. Nous devons introduire les bibliothèques suivantes :
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 ,需要注意,不然不会报错,但是也无法生成我们想要的代码
Une fois la configuration terminée, vous pouvez commencer à développer. Le code de traitement des annotations précédentes est le suivant :
@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);
}
}
Le code ci-dessus est très simple. Cela signifie en gros utiliser la classe d'outils pour obtenir les informations d'annotation, puis générer le code que nous voulons, et enfin l'écrire dans le fichier. Lorsque nous voulons utiliser la fonction correspondante, nous pouvons l'appeler directement dans le programme. Le bloc de code suivant apparaît dans le code ci-dessus :
TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
.superclass(TypeName.get(typeElement.asType()))
.addModifiers(Modifier.PUBLIC,Modifier.FINAL)
.addMethod(bindViewMethodSpecBuilder.build())
.build();
C'est com.squareup:javapoet:1.10.0
ce que fait le framework, générer du code de manière orientée objet. S'il n'y a pas un tel framework, nous devons utiliser des chaînes pour séparer les classes que nous voulons générer. Par exemple, le processeur d'annotations d'EventBus utilise la fusion de caractères :
3.3 Utilisation des annotations
L'étape suivante consiste à utiliser des annotations. Avant de les utiliser, nous devons introduire des dépendances associées. Tout d'abord, nous devons utiliser nos annotations personnalisées, nous devons donc introduire les dépendances du module d'annotation. Deuxièmement, nous devons nous appuyer sur l'annotation module processeur, comme illustré ci-dessous :
implementation project(':annotation')
annotationProcessor project(':annotation-processor')
注意:引用注解处理器模块时要用:annotationProcessor project(':annotation-processor'),是annotationProcessor 不是Implementation,如果使用错误会导致无法生成我们想要的目标代码
L'étape suivante consiste à utiliser des annotations.
@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);
}
}
Lorsque nous compilons le code, les classes que nous avons assemblées dans le processeur d'annotations seront générées dans le chemin de la figure ci-dessous. Lorsque nous voyons le contenu de la classe, nous pouvons réellement comprendre ce que le framework fait pour nous. Si la classe que nous voulons générer n'est pas générée, il est conseillé aux lecteurs de vérifier s'il y a des erreurs dans leur propre code et si l'introduction de dépendances provoque des erreurs.
À ce stade, le contenu du processeur d'annotation APT est terminé. Il est encore nécessaire que les lecteurs comprennent ce contenu, car cette technologie est désormais utilisée dans de nombreux frameworks, et le code source du projet est relativement simple, il n'est donc pas fourni ici Si vous en avez besoin, vous pouvez laisser un message dans la zone de commentaire , en laissant une adresse e-mail, et les lecteurs sont invités à l'implémenter manuellement.