關(guān)聯(lián)文章:Android自定義注解
新手村
先來(lái)說(shuō)說(shuō)注解處理器(AbstractProcessor)是干嘛的,它主要是用來(lái)處理注解的一些內(nèi)部邏輯桐腌,拿butterknife舉例拄显,我聲明了一個(gè)bindView注解,那肯定是要寫一些邏輯才能找到控件的id對(duì)吧案站,AbstractProcessor就是注解處理的邏輯入口躬审,出于性能考慮,肯定是不能使用反射來(lái)處理找id這個(gè)邏輯的,這時(shí)承边,JavaPoet就派上用場(chǎng)了遭殉,它的作用是根據(jù)特定的規(guī)則生成java代碼文件,這樣博助,我通過(guò)注解來(lái)拿到需要的參數(shù)险污,通過(guò)JavaPoet來(lái)生成模板代碼,對(duì)性能沒(méi)有任何的影響富岳,由于ServiceLoader加載Processor需要手動(dòng)注冊(cè)配置蛔糯,框架AutoService就是用來(lái)自動(dòng)注冊(cè)ServiceLoader的,省去了AbstractProcessor繁瑣的配置。理解了這三者的關(guān)系城瞎,下面開始真正的學(xué)習(xí)吧
副本之JavaPoet的使用
項(xiàng)目地址:https://github.com/square/javapoet
javapoet的api非常的通俗易懂渤闷,我用主頁(yè)的使用示例來(lái)說(shuō)明一下
例如我們要生成一個(gè)這樣的代碼:
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
對(duì)應(yīng)的代碼為:
MethodSpec main = MethodSpec.methodBuilder("main") //方法名
.addModifiers(Modifier.PUBLIC, Modifier.STATIC) //修飾符
.returns(void.class)//返回值
.addParameter(String[].class, "args")//參數(shù)
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")//內(nèi)容
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") //類名
.addModifiers(Modifier.PUBLIC, Modifier.FINAL) //修飾符
.addMethod(main) //方法
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
MethodSpec類是用來(lái)配置方法的疾瓮,一個(gè)方法包括方法名脖镀,修飾符,返回值狼电,參數(shù)蜒灰,內(nèi)容,配置對(duì)應(yīng)的方法已在注釋中標(biāo)出肩碟。
TypeSpec為類的配置强窖,類包括類名,修飾符削祈,方法,字段等
JavaFile用于指定輸出位置翅溺,生成類,我們傳入包名髓抑,和類咙崎,最后通過(guò)writeTo指定輸出到控制臺(tái)。
可以看出吨拍,復(fù)雜的地方就是在MethodSpec的配置褪猛,下面著重介紹MethodSpec的一些常用用法
基本用法
MethodSpec main = MethodSpec.methodBuilder("main")
.addCode(""
+ "int total = 0;\n"
+ "for (int i = 0; i < 10; i++) {\n"
+ " total += i;\n"
+ "}\n")
.build();
效果:
void main() {
int total = 0;
for (int i = 0; i < 10; i++) {
total += i;
}
}
可以看到里面的分號(hào)和換行符混在一起看起來(lái)眼花繚亂,丟失一個(gè)還不會(huì)報(bào)錯(cuò)羹饰,讓人很抓狂伊滋,因此JavaPoet很貼心的準(zhǔn)備了換行符分號(hào)和起始結(jié)束括號(hào)的api:
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("int total = 0") //這段代碼之后會(huì)添加一個(gè)分號(hào)和換行符
.beginControlFlow("for (int i = 0; i < 10; i++)")//這段代碼之后會(huì)添加一個(gè)起始的括號(hào)
.addStatement("total += i")
.endControlFlow()//括號(hào)結(jié)束
.build();
此外還有一個(gè)nextControlFlow
是前后都加括號(hào),通常用于if else
的邏輯判斷中队秩,示例:
MethodSpec main = MethodSpec.methodBuilder("main")
.addStatement("long now = $T.currentTimeMillis()", System.class)
.beginControlFlow("if ($T.currentTimeMillis() < now)", System.class)
.addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!")
.nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class)
.addStatement("$T.out.println($S)", System.class, "Time stood still!")
.nextControlFlow("else")
.addStatement("$T.out.println($S)", System.class, "Ok, time still moving forward")
.endControlFlow()
.build();
輸出:
void main() {
long now = System.currentTimeMillis();
if (System.currentTimeMillis() < now) {
System.out.println("Time travelling, woo hoo!");
} else if (System.currentTimeMillis() == now) {
System.out.println("Time stood still!");
} else {
System.out.println("Ok, time still moving forward");
}
}
可以看到上面有幾個(gè)不明覺(jué)厲的符號(hào)笑旺,我們稱之為占位符,占位符常用的有以下幾種:
- $T 類占位符馍资,用于替換代碼中的類
- $L 姑且叫它變量占位符吧燥撞,用法和String.format中的%s差不多,按照順序依次替換里面的變量值
- $S 字符串占位符,當(dāng)我們需要在代碼中使用字符串時(shí)物舒,用這個(gè)替換
- $N 名稱占位符色洞,比方說(shuō)需要在一個(gè)方法里使用另一個(gè)方法,可以用這個(gè)替換
$L演示示例,后面的變量按照順序?qū)μ?hào)入座:
private MethodSpec computeRange(String name, int from, int to, String op) {
return MethodSpec.methodBuilder(name)
.returns(int.class)
.addStatement("int result = 0")
.beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
.addStatement("result = result $L i", op)
.endControlFlow()
.addStatement("return result")
.build();
}
$N
MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
.addParameter(int.class, "i")
.returns(char.class)
.addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
.build();
MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
.addParameter(int.class, "b")
.returns(String.class)
.addStatement("char[] result = new char[2]")
.addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
.addStatement("result[1] = $N(b & 0xf)", hexDigit)
.addStatement("return new String(result)")
.build();
輸出:
public String byteToHex(int b) {
char[] result = new char[2];
result[0] = hexDigit((b >>> 4) & 0xf);
result[1] = hexDigit(b & 0xf);
return new String(result);
}
public char hexDigit(int i) {
return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}
其余兩個(gè)前面的示例中已經(jīng)使用過(guò)了,T傳入類冠胯,S傳入字符串火诸,注意順序:
addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
類的獲取與使用
java中的源碼類我們?cè)谑褂玫臅r(shí)候都會(huì)自動(dòng)導(dǎo)入,但是我們自定義的類是不會(huì)的荠察,所以我們需要使用ClassName
來(lái)獲取我們想要的類
ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
MethodSpec today = MethodSpec.methodBuilder("tomorrow")
.returns(hoverboard)
.addStatement("return new $T()", hoverboard)
.build();
輸出:
package com.example.helloworld;
import com.mattel.Hoverboard;
public final class HelloWorld {
Hoverboard tomorrow() {
return new Hoverboard();
}
}
傳入包名前半段和后半段的類名就能獲取到我們想要的類了置蜀,但是參數(shù)化類型我們要怎么定義呢,比如List<Hoverboard>
,這時(shí)TypeName
就上場(chǎng)了悉盆,TypeName有多個(gè)子類盯荤,包括上面的ClassName也是它的子類,每個(gè)子類都承擔(dān)著不同的職責(zé):
- ArrayTypeName 用于生成數(shù)組類焕盟,例如Hoverboard []
- ClassName 獲取普通的類,例如Hoverboard
- Parameterized 獲取參數(shù)化類秋秤,例如List<Hoverboard>
- TypeVariableName 獲取類型變量,例如泛型T
- WildcardTypeName 獲取通配符脚翘,例如? extends Hoverboard
它們的用法差不多灼卢,以Parameterized舉例:
ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);
MethodSpec beyond = MethodSpec.methodBuilder("beyond")
.returns(listOfHoverboards)
.addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
.addStatement("result.add(new $T())", hoverboard)
.addStatement("result.add(new $T())", hoverboard)
.addStatement("result.add(new $T())", hoverboard)
.addStatement("return result")
.build();
輸出:
package com.example.helloworld;
import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;
public final class HelloWorld {
List<Hoverboard> beyond() {
List<Hoverboard> result = new ArrayList<>();
result.add(new Hoverboard());
result.add(new Hoverboard());
result.add(new Hoverboard());
return result;
}
}
其他的例如字段(FieldSpec),注解(AnnotationSpec)来农,參數(shù)(ParameterSpec)等api用起來(lái)都大同小異鞋真,由于篇幅有限,javaPoet的介紹就講到這里沃于,如果還有不太明白的地方或有想進(jìn)一步了解的可以參考這個(gè)比較全面的介紹:JavaPoet使用詳解
副本之AutoService的使用
項(xiàng)目地址:https://github.com/google/auto
使用非常的簡(jiǎn)單:
package foo.bar;
import javax.annotation.processing.Processor;
@AutoService(Processor.class)
final class MyProcessor implements Processor {
// …
}
編譯后涩咖,則會(huì)在META-INF文件夾下生成Processor配置信息文件,而當(dāng)外部程序裝配這個(gè)模塊的時(shí)候,
就能通過(guò)該jar包META-INF/services/里的配置文件找到具體的實(shí)現(xiàn)類名繁莹,并裝載實(shí)例化檩互,完成模塊的注入。
副本之AbstractProcessor的使用
AbstractProcessor繼承自Processor蒋困,是一個(gè)抽象處理器盾似,它的作用是在編譯時(shí)掃描注解并處理一些邏輯,例如生成代碼等雪标,一般我們繼承它需要實(shí)現(xiàn)4個(gè)方法:
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
//文件相關(guān)輔助類
private Filer mFiler;
//元素
private Elements elements;
//日志信息
private Messager messager;
/**
* 入口零院,相當(dāng)于java的main入口
*
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
elements = processingEnvironment.getElementUtils();
messager = processingEnvironment.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return true;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> mSet = new LinkedHashSet<>();
mSet.add(BindView.class.getCanonicalName());
return mSet;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
注解類:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
init方法是一個(gè)入口,ProcessingEnvironment類主要提供了一些工具類給我們使用村刨,我們可以在init方法中獲取我們需要的工具類告抄。
getSupportedAnnotationTypes用于獲取我們自定義的注解,寫法可以固定
getSupportedSourceVersion用于獲取java版本嵌牺,寫法可以固定
process方法是我們處理邏輯的核心方法打洼,返回true,代表注解已申明龄糊,并要求Processor后期不用再處理了它們
參數(shù)Set<? extends TypeElement> set是請(qǐng)求處理的類型的集合,RoundEnvironment 是當(dāng)前或之前的請(qǐng)求處理類型的環(huán)境募疮,可以通過(guò)它獲取當(dāng)前需要處理請(qǐng)求的元素炫惩,例如我需要獲取BindView注解的元素的類并獲取其中的內(nèi)容可以這樣寫:
//先拿到所有使用了BindView注解的元素集合
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element:elementsAnnotatedWith){
//從元素中拿到這個(gè)注解實(shí)例
BindView annotation = element.getAnnotation(BindView.class);
//從這個(gè)注解實(shí)例中獲取到注解中包含的值
int value = annotation.value();
}
這樣我們就獲取到了注解中的值,思考下butterknife中bindView注解中的那個(gè)id的獲取,是不是有點(diǎn)豁然開朗了呢阿浓。
我們獲取信息都是基于Element
這個(gè)類展開來(lái)他嚷,所以了解下這個(gè)類很有必要,Element表示一個(gè)程序元素芭毙,比如包筋蓖、類或者方法,主要包括以下幾種方法:
public interface Element extends AnnotatedConstruct {
TypeMirror asType();
ElementKind getKind();
Set<Modifier> getModifiers();
Name getSimpleName();
Element getEnclosingElement();
List<? extends Element> getEnclosedElements();
boolean equals(Object var1);
int hashCode();
List<? extends AnnotationMirror> getAnnotationMirrors();
<A extends Annotation> A getAnnotation(Class<A> var1);
<R, P> R accept(ElementVisitor<R, P> var1, P var2);
}
public interface AnnotatedConstruct {
List<? extends AnnotationMirror> getAnnotationMirrors();
<A extends Annotation> A getAnnotation(Class<A> var1);
<A extends Annotation> A[] getAnnotationsByType(Class<A> var1);
}
- asType
獲取元素的類型信息,包括包名退敦,類名等粘咖,配合javapoet的ClassName可以直接獲取到該TypeName
TypeName typeName = ClassName.get(element.asType());
- getKind 用于判斷是哪種element
- getModifiers 用于獲取元素的關(guān)鍵字public static等
- getEnclosingElement 返回包含該element的父element
- getAnnotation 獲取元素上的注解
- accept是一個(gè)判斷方法,用于判斷如果是某一個(gè)元素就執(zhí)行某一個(gè)方法侈百,用的很少瓮下,不細(xì)講了
可能會(huì)遇到的問(wèn)題
加入AutoService發(fā)現(xiàn)配置都正確,但就是不能生成代碼设哗,原因可能是你的Gradle過(guò)高唱捣,把版本降到4.10.1或以下就可以了两蟀,原因不詳网梢,如果有知道原因的朋友可以在留言區(qū)說(shuō)一下
另外一個(gè)就是你的注解器的lib包需要使用annotationProcessor
來(lái)使用,而不是implementation
文章到這就要到我們的實(shí)戰(zhàn)環(huán)節(jié)了赂毯,下一篇我將帶領(lǐng)大家仿butterknife簡(jiǎn)單實(shí)現(xiàn)一個(gè)findviewbyid的功能战虏,幫助大家更好的運(yùn)用和消化這些知識(shí),加油党涕。關(guān)注我不迷茫烦感,支持我的就點(diǎn)個(gè)贊唄