注解的介紹
注解介紹
注解是在 Java SE5 引入進(jìn)來(lái)的喷户。
注解又稱為標(biāo)注隙轻,用于為代碼提供元數(shù)據(jù)始绍。 作為元數(shù)據(jù)瞳购,注解不直接影響你的代碼執(zhí)行,但也有一些類型的注解實(shí)際上可以用于這一目的亏推⊙可以作用在類、方法吞杭、變量盏浇、參數(shù)和包等上。 你可以通俗的理解成“標(biāo)簽”篇亭,這個(gè)標(biāo)簽可以標(biāo)記類缠捌、方法、變量译蒂、參數(shù)和包曼月。
注解作用
注解單獨(dú)存在時(shí)是沒(méi)有意義的,需要與注解處理器一起柔昼,才能起作用
- 注解+APT哑芹,用于生成一些Java 文件
- 注解+代碼埋點(diǎn),用戶做日志手機(jī)統(tǒng)計(jì)等
- 注解+反射捕透,用于為View 組件 增加事件監(jiān)聽(tīng)等
Java 元注解
名字 | 描述 |
---|---|
@Retention | 標(biāo)識(shí)這個(gè)注解怎么保存聪姿,是只在代碼中碴萧,還是編入class文件中,或者是在運(yùn)行時(shí)可以通過(guò)反射訪問(wèn) |
@Documented | 標(biāo)記這些注解是否包含在用戶文檔中末购,即包含到 Javadoc 中去 |
@Target | 標(biāo)記這個(gè)注解的作用目標(biāo) |
@Inherited | 標(biāo)記這個(gè)注解是繼承于哪個(gè)注解類 |
@Repeatable | Java 8 開始支持破喻,標(biāo)識(shí)某注解可以在同一個(gè)聲明上使用多次 |
@Retention
表示注解保留時(shí)間長(zhǎng)短∶肆瘢可選的參數(shù)值在枚舉類型 java.lang.annotation.RetentionPolicy
中曹质,取值為:
- RetentionPolicy.SOURCE:注解只在源碼階段保留,在編譯器進(jìn)行編譯時(shí)它將被丟棄忽視擎场,不會(huì)寫入 class 文件羽德;
- RetentionPolicy.CLASS:注解只被保留到編譯進(jìn)行的時(shí)候,會(huì)寫入 class 文件迅办,它并不會(huì)被加載到 JVM 中宅静;
- RetentionPolicy.RUNTIME:注解可以保留到程序運(yùn)行的時(shí)候,它會(huì)被加載進(jìn)入到 JVM 中站欺,所以在程序運(yùn)行時(shí)可以反射獲取到它們姨夹。
@Target
用于指明被修飾的注解最終可以作用的目標(biāo)是誰(shuí),也就是指明镊绪,你的注解到底是用來(lái)修飾方法的匀伏?修飾類的?還是用來(lái)修飾字段屬性的蝴韭。 可能的值在枚舉類 java.lang.annotation.ElementType
中够颠,包括:
ElementType.TYPE:允許被修飾的注解作用在類、接口和枚舉上榄鉴;
ElementType.FIELD:允許作用在屬性字段上履磨;
ElementType.METHOD:允許作用在方法上;
ElementType.PARAMETER:允許作用在方法參數(shù)上庆尘;
ElementType.CONSTRUCTOR:允許作用在構(gòu)造器上剃诅;
ElementType.LOCAL_VARIABLE:允許作用在本地局部變量上;
ElementType.ANNOTATION_TYPE:允許作用在注解上驶忌;
ElementType.PACKAGE:允許作用在包上矛辕。
@Target
注解的參數(shù)也可以接收一個(gè)數(shù)組,表示可以作用在多種目標(biāo)類型上付魔,如:@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})
注解+APT 實(shí)戰(zhàn):簡(jiǎn)單實(shí)現(xiàn)ButterKnife框架
APT是注解處理器聊品,是一種用來(lái)處理注解的工具。JVM會(huì)在編譯期就運(yùn)行APT去掃描處理代碼中的注解几苍。我們?cè)谀玫綄?duì)應(yīng)的注解時(shí)翻屈,可以生成我們需要的模板代碼
在獲取View空間的時(shí)候,我們往往需要通過(guò)findViewById
來(lái)拿到控件的示例妻坝,會(huì)比較繁瑣伸眶,我們可以通過(guò)APT的核心原理惊窖,來(lái)生成findViewById
的模板代碼,這樣就不需要在每個(gè)Activity中都通過(guò)這個(gè)方式去執(zhí)行厘贼。
核心原理:
- 聲明控件變量界酒,并添加
BindView
注解,綁定控件對(duì)應(yīng)的ID - 在apt注解處理器嘴秸,處理
BindView
注解盾计,生成對(duì)應(yīng)的模板代碼Java文件(用到Writer
去生成文件) - 調(diào)用模板代碼,實(shí)現(xiàn)
findViewById
功能
實(shí)現(xiàn)步驟
1. 創(chuàng)建annotation庫(kù)
創(chuàng)建一個(gè)java-library
庫(kù)赁遗,命名為:annotation。這個(gè)庫(kù)用來(lái)存放自定義注解族铆。
在此庫(kù)中創(chuàng)建一個(gè)注解BindView
BindView
代碼如下:
@Retention(RetentionPolicy.CLASS)//編譯時(shí)起效
@Target(ElementType.FIELD)//針對(duì)的是屬性
public @interface BindView {
int value(); //定義輸入?yún)?shù)為整形岩四,例如:@BindView(R.id.xxx)
}
2. 創(chuàng)建annotation_compiler庫(kù)
此庫(kù)是APT的核心處理庫(kù),用來(lái)在編譯時(shí)生成處模板代碼
注意: 這也是一個(gè)
java-library
庫(kù)哥攘,繼承的 AbstractProcessor 在 javax包下才能引入
在此之前剖煌,我們需要在App
模塊中定義一個(gè)IBinder
接口,讓即將生成的模板類繼承此接口
public interface IBinder<T> {
void bind(T target);
}
這個(gè)接口的作用是逝淹,當(dāng)我們通過(guò)反射的方式生成模板類實(shí)例耕姊,直接調(diào)用bind方法來(lái)實(shí)現(xiàn)View綁定
String name = activity.getClass().getName() + "_ViewBinding";//這個(gè)是模板類的類名
try {
Class<?> aClass = Class.forName(name);
IBinder iBinder = (IBinder) aClass.newInstance();//生成模板類實(shí)列
iBinder.bind(activity);//實(shí)現(xiàn)findViewbyId功能
} catch (Exception e) {
e.printStackTrace();
}
注意:記住這個(gè)IBinder所在包目錄,下面生成模板時(shí)用到
創(chuàng)建完成后栅葡,在build.gradle中茉兰,引入如下:
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
dependencies {
compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7' //Google 注解處理器
implementation project(path: ':annotation') //引用剛剛創(chuàng)建的注解
}
定義處理器代碼 BindViewProcessor.java
,這個(gè)處理器的目的是生成如下模板代碼:
package com.example.annotation;
import com.example.annotation.IBinder;
public class MainActivity_ViewBinding implements IBinder<com.example.annotation.MainActivity> {
@Override
public void bind(com.example.annotation.MainActivity target) {
target.mTvText = (android.widget.TextView) target.findViewById(2131231186);
}
}
以上的 IBinder
就是我們?cè)贏pp中創(chuàng)建的欣簇,在com.example.annotation
包下面规脸,我們需要一點(diǎn)點(diǎn)將以上目標(biāo)代碼拼裝在一起
所有代碼處理,都在 process
方法中實(shí)現(xiàn)
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private static final String TAG = BindViewProcessor.class.getSimpleName();
//1.支持的版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//2.能用來(lái)處理哪些注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
//3.定義一個(gè)用來(lái)生成APT目錄下面的文件的對(duì)象
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//filter 用于后續(xù)寫文件
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//打印測(cè)試
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "===>" + set);
//獲取APP中所有用到了BindView注解的對(duì)象
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
// TypeElement -->類
// ExecutableElement --> 方法
// VariableElement--> 屬性
//開始對(duì)elementsAnnotatedWith進(jìn)行分類:我們可能很多activity中都定義有 BindView注解熊咽,用activity作為key莫鸭,List作為注解列表
Map<String, List<VariableElement>> map = new HashMap<>();
for (Element element : elementsAnnotatedWith) {
VariableElement variableElement = (VariableElement) element;
//例如MainActivity
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
Class aClass = variableElement.getEnclosingElement().getClass();
List<VariableElement> variableElements = map.get(activityName);
if (variableElements == null) {
variableElements = new ArrayList<>();
map.put(activityName, variableElements); //Activity跟注解列表是一對(duì)多的關(guān)系
}
variableElements.add(variableElement);
}
//開始遍歷map, 生成每個(gè)activity對(duì)應(yīng)的模板代碼
if (map.size() > 0) {
Writer writer = null;
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String activityName = iterator.next();
//對(duì)應(yīng)Activity下面的注解列表
List<VariableElement> variableElements = map.get(activityName);
//獲取Activity所在包名
TypeElement enclosingElement = (TypeElement) variableElements.get(0).getEnclosingElement();
String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
try {
//文件名:MainActivity_ViewBinding
JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
writer = sourceFile.openWriter();
// package com.example.annotation;
writer.write("package " + packageName + ";\n");
// import com.example.annotation.IBinder;
writer.write("import " + packageName + ".IBinder;\n");
// public class MainActivity_ViewBinding implements IBinder<com.example.annotation.MainActivity> {
writer.write("public class " + activityName + "_ViewBinding implements IBinder<" +
packageName + "." + activityName + ">{\n");
// @Overrid
// public void bind(com.example.annotation.MainActivity target) {
writer.write(" @Override\n" +
" public void bind(" + packageName + "." + activityName + " target){");
// target.mTvText = (android.widget.TextView) target.findViewById(2131231186);
for (VariableElement variableElement : variableElements) { //這里可能有多個(gè)注解的View控件,因此需要循環(huán)遍歷
//得到名字
String variableName = variableElement.getSimpleName().toString();
//得到ID
int id = variableElement.getAnnotation(BindView.class).value();
//得到類型
TypeMirror typeMirror = variableElement.asType();
writer.write("target." + variableName + "=(" + typeMirror + ")target.findViewById(" + id + ");\n");
}
writer.write("\n}}");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
return false;
}
}
創(chuàng)建完成處理器后横殴,執(zhí)行build方法被因,會(huì)生成模板代碼:
3. 創(chuàng)建輔助類,完成一鍵綁定
轉(zhuǎn)到App模塊衫仑,App的build.gradle中做如下依賴:
implementation project(path: ':annotation') //注解
annotationProcessor project(path: ':annotation_compiler') //注解處理器
創(chuàng)建MyButterknife
輔助類梨与,其中IBinder
是我們第2步驟中定義的接口,所有模板類都會(huì)繼承此接口
public class MyButterknife {
public static void bind(Activity activity) {
String name = activity.getClass().getName() + "_ViewBinding";
try {
Class<?> aClass = Class.forName(name);
IBinder iBinder = (IBinder) aClass.newInstance(); //通過(guò)反射惑畴,生成模板類的實(shí)例
iBinder.bind(activity); //調(diào)用這個(gè)方法蛋欣,會(huì)去執(zhí)行對(duì)應(yīng)模板類的bind方法,實(shí)現(xiàn)findViewById的功能
} catch (Exception e) {
e.printStackTrace();
}
}
}
在MainActivity中實(shí)現(xiàn)代碼如下:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tvText)
TextView mTvText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyButterknife.bind(this); //一鍵實(shí)現(xiàn)findViewById功能,
mTvText.setText("Test");
}
}
以上代碼已上傳 GitHub