一.APT技術(shù)
APT(Annotation Process Tool),注解處理器。用來在編譯時(shí)掃描和處理注解驶睦,按照一定的規(guī)則,生成相應(yīng)的java文件匿醒。
Android 目前比較流行的Dagger2, ButterKnife, EventBus3都采用了APT技術(shù)场航。
二.APT使用
1. AbstractProcesser類
Java語言自帶的Override等注解,虛擬機(jī)會默認(rèn)處理廉羔。
對于自定義注解溉痢,需要我們自己處理。java提供一個AbstractProcesser.java類憋他,我們需要繼承該類孩饼,實(shí)現(xiàn)自動的注解處理器,來處理自定義注解
public abstract class AbstractProcessor implements Processor {
protected ProcessingEnvironment processingEnv;
private boolean initialized = false;
/**
* 構(gòu)造器
*/
protected AbstractProcessor() {}
/**
* 指定可以被處理器識別的選項(xiàng)
*
* 默認(rèn)實(shí)現(xiàn):
* 如果處理器類使用{@SupportedOptions}注解举瑰,則返回一個不可修改的字符串set集
* 合捣辆,包含注解設(shè)置的屬性值。如果未對類進(jìn)行注解此迅,則返回空集合
*/
public Set<String> getSupportedOptions() {
SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);
if (so == null)
return Collections.emptySet();
else
return arrayToSet(so.value());
}
/**
* 指定此處理器支持的注解類型
*
* 默認(rèn)實(shí)現(xiàn):
* 如果此處理器類使用了{(lán)@SupportedAnnotationTypes}注解汽畴,則返回一個不可修改的
* set字符串集合,包含注解設(shè)置的屬性值耸序。如果未對類進(jìn)行注釋忍些,則返回空集合
*/
public Set<String> getSupportedAnnotationTypes() {
SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
if (sat == null) {
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedAnnotationTypes annotation " +
"found on " + this.getClass().getName() +
", returning an empty set.");
return Collections.emptySet();
}
else
return arrayToSet(sat.value());
}
/**
* 如果處理器類使用{@SupportedSourceVersion}注解,則返回注釋中設(shè)置的版本值坎怪。
* 如果沒有對類進(jìn)行注解罢坝,則返回Java 6.0
*
* 指定此處理器支持的最新Java版本,通常返回SourceVersion.latestSupported()
*/
public SourceVersion getSupportedSourceVersion() {
SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
SourceVersion sv = null;
if (ssv == null) {
sv = SourceVersion.RELEASE_6;
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedSourceVersion annotation " +
"found on " + this.getClass().getName() +
", returning " + sv + ".");
} else
sv = ssv.value();
return sv;
}
/**
* 初始化方法 搅窿,只能初始化一次嘁酿,否則會拋出IllegalStateException異常
*
*一般在這里獲取我們需要的工具類
* @param processingEnv 提供工具類Elements, Types和Filer
*/
public synchronized void init(ProcessingEnvironment processingEnv) {
if (initialized)
throw new IllegalStateException("Cannot call init more than once.");
Objects.requireNonNull(processingEnv, "Tool provided null ProcessingEnvironment");
this.processingEnv = processingEnv;
initialized = true;
}
/**
* 注解處理方法隙券,可以在這里寫掃描、評估和處理注解的代碼闹司,生成Java文件
*/
public abstract boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv);
/**
* Returns an empty iterable of completions.
*/
public Iterable<? extends Completion> getCompletions(Element element,
AnnotationMirror annotation,
ExecutableElement member,
String userText) {
return Collections.emptyList();
}
protected synchronized boolean isInitialized() {
return initialized;
}
private static Set<String> arrayToSet(String[] array) {
assert array != null;
Set<String> set = new HashSet<String>(array.length);
for (String s : array)
set.add(s);
return Collections.unmodifiableSet(set);
}
}
2. 自定義注解
Java注解
這里自定義一個BindView娱仔,應(yīng)用場景類似于ButterKnife框架的findView功能,用來找到View對象
注解定義如下:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
3. 自定義注解處理器
這里需要注意需要創(chuàng)建Java library Module游桩,apply plugin: ‘java-library’
不然找不到AbstractProcessor 類
依賴:
implementation project(':annotation')
implementation 'com.google.auto.service:auto-service:1.0-rc4'
implementation 'com.squareup:javapoet:1.11.1'
- :annotation:自定義注解的Module
-
auto-service依賴庫:
為什么引入auto-service
在使用注解處理器需要先聲明牲迫,步驟:
1、需要在 processors 庫的 main 目錄下新建 resources 資源文件夾借卧;
2盹憎、在 resources文件夾下建立 META-INF/services 目錄文件夾;
3铐刘、在 META-INF/services 目錄文件夾下創(chuàng)建 javax.annotation.processing.Processor 文件陪每;
4、在 javax.annotation.processing.Processor 文件寫入注解處理器的全稱镰吵,包括包路徑奶稠;)
引入auto-service庫,使用@AutoService
注解聲明就可以自動完成APT的聲明步驟捡遍。 - javapoet square依賴庫:提供的生成Java文件開源庫 github地址 中文教程
自定義注解處理器
一般注解處理器邏輯:
1. 遍歷得到源碼中,需要解析的元素列表竹握。
2. 判斷元素是否可見和符合要求画株。
3. 組織數(shù)據(jù)結(jié)構(gòu)得到輸出類參數(shù)。
4. 輸入生成java文件啦辐。
5. 錯誤處理谓传。
自定義APT,需要了解一下Java Element
Processor處理過程中芹关,會掃描全部Java源碼续挟,代碼的每一個部分都是一個特定類型的Element,它們像是XML一層的層級機(jī)構(gòu)侥衬。
自定義BindView注解處理器:BinderProcessor類
@AutoService(Processor.class)
public class BinderProcessor extends AbstractProcessor {
private Elements mElementUtils;
private HashMap<String, BinderClassCreator> mCreatorMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//processingEnvironment.getElementUtils(); 處理Element的工具類诗祸,用于獲取程序的元素,例如包轴总、類直颅、方法。
//processingEnvironment.getTypeUtils(); 處理TypeMirror的工具類怀樟,用于取類信息
//processingEnvironment.getFiler(); 文件工具
//processingEnvironment.getMessager(); 錯誤處理工具
mElementUtils = processingEnv.getElementUtils();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
//大部分class而已getName功偿、getCanonicalNam這兩個方法沒有什么不同的。
//但是對于array或內(nèi)部類等就不一樣了往堡。
//getName返回的是[[Ljava.lang.String之類的表現(xiàn)形式械荷,
//getCanonicalName返回的就是跟我們聲明類似的形式共耍。
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
//因?yàn)榧嫒莸脑颍貏e是針對Android平臺吨瞎,建議使用重載getSupportedAnnotationTypes()方法替代默認(rèn)使用注解實(shí)現(xiàn)
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//掃描整個工程 找出含有BindView注解的元素
Set<? extends Element> elements =
roundEnvironment.getElementsAnnotatedWith(BindView.class);
//遍歷元素
for (Element element : elements) {
//BindView限定了只能屬性使用痹兜,這里強(qiáng)轉(zhuǎn)為VariableElement
VariableElement variableElement = (VariableElement) element;
//返回此元素直接封裝(非嚴(yán)格意義上)的元素。
//類或接口被認(rèn)為用于封裝它直接聲明的字段关拒、方法佃蚜、構(gòu)造方法和成員類型
//這里就是獲取封裝屬性元素的類元素
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
//獲取簡單類名
String fullClassName = classElement.getQualifiedName().toString();
BinderClassCreator creator = mCreatorMap.get(fullClassName);
if (creator == null) {
creator = new BinderClassCreator(mElementUtils.getPackageOf(classElement),
classElement);
mCreatorMap.put(fullClassName, creator);
}
//獲取元素注解
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
//注解值
int id = bindAnnotation.value();
creator.putElement(id, variableElement);
}
for (String key : mCreatorMap.keySet()) {
BinderClassCreator binderClassCreator = mCreatorMap.get(key);
//通過javapoet構(gòu)建生成Java類文件
JavaFile javaFile = JavaFile.builder(binderClassCreator.getPackageName(),
binderClassCreator.generateJavaCode()).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
}
BinderClassCreator類 用于生成Java類代碼,具體代碼如下:
public class BinderClassCreator {
public static final String ParamName = "view";
private TypeElement mTypeElement;
private String mPackageName;
private String mBinderClassName;
private Map<Integer, VariableElement> mVariableElements = new HashMap<>();
/**
* @param packageElement 包元素
* @param classElement 類元素
*/
public BinderClassCreator(PackageElement packageElement, TypeElement classElement) {
this.mTypeElement = classElement;
mPackageName = packageElement.getQualifiedName().toString();
mBinderClassName = classElement.getSimpleName().toString() + "_ViewBinding";
}
public void putElement(int id, VariableElement variableElement) {
mVariableElements.put(id, variableElement);
}
public TypeSpec generateJavaCode() {
return TypeSpec.classBuilder(mBinderClassName)
//public 修飾類
.addModifiers(Modifier.PUBLIC)
//添加類的方法
.addMethod(generateMethod())
//構(gòu)建Java類
.build();
}
private MethodSpec generateMethod() {
//獲取所有注解的類的類名
ClassName className = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
//構(gòu)建方法--方法名
return MethodSpec.methodBuilder("bindView")
//public方法
.addModifiers(Modifier.PUBLIC)
//返回void
.returns(void.class)
//方法傳參(參數(shù)全類名着绊,參數(shù)名)
.addParameter(className, ParamName)
//方法代碼
.addCode(generateMethodCode())
.build();
}
private String generateMethodCode() {
StringBuilder code = new StringBuilder();
for (int id : mVariableElements.keySet()) {
VariableElement variableElement = mVariableElements.get(id);
//使用注解的屬性的名稱
String name = variableElement.getSimpleName().toString();
//使用注解的屬性的類型
String type = variableElement.asType().toString();
//view.name = (type)view.findViewById(id)
String findViewCode = ParamName + "." + name + "=(" + type + ")" + ParamName +
".findViewById(" + id + ");\n";
code.append(findViewCode);
}
return code.toString();
}
public String getPackageName() {
return mPackageName;
}
}
4. APT生成代碼使用
注解處理器完成了谐算,怎么使用呢?需要通過反射來調(diào)用注解處理器生成代碼
通過上面的注解處理器可知道归露,注解生成的類包名和使用注解的類的包名一致洲脂,類名為使用注解的類名__ViewBinding
,方法名為bindView
剧包,傳參為使用注解的類對象恐锦。所以可以如下通過反射調(diào)用
BinderViewTools類
public class BinderViewTools {
public static void init(Activity activity) {
Class clazz = activity.getClass();
try {
Class<?> bindClass = Class.forName(clazz.getName() + "_ViewBinding");
Method bind = bindClass.getMethod("bindView", clazz);
bind.invoke(bindClass.newInstance(),activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. APP Module中使用注解
依賴:
-
gradle>=2.2
使用annotationProcessor替代android-apt依賴注解處理器并進(jìn)行工作
(關(guān)于annotationProcessor替代android-apt:能夠輔助 Android Studio 在項(xiàng)目的對應(yīng)目錄中存放注解處理器在編譯期間生成的文件,且打包時(shí)不會包含處理器代碼)
在Moudle的gradle中配置
annotationProcessor project(':apt_lib')
implementation project(':annotation')
-
gradle<2.2
在project的gradle和Moudle的gradle中分別做如下配置
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
apt project(':apt_lib')
}
在Activity使用注解
MainActivity類
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textView)
TextView mTextView;
@BindView(R.id.imageView)
ImageView mImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BinderViewTools.init(this);
mTextView.setVisibility(View.VISIBLE);
mImageView.setVisibility(View.VISIBLE);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="獲取View成功"
android:visibility="gone"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/textView"
android:layout_centerInParent="true"
android:src="@mipmap/ic_launcher"
android:visibility="gone"/>
</RelativeLayout>
兩個View在XML中默認(rèn)隱藏疆液,使用BindView注解一铅,然后調(diào)用BinderViewTools.init(this);
方法調(diào)用注解處理器生成的代碼來獲取View,最后設(shè)置View顯示
注解處理器生成代碼 MainActivity_ViewBinding類
public class MainActivity_ViewBinding {
public void bindView(MainActivity view) {
view.mTextView=(android.widget.TextView)view.findViewById(2131165295);
view.mImageView=(android.widget.ImageView)view.findViewById(2131165240);
}
}
運(yùn)行效果
可以看到注解處理器正常生成代碼堕油,并且最終通過反射成功調(diào)用潘飘,View對象獲取成功。
參考文章
1.[Android] APT
2.Android注解快速入門和實(shí)用解析