??我們在項(xiàng)目中經(jīng)常用到注解侄旬,比如原生自帶的@Override、@NonNull等 盔粹,等三方框架ButterKnife中@BindView、@OnClick等程癌。用的久了舷嗡,就想著去看看它是怎么弄出來的,因自定義注解的使用場景比較少嵌莉,故此次咱先做下簡單了解进萄。
注解的作用:
1.和編譯器一起給你一些提示警告信息。
2.配合一些ide 可以更加方便快捷 安全有效的編寫java代碼。谷歌出的support-annotations這個庫 就是主要干這個的垮斯。
3.和反射一起 提供一些類似于spring 可配置的功能郎仆,方便簡潔。
運(yùn)行時注解與編譯時注解的區(qū)別是什么呢兜蠕?
a)保留階段不同扰肌。運(yùn)行時注解保留到運(yùn)行時,可在運(yùn)行時訪問熊杨。而編譯時注解保留到編譯時曙旭,運(yùn)行時無法訪問。
b)原理不同晶府。運(yùn)行時注解是Java反射機(jī)制桂躏,而編譯時注解通過APT、AbstractProcessor川陆。
c)性能不同剂习。運(yùn)行時注解由于使用Java反射,因此對性能上有影響较沪。編譯時注解對性能沒影響鳞绕。這也是為什么ButterKnife從運(yùn)行時切換到了編譯時的原因。
d)產(chǎn)物不同尸曼。運(yùn)行時注解只需自定義注解處理器即可们何,不會產(chǎn)生其他文件。而編譯時注解通常會產(chǎn)生新的Java源文件控轿。
??下面來試試實(shí)現(xiàn)ButterKnife中的BindView和OnClick冤竹。
先定義下BindView(ViewInject)和OnClick的注解,寫的是運(yùn)行時注解:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Retention 用于聲明該注解生效的生命周期茬射,有三個枚舉值可以選擇<br>
* 1. RetentionPolicy.SOURCE 注釋只保留在源碼上面鹦蠕,編譯成class的時候自動被編譯器抹除
* 2. RetentionPolicy.CLASS 注釋只保留到字節(jié)碼上面,VM加載字節(jié)碼時自動抹除
* 3. RetentionPolicy.RUNTIME 注釋永久保留躲株,可以被VM加載時加載到內(nèi)存中
* 注意:由于我們的目的是想在VM運(yùn)行時對Filed上的該注解進(jìn)行反射操作片部,因此Retention值必須設(shè)置為RUNTIME
* @Target 用于指定該注解可以聲明在哪些成員上面,常見的值有FIELD和Method霜定,
* 由于我們的當(dāng)前注解類是想聲明在Filed上面
* 因此這里設(shè)置為ElementType.FIELD档悠。
* 注意:如果@Target值不設(shè)置,則默認(rèn)可以添加到任何元素上望浩,不推薦這么寫辖所。
* @interface 是聲明注解類的組合關(guān)鍵字。
*/
@Target({java.lang.annotation.ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {//通過@interface 來定義注解
public abstract int value();
}
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({java.lang.annotation.ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick
{
public abstract int[] value();
}
定義好注解后磨德,再寫個的類去處理findViewById和OnClick的操作:
import android.app.Activity;
import android.view.View;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ViewUtilsTest {
public static void inject(final Activity activity) {
/**
* 通過字節(jié)碼獲取activity類中所有的字段缘回,在獲取Field的時候一定要使用
* getDeclaredFields(),
* 因?yàn)橹挥性摲椒ú拍塬@取到任何權(quán)限修飾的Filed吆视,包括私有的。
*/
Class clazz = activity.getClass();
Field[] declaredFields = clazz.getDeclaredFields();
//一個Activity中可能有多個Field酥宴,因此遍歷啦吧。
for (int i = 0; i < declaredFields.length; i++) {
Field field = declaredFields[i];
//設(shè)置為可訪問,暴力反射拙寡,就算是私有的也能訪問到
field.setAccessible(true);
//獲取到字段上面的注解對象
ViewInject annotation = (ViewInject) field.getAnnotation(ViewInject.class);
//一定對annotation是否等于null進(jìn)行判斷授滓,因?yàn)椴⒉皇撬蠪iled上都有我們想要的注解
if (annotation == null) {
continue;
}
//獲取注解中的值
int id = annotation.value();
//獲取控件
View view = activity.findViewById(id);
try {
//將該控件設(shè)置給field對象
field.set(activity, view);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
//獲取所有的方法(私有方法也可以獲取到)
Method[] declaredMethods = clazz.getDeclaredMethods();
for (int i = 0; i < declaredMethods.length; i++) {
final Method method = declaredMethods[i];
//獲取方法上面的注解
OnClick annotation = (OnClick) method.getAnnotation(OnClick.class);
if (annotation == null) {
//如果該方法上沒有注解,循環(huán)下一個
continue;
}
//獲取注解中的數(shù)據(jù)肆糕,因?yàn)榭梢越o多個button綁定點(diǎn)擊事件般堆,因此定義注解類時使用的是int[]作為數(shù)據(jù)類型。
int[] value = annotation.value();
for (int j = 0; j < value.length; j++) {
int id = value[j];
final View button = activity.findViewById(id);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
try {
//反射調(diào)用用戶指定的方法
method.invoke(activity, button);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
}
}
代碼中的意思很容易看懂诚啃,就是把界面activity傳過來淮摔,再去遍歷定義好的字段,取出注入的id始赎,最后仍然是咱們經(jīng)常用的findViewById和橙。點(diǎn)擊監(jiān)聽也是如此。然后极阅,就結(jié)束了胃碾。。就可以用了筋搏,像ButterKnife一樣用就行:
public class TestActivity extends AppCompatActivity {
@ViewInject(R.id.btn_test)
Button button;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test1);
ViewUtilsTest.inject(this);
}
@OnClick(R.id.btn_test)
public void onClick(View v) {
if (v.getId() == R.id.btn_test) {
Toast.makeText(TestActivity.this,"test111111",Toast.LENGTH_SHORT).show();
}
}
}
貌似沒問題了,可以成功的toast出來厕隧。但這只是最簡單的了解奔脐,想再多了解點(diǎn)的,可以看看這個:http://www.reibang.com/p/806e3500fec4