注解處理器,顧名思義:就是專門處理注解的處理器.
上一篇 我們對java的注解進行了比較詳細的認識和了解,系統(tǒng)提供的注解是由系統(tǒng)的注解處理器來執(zhí)行處理,而我們自定義的注解系統(tǒng)是沒法幫我們直接處理的,這樣我們自定義的注解就失去了本身存在的意義了,實則不然,我們自定義的注解需要我們自己來處理,比如通過APT技術、反射就可以來處理我們自己的注解。
今天我們從APT技術的基礎來進行了解。
APT ( Annotation Processing Tool),他是javac的一個工具,中文意思為注解處理器。他可以用>來在 編譯時 掃描和處理注解。通過APT我們可以獲取到注解和被注解對象的相關信息,在拿>到這些信息后我們可以根據(jù)需求來自動生成一些代碼,省去了不少手動編寫代碼。
需要強調(diào)的一點:<u>獲取注解和生成一些代碼都是在編譯時完成的。</u>
我們先來看看JDK提供給我們的注解處理器:

手把手教你自定義注解處理器:
背景:Android Studio新建工程 CustomAnnotation
1. 注解Annotation Module :CusAnnotation
<!--Java Library-->
自定義注解 BindView,保留級別是Class ,作用域是 Field , 參數(shù)是viewId,
? 如下代碼:
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int viewId() ;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ViewOnClick {
int[] viewId();
}
2. 注解處理器Annotation Processor Module: CusProcessor
?
2.1 ) 在該module的build.gradle 中添加:
api 'com.squareup:javapoet:1.7.0' //生成java文件 可以去查一下該庫的作用
api project(':CusAnnotation') //應用自定義的注解module
2.2 ) 新建AnnotationProcessor.java 文件
@SupportedAnnotationTypes({"com.leon.annotation.BindView", "com.leon.annotation.ViewOnClick"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AnnotationProcessor extends AbstractProcessor {
private static final String TAG = AnnotationProcessor.class.getSimpleName();
private Filer filer;
private Elements elementUtils;
private HashMap<String, List<Element>> mPackageClassSet = new HashMap<>();//存放解析的注解節(jié)點信息 注解名-包名-類名 作為key
private HashMap<String, TypeSpec> mFilerTypeSpec = new HashMap<>();//存放生成對應文件的type info 包名-類名 作為key
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set != null && set.size() > 0) {
//set中存放的是注解處理器支持并解析的注解類型
for (TypeElement typeElement : set) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(typeElement);
for (Element element : elements) {
//獲取注解所屬的類名
String className = element.getEnclosingElement().getSimpleName().toString();
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
//使用annotationTypeName , packageName ,ClassName 拼接為mapkey ,中間用-連接,方便split來區(qū)分
if (!mPackageClassSet.containsKey(typeElement.toString() + "-" + packageName + "-" + className)) {
List<Element> subelements = new ArrayList<>();
subelements.add(element);
/*
* TODO 相同注解的存在在一起,便于做方法體時使用
*/
mPackageClassSet.put(typeElement.toString() + "-" + packageName + "-" + className, subelements);
} else {
mPackageClassSet.get(typeElement.toString() + "-" + packageName + "-" + className).add(element);
}
}
}
generateCodeByJavaPoet();
}
return false;
}
private void generateCodeByJavaPoet() {
Iterator entries = mPackageClassSet.entrySet().iterator();
while (entries.hasNext()) {
//取出hashmap的元素
Map.Entry<String, ArrayList<Element>> entry = (Map.Entry<String, ArrayList<Element>>) entries.next();
//解析key ,包名和類名
String[] subKeys = entry.getKey().split("-");
ClassName t = ClassName.bestGuess(subKeys[2]);
//用來存放創(chuàng)建的方法,因為如果同時存在多個注解,可以穿記得方法會比較多
List<MethodSpec> methods = new ArrayList<>();
if (subKeys[0].equals(BindView.class.getTypeName())) {
//創(chuàng)建綁定view的方法
MethodSpec.Builder bindMethodBuilder = MethodSpec.methodBuilder("BindViewById")
.addModifiers(Modifier.PUBLIC)
.addParameter(t, "activity");
for (Element element : entry.getValue()) {
//注解上的額值
int resId = element.getAnnotation(BindView.class).viewId();
//添加方法體
bindMethodBuilder.addStatement("activity.$L = activity.findViewById($L)", element.getSimpleName(), resId);
}
methods.add(bindMethodBuilder.build());
}
if (subKeys[0].equals(ViewOnClick.class.getTypeName())) {
//創(chuàng)建綁定點擊事件的方法
MethodSpec.Builder onClickMethodSpecBuilder = MethodSpec.methodBuilder("BindViewOnClick")
.addModifiers(Modifier.PUBLIC)
.addParameter(t, "activity", Modifier.FINAL)
.returns(void.class);
for (Element element : entry.getValue()) {
int[] resId = element.getAnnotation(ViewOnClick.class).viewId();
for (int i = 0; i < resId.length; i++) {
String stateMent = String.format("activity.findViewById(%d).setOnClickListener(new android.view.View.OnClickListener() {" +
"\n @Override" +
"\n public void onClick(android.view.View v) {" +
"\n activity.onClick(%d);" +
"\n }" +
"\n })", resId[i], resId[i]);
onClickMethodSpecBuilder.addStatement(stateMent);
}
}
methods.add(onClickMethodSpecBuilder.build());
}
/*
* TODO 根據(jù)類名和包名進行分組存放,便于在不各自不同的文件中存放對應的代碼
*/
String filerTypeKey = entry.getKey().substring(entry.getKey().indexOf("-") + 1);
if (mFilerTypeSpec.containsKey(filerTypeKey)) {
TypeSpec typeSpec = mFilerTypeSpec.get(filerTypeKey).toBuilder().addMethods(methods).build();
mFilerTypeSpec.put(filerTypeKey, typeSpec);
} else {
TypeSpec typeSpec = TypeSpec.classBuilder(String.format(subKeys[2] + "_customButterKnife"))//設置類名
.addModifiers(Modifier.PUBLIC).build();//添加修飾符
typeSpec = typeSpec.toBuilder().addMethods(methods).build();
mFilerTypeSpec.put(filerTypeKey, typeSpec);
}
if (!entries.hasNext()) {
//通過包名-類名和TypeSpec(類)生成一個java文件
Set<String> keySet = mFilerTypeSpec.keySet();
for (String key : keySet) {
String[] split = key.split("-");
JavaFile build = JavaFile.builder(split[0], mFilerTypeSpec.get(key)).build();
try {
//寫入到filer中
build.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
2.3) 注冊自定義的注解處理器
? a. 在main目錄下面新建resources 文件夾,
? b. 在resources中新建 'META-INF/services'的文件夾,
? c. 新建命名為:javax.annotation.processing.Processor文件,文件內(nèi)容為自定義注解的包名+注解處理器名。如:
com.com.leon.processor.AnnotationProcessor
? 注:
上述每一步的名字都不能錯,包括大小寫
網(wǎng)上說可以使用com.google.auto.service:auto-service:1.0-rc6 ,參考:http://m.itdecent.cn/p/402788e70b39
3. 注解處理器綁定
? 截止目前為止,注解和注解處理器都已經(jīng)完成了,那么怎么樣才可以讓我們得注解處理器工作起來呢。我們新建一個module 來專門完成注解與注解處理器的綁定工作。
?
3.1 ) 引用自定義的注解 build.gradle
api project(':CusAnnotation')
3.2 ) 創(chuàng)建綁定類:CustomButterKnife
public class CustomButterKnife {
public static void bind(Activity activity) {
String activityName = activity.getClass().getName();
String genrateClass = activityName + "_customButterKnife";
try {
//調(diào)用構造器來實現(xiàn)bind
Object inject = Class.forName(genrateClass).getConstructor().newInstance();
/*
* TODO 調(diào)用生成的java類進行onclick事件的注解綁定
*/
if (null != inject) {
Method bindViewByIdMethond = inject.getClass().getDeclaredMethod("BindViewById", activity.getClass());
bindViewByIdMethond.invoke(inject, activity);
}
/*
* TODO 調(diào)用生成的java類進行onclick事件的注解綁定
*/
if (null != inject) {
Method bindViewOnClickMethod = inject.getClass().getDeclaredMethod("BindViewOnClick", activity.getClass());
bindViewOnClickMethod.invoke(inject, activity);
}
} catch (InstantiationException | ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
?
4.測試module
? 這一步就是在app module 中使用我們自己的注解。
public class MainActivity extends AppCompatActivity {
@BindView(viewId = R.id.name)
TextView mName;
@BindView(viewId = R.id.school)
TextView mSchool;
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected void onResume() {
super.onResume();
if (mName != null) {
mName.setText(mName.getText().toString() + "張三");
}
if (mSchool != null) {
mSchool.setText(mSchool.getText().toString() + "學校");
}
}
@ViewOnClick(viewId = {R.id.name, R.id.school})
public void onClick(int viewId) {
if (viewId == R.id.name) {
Toast.makeText(this, mName.getText().toString(), Toast.LENGTH_SHORT).show();
}
if (viewId == R.id.school) {
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
}
}
}
記得要在build.gradle中添加注解和注解處理的引用
implementation 'com.squareup:javapoet:1.7.0' annotationProcessor project(":CusProcessor") api project (':CustomButterKnife')
? 至此,你可以將運行你的程序,自定義的注解處理器可以完成你安排的事情了。
? 筆者剛開始遇到注解無效的情況,那么debug 有無法調(diào)試注解處理器。不要慌,坑我已經(jīng)踩過了,接著往下走。。。。
5. Debug調(diào)試注解處理器
注解處理器不是不可以調(diào)試,
5.1 修改根目錄中的gradle.properties文件
- org.gradle.jvmargs= -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5006
- org.gradle.parallel=true
5.2 修改Edit configuation

? 新建Remote ,可以修改名字為APT ,其他配置和我們gradle.properties一致

點擊OK ,
5.3調(diào)試注解處理器
? 選擇目標設備APT ,在自定義的注解處理器中打斷點,然后 Clean Project , 然后Make Project 即可進入斷點。
5.4調(diào)試注解處理器信息

d. 查看注解處理器生成的java文件

以上測試代碼的demo地址:CustomAnnotation 更新中。。。