關(guān)聯(lián)文章:Android自定義注解
新手村
先來(lái)說(shuō)說(shuō)注解處理器(AbstractProcessor)是干嘛的,它主要是用來(lái)處理注解的一些內(nèi)部邏輯,拿butterknife舉例,我聲明了一個(gè)bindView注解,那肯定是要寫(xiě)一些邏輯才能找到控件的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)系,下面開(kāi)始真正的學(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") //類(lèi)名
.addModifiers(Modifier.PUBLIC, Modifier.FINAL) //修飾符
.addMethod(main) //方法
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
MethodSpec類(lèi)是用來(lái)配置方法的,一個(gè)方法包括方法名,修飾符,返回值,參數(shù),內(nèi)容,配置對(duì)應(yīng)的方法已在注釋中標(biāo)出。
TypeSpec為類(lèi)的配置,類(lèi)包括類(lèi)名,修飾符,方法,字段等
JavaFile用于指定輸出位置,生成類(lèi),我們傳入包名,和類(lèi),最后通過(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èi)占位符,用于替換代碼中的類(lèi)
- $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傳入類(lèi),S傳入字符串,注意順序:
addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
類(lèi)的獲取與使用
java中的源碼類(lèi)我們?cè)谑褂玫臅r(shí)候都會(huì)自動(dòng)導(dǎo)入,但是我們自定義的類(lèi)是不會(huì)的,所以我們需要使用ClassName
來(lái)獲取我們想要的類(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();
}
}
傳入包名前半段和后半段的類(lèi)名就能獲取到我們想要的類(lèi)了,但是參數(shù)化類(lèi)型我們要怎么定義呢,比如List<Hoverboard>
,這時(shí)TypeName
就上場(chǎng)了,TypeName有多個(gè)子類(lèi),包括上面的ClassName也是它的子類(lèi),每個(gè)子類(lèi)都承擔(dān)著不同的職責(zé):
- ArrayTypeName 用于生成數(shù)組類(lèi),例如Hoverboard []
- ClassName 獲取普通的類(lèi),例如Hoverboard
- Parameterized 獲取參數(shù)化類(lèi),例如List<Hoverboard>
- TypeVariableName 獲取類(lèi)型變量,例如泛型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)類(lèi)名,并裝載實(shí)例化,完成模塊的注入。
副本之AbstractProcessor的使用
AbstractProcessor繼承自Processor,是一個(gè)抽象處理器,它的作用是在編譯時(shí)掃描注解并處理一些邏輯,例如生成代碼等,一般我們繼承它需要實(shí)現(xiàn)4個(gè)方法:
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
//文件相關(guān)輔助類(lèi)
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();
}
}
注解類(lèi):
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
init方法是一個(gè)入口,ProcessingEnvironment類(lèi)主要提供了一些工具類(lèi)給我們使用,我們可以在init方法中獲取我們需要的工具類(lèi)。
getSupportedAnnotationTypes用于獲取我們自定義的注解,寫(xiě)法可以固定
getSupportedSourceVersion用于獲取java版本,寫(xiě)法可以固定
process方法是我們處理邏輯的核心方法,返回true,代表注解已申明,并要求Processor后期不用再處理了它們
參數(shù)Set<? extends TypeElement> set是請(qǐng)求處理的類(lèi)型的集合,RoundEnvironment 是當(dāng)前或之前的請(qǐng)求處理類(lèi)型的環(huán)境,可以通過(guò)它獲取當(dāng)前需要處理請(qǐng)求的元素,例如我需要獲取BindView注解的元素的類(lèi)并獲取其中的內(nèi)容可以這樣寫(xiě):
//先拿到所有使用了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)豁然開(kāi)朗了呢。
我們獲取信息都是基于Element
這個(gè)類(lèi)展開(kāi)來(lái),所以了解下這個(gè)類(lèi)很有必要,Element表示一個(gè)程序元素,比如包、類(lèi)或者方法,主要包括以下幾種方法:
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
獲取元素的類(lèi)型信息,包括包名,類(lèi)名等,配合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è)贊唄