轉載請標明出處:https:////www.cnblogs.com/tangZH/p/12343786.html, html
APT 是Annotation Processing Tool 的簡稱。android
事實上它是javac的一個工具,命令行運行javac後即可以看到:git
接下來咱們就來實現一個apt的實例,相似於ButterKnife中@BindView註解,基本步驟以下:github
一、定義要被處理的註解。app
二、定義註解處理器(生成具體的類)。ide
三、調用處理器生成的代碼工具
對應的,咱們在工程中須要有這幾個模塊:測試
一、app。測試咱們的功能gradle
二、apt-annotation。一個Java library module,放置咱們自定義註解
三、apt-processor。一個Java library module,註解處理器模塊
四、apt-sdk。一個Android library module,經過反射調用apt-processor模塊生成的方法,實現view的綁定。
工程目錄以下:
一、在apt-annotation中自定義註解:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface BindView { int value(); }
二、apt-processor中引入依賴,它須要依賴apt-annotation,同時還須要依賴auto-service第三方庫,後面建立註解處理器的時候須要用到。
apt-processor/build.gradle文件中:
implementation project(':apt-annotation') implementation 'com.google.auto.service:auto-service:1.0-rc2'
三、在pat-processor中建立註解處理器:
處理器須要繼承AbstractProcessor,注意該module是 java module,若是建立的是android module的話那麼就會找不到AbstractProcessor
@AutoService(Processor.class) @SuppressWarnings("unused") public class BindViewProcessor extends AbstractProcessor { private Elements mElementUtils; private Map<String, ClassCreatorFactory> mClassCreatorFactoryMap = new HashMap<>(); @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); //拿到工具類
mElementUtils = processingEnvironment.getElementUtils(); } @Override public Set<String> getSupportedAnnotationTypes() { //這個註解處理器是給哪一個註解用的
HashSet<String> supportType = new LinkedHashSet<>(); supportType.add(BindView.class.getCanonicalName()); return supportType; } @Override public SourceVersion getSupportedSourceVersion() { //返回java版本
return SourceVersion.latestSupported(); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { mClassCreatorFactoryMap.clear(); //獲得全部包含該註解的element集合
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class); for (Element element : elements) { //轉換爲VariableElement,VariableElement爲element的子類
VariableElement variableElement = (VariableElement) element; //能夠獲取類的信息的element,也是element的子類
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement(); //獲取包名加類名
String fullClassName = classElement.getQualifiedName().toString(); //保存到集合中
ClassCreatorFactory factory = mClassCreatorFactoryMap.get(fullClassName); if (factory == null) { factory = new ClassCreatorFactory(mElementUtils, classElement); mClassCreatorFactoryMap.put(fullClassName, factory); } BindView bindViewAnnotation = variableElement.getAnnotation(BindView.class); int id = bindViewAnnotation.value(); factory.putElement(id, variableElement); } //開始建立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(); //寫入java代碼
writer.write(factory.generateJavaCode()); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } } return true; } }
須要注意的是代碼中不能有中文,不然編譯不經過,我這裏爲了方便註釋解釋加上了中文。
ClassCreatorFactory的代碼以下,這個類負責提供須要寫入新的類的代碼:
public class ClassCreatorFactory { private String mBindClassName; private String mPackageName; private TypeElement mTypeElement; private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>(); ClassCreatorFactory(Elements elementUtils, TypeElement classElement) { this.mTypeElement = classElement; //PackageElement是element的子類,能夠拿到包信息
PackageElement packageElement = elementUtils.getPackageOf(mTypeElement); String packageName = packageElement.getQualifiedName().toString(); String className = mTypeElement.getSimpleName().toString(); this.mPackageName = packageName; //生成的類的名稱
this.mBindClassName = className + "_ViewBinding"; } public void putElement(int id, VariableElement element) { mVariableElementMap.put(id, element); } public String generateJavaCode() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("/**\n" + " * Auto Created by apt\n" + "*/\n"); stringBuilder.append("package ").append(mPackageName).append(";\n"); stringBuilder.append('\n'); stringBuilder.append("public class ").append(mBindClassName); stringBuilder.append(" {\n"); generateBindViewMethods(stringBuilder); stringBuilder.append('\n'); stringBuilder.append("}\n"); return stringBuilder.toString(); } private void generateBindViewMethods(StringBuilder stringBuilder) { stringBuilder.append("\tpublic void bindView("); stringBuilder.append(mTypeElement.getQualifiedName()); stringBuilder.append(" owner) {\n"); for (int id : mVariableElementMap.keySet()) { VariableElement variableElement = mVariableElementMap.get(id); String viewName = variableElement.getSimpleName().toString(); String viewType = variableElement.asType().toString(); stringBuilder.append("\t\towner."); stringBuilder.append(viewName); stringBuilder.append(" = "); stringBuilder.append("("); stringBuilder.append(viewType); stringBuilder.append(")(((android.app.Activity)owner).findViewById( "); stringBuilder.append(id); stringBuilder.append("));\n"); } stringBuilder.append(" }\n"); } public String getClassFullName() { return mPackageName + "." + mBindClassName; } public TypeElement getTypeElement() { return mTypeElement; } }
先不談apt-sdk模塊,咱們先來看看生成的代碼是怎麼樣的。
在app的gradle中引入:
implementation project(':apt-annotation') annotationProcessor project(':apt-processor')
特別要注意的是apt-processor模塊的依賴引進要用 annotationProcessor,不然編譯報錯
兩個activity中:
public class MainActivity extends AppCompatActivity { @BindView(R.id.tv) TextView textView; @BindView(R.id.tv_1) TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
public class Main2Activity extends AppCompatActivity { @BindView(R.id.tv_2) TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); } }
rebuild一下即可以看到在這個目錄下有咱們生成的文件了。
gradle高版本出現編譯後沒出現文件的問題,無奈只好下降版本,我使用的版本是gradle 3.1.4 + gralde_wrap gradle-4.4-all.zip
點進入其中一個能夠看到是這樣的代碼:
/** * Auto Created by apt */
package com.example.aptsample; public class MainActivity_ViewBinding { public void bindView(com.example.aptsample.MainActivity owner) { owner.tv = (android.widget.TextView)(((android.app.Activity)owner).findViewById( 2131165360)); owner.textView = (android.widget.TextView)(((android.app.Activity)owner).findViewById( 2131165359)); } }
因此咱們只要調用bindView就可以找到該view了,這也是apt-sdk要作的事情。
四、在apt-sdk中建立類,反射調用生成的類中的方法
public class DataApi { public static void bindView(Activity activity) { Class clazz = activity.getClass(); try { Class<?> bindViewClass = Class.forName(clazz.getName() + "_ViewBinding"); Method method = bindViewClass.getMethod("bindView", activity.getClass()); method.invoke(bindViewClass.newInstance(),activity); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
五、app的gradle中引入apt-sdk,而後代碼調用DataApi的方法
implementation project(':apt-annotation') annotationProcessor project(':apt-processor') implementation project(':apt-sdk')
app的MainActivity中實現
public class MainActivity extends AppCompatActivity { @BindView(R.id.tv) TextView textView; @BindView(R.id.tv_1) TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DataApi.bindView(this); tv.setText("a"); } }
這樣就大功告成了