APT的簡介
定義
APT即是Annotation Processing Tool 捶惜,它是一個(gè)javac的一個(gè)工具,中文的意思是編譯時(shí)注解處理器,可以用來在編譯時(shí)掃描和處理注解胞谭。通過APT可以取到注解和被注解對象的相關(guān)信息,在拿到這些信息后我們可以根據(jù)需求來自動(dòng)生成一些代碼男杈,省去了手動(dòng)編寫丈屹。獲取注解及生成代碼都是在代碼編譯的時(shí)候完成的,相比反射在運(yùn)行時(shí)處理注解大大提高了程序性能伶棒。APT的核心就是AbstractProcessor類旺垒。
如何在Android中使用APT呢
在Android工程中使用APT,至少需要兩個(gè)Java Library模塊組成苞冯,在Android中創(chuàng)建Java Library的的步驟是袖牙,首先建一個(gè)Android項(xiàng)目,然后點(diǎn)擊File-> New ->New Module,打開如圖所示舅锄,然后選擇Java Library模塊鞭达。
這兩個(gè)模塊的作用是:
一個(gè)Annotation模塊司忱,這個(gè)用來存在自定義的注解。
一個(gè)Compiler模塊畴蹭,這個(gè)模塊依賴Annotation模塊坦仍。
在項(xiàng)目的App模塊和其它的業(yè)務(wù)模塊都需要依賴Annotation模塊,同時(shí)需要通過annotationProcessor依賴Compiler模塊叨襟。
app模塊的gradle中依賴關(guān)系如下:
implementation project(':annotation')
annotationProcessor project(':factory-compiler')
實(shí)現(xiàn)ButterKnife例子學(xué)習(xí)APT
步驟:
新建一個(gè)Android的app的工程繁扎。
創(chuàng)建一個(gè)Java Library ,定義要被處理的注解(apt-annotation)。
創(chuàng)建一個(gè)Java Library,定義注解處理器生成具體的類糊闽。(apt-processor)
創(chuàng)建一個(gè)Android library module梳玫,通過反射調(diào)用apt-processor模塊生成的方法,實(shí)現(xiàn)View的綁定右犹。
工程目錄如下:
在apt-annotation中定義一個(gè)注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value() default -1;
}
因?yàn)檫@個(gè)注解是在成員變量中使用的提澎,保留的時(shí)間是在.class字節(jié)碼文件,不需要值JVM期間保留念链,這也體現(xiàn)了APT只是在編譯器的編譯時(shí)期完成工作盼忌。
在apt-processor中的gradle文件中加入兩個(gè)依賴。
為了解決android studio升級到3.4.2,gradle升級到5.1.1后,apt不會(huì)執(zhí)行,沒辦法自動(dòng)生成注解文件的問題掂墓,還需要添加 annotationProcessor谦纱。
implementation project(':apt-annotation')
implementation 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
第二個(gè)依賴是AutoService,它是google開發(fā)的一個(gè)庫君编,在使用@AutoService注解時(shí)需要用到跨嘉,它的作用是用來生成META.INF/services/javax.annotation.processing.Processor文件的,在使用注解器的時(shí)候就不需要手動(dòng)添加該文件啦粹。
在apt-processor中創(chuàng)建注解處理器
該Module是Java Library偿荷,不能是Android Library窘游,因?yàn)锳ndroid平臺(tái)的是基于OpenJDK的唠椭,而OpenJDK中是不包含APT的相關(guān)代碼。
BindViewProcessor要引用AutoService的注解忍饰,以及繼承AbstractProcessor.并重寫相應(yīng)的方法:
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
}
接下來對重寫的方法進(jìn)行解析
- public synchronized void init()
這個(gè) 方法是用來初始化處理器的贪嫂,方法中有個(gè)ProcessingEnvironment processingEnvironment類型的參數(shù),它是一個(gè)注解處理工具的集合艾蓝。包含了眾多的工具類力崇,例如Filer可以用來編寫新文件。Meessage可以用來處理錯(cuò)誤信息赢织。Elements是一個(gè)可以處理Element的工具類亮靴。
- 什么是Elments?
在Java語言中,Elenent是一個(gè)接口于置,表示一個(gè)程序的元素茧吊,例如包,類,方法搓侄,變量瞄桨。Element已知的子接口有如下幾種。
PackageElement 表示一個(gè)包程序元素讶踪。
ExecutableElement表示某個(gè)類或者接口的方法芯侥,構(gòu)造方法和初始化程序,包括注釋類型元素乳讥。
TypeElement 表示一個(gè)類或者接口的元素柱查。注意對有關(guān)類型及其成員的信息的訪問。注意云石,枚舉類型是一種類物赶,而注解類型是一種接口。
VariableElement表示一個(gè)字段留晚,enum常量酵紫,方法或者構(gòu)成方法參數(shù),局部變量或者異常參數(shù)错维。
- public boolean process()
這個(gè)是AbstractProcessor中最重要的一個(gè)方法奖地,該方法的返回值是一個(gè)boolean類型,返回值表示注解是否由當(dāng)前Processor處理赋焕,如果返回true,則這些注解由這個(gè)處理器進(jìn)行處理参歹,后續(xù)的其他Processor無需處理它們。如果返回false隆判,則這些注解未在次Processor中處理犬庇,需要后續(xù)的其他Processor處理它們。在這個(gè)方法中,我們需要檢驗(yàn)被注解的對象是否合法,可以編寫處理注解的代碼麸锉,以及自動(dòng)生成需要的java文件等月洛。處理的大部分邏輯都在這個(gè)類中。
- public Set<String> getSupportedAnnotationTypes()
這方法是返回一個(gè)set集合,集合中指定那些注解是需要這個(gè)處理器處理的。
- public SourceVersion getSupportedSourceVersion()
這個(gè)返回是返回當(dāng)前的正在使用的java版本。通常返回SourceVersion.latestSupported()就可以了纽帖。
接下來是編寫B(tài)ingViewProcessor的代碼,注意改文件中不可以含有中文举反,否則編譯不通過懊直。代碼中的中文是為了方便理解加的解析,在運(yùn)行的時(shí)候要?jiǎng)h除火鼻。如果需要中文解析的可以在build.gradle中添加
//中文亂碼問題(錯(cuò)誤:編碼GBK不可映射字符)
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private Elements mElementUtil;
private Map<String,ClassCreatorFactory> mClassCreatorFactoryMap=new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//初始化Element
mElementUtil= processingEnvironment.getElementUtils();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mClassCreatorFactoryMap.clear();
//獲取所有包含了@BingView注解的集合
Set<? extends Element> elements=roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element:elements){
VariableElement variableElement= (VariableElement) element;
TypeElement typeElement= (TypeElement) variableElement.getEnclosingElement();
String fullClassName=typeElement.getQualifiedName().toString();
ClassCreatorFactory classCreatorFactory=mClassCreatorFactoryMap.get(fullClassName);
if (classCreatorFactory==null){
classCreatorFactory=new ClassCreatorFactory( mElementUtil,typeElement);
mClassCreatorFactoryMap.put(fullClassName,classCreatorFactory);
}
BindView bindViewAnnotation=variableElement.getAnnotation(BindView.class);
int id=bindViewAnnotation.value();
classCreatorFactory.putElement(id,variableElement);
}
//開始創(chuàng)建Java類
for (String key:mClassCreatorFactoryMap.keySet()){
ClassCreatorFactory factory=mClassCreatorFactoryMap.get(key);
try {
JavaFileObject fileObject=processingEnv.getFiler().createSourceFile(factory.getClassFullName(),factory.getTypeElement());
Writer writer=fileObject.openWriter();
writer.write(factory.generateJavaCode());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//返回true說明這個(gè)processor要處理這個(gè)注解室囊,后續(xù)的Processor不需
return true;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
//設(shè)置這個(gè)注解器是給哪個(gè)注解類用的瘦陈,這里是給BingView的注解類使用
HashSet<String> supportType=new LinkedHashSet<>();
supportType.add(BindView.class.getCanonicalName());
return supportType;
}
@Override
public SourceVersion getSupportedSourceVersion() {
//返回java的版本
return SourceVersion.latestSupported();
}
}
ClassCreatorFactory的代碼如下,注意該類的代碼不能出現(xiàn)中文的注釋波俄,該類的代碼就是相當(dāng)于在代碼中通過字符串的方式編寫代碼晨逝,需要注意空格等細(xì)節(jié)問題,這個(gè)類很容易出錯(cuò)懦铺,可以利用JavaPoet根據(jù)實(shí)體類生成捉貌。
public class ClassCreatorFactory {
private String mClassName;
private String mPackageName;
private TypeElement typeElement;
private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
public ClassCreatorFactory(Elements elements, TypeElement typeElement) {
//獲取該類
this.typeElement = typeElement;
//獲取包元素
PackageElement packageElement = elements.getPackageOf(typeElement);
mPackageName = packageElement.getQualifiedName().toString();
String temClassName = typeElement.getSimpleName().toString();
//生成類的名稱
mClassName = temClassName + "_ViewBinding";
}
/**
* 添加VariableElement,例如字段,局部變量冬念,參數(shù)等
* @param id
* @param element
*/
public void putElement(int id,VariableElement element){
mVariableElementMap.put(id,element);
}
/**
* 創(chuàng)建java代碼趁窃,可以看出是文本的形式,對字符進(jìn)行拼接即可急前。
* @return
*/
public String generateJavaCode(){
StringBuilder stringBuilder=new StringBuilder();
//注釋
stringBuilder.append("/**\n"+"* 通過APT自動(dòng)創(chuàng)建的類"+"\n*/\n");
//包名
stringBuilder.append("package ").append(mPackageName).append(";\n\n");
stringBuilder.append("public class ").append(mClassName).append("{\n");
//創(chuàng)建方法
generateMethod(stringBuilder);
stringBuilder.append("\n}\n");
return stringBuilder.toString();
}
/**
* 創(chuàng)建方法
* @param stringBuilder
*/
private void generateMethod(StringBuilder stringBuilder) {
stringBuilder.append("\tpublic void bindView(");
stringBuilder.append(typeElement.getQualifiedName());
stringBuilder.append(" value) { \n");
for (int id:mVariableElementMap.keySet()){
VariableElement variableElement=mVariableElementMap.get(id);
String viewName=variableElement.getSimpleName().toString();
String viewType=variableElement.asType().toString();
stringBuilder.append("\t\tvalue.");
stringBuilder.append(viewName);
stringBuilder.append(" = ");
stringBuilder.append("(");
stringBuilder.append(viewType);
//findViewById(id);
stringBuilder.append(")(((android.app.Activity)value).findViewById( ");
stringBuilder.append(id+" ));");
stringBuilder.append("\n}\n");
}
}
public String getClassFullName(){
return mPackageName+"."+mClassName;
}
public TypeElement getTypeElement(){
return typeElement;
}
}
創(chuàng)建apt-sdk
通過反射調(diào)用生成類的方法醒陆。
public class BindViewUtil {
public static void bindView(Activity activity) throws ClassNotFoundException, NoSuchMethodException {
try {
Class cla=activity.getClass();
Class bindViewClass=Class.forName(cla.getName()+"_ViewBinding");
Method bindViewMethod=bindViewClass.getMethod("bindView",cla);
bindViewMethod.setAccessible(true);
bindViewMethod.invoke(bindViewClass.newInstance(),activity);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
在App中調(diào)用
需要加入以下的權(quán)限:
implementation project(":apt-annotation")
//要用 annotationProcessor ,否則編譯不通過
annotationProcessor project(":apt-procrssor")
implementation project(':apt-sdk')
使用該Apt
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textView)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
BindViewUtil.bindView(this);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
textView.setText("你好,注解處理器");
}
}
接下來我們看到生成的類在下面的目錄中
public class MainActivity_ViewBinding{
public void bindView(com.example.hx.apttest.MainActivity value) {
value.textView = (android.widget.TextView)(((android.app.Activity)value).findViewById( 2131165353 ));
}
}
從上面的類中可以看到調(diào)用用findViewById的方法裆针。
參考鏈接:
https://blog.csdn.net/qq_20521573/article/details/82321755
http://77blogs.com/?p=199