Github地址:Github
其實(shí)Android開發(fā)的時(shí)候得问,每次都要寫什么findViewById之類的挺煩的,所以如果可以直接用注解的方式最好。
先直接上效果:
@ContentView(value = R.layout.activity_main)
public class MainActivity extends BaseActivity {
super.onCreate(savedInstanceState);
Injection.inject(this);
@BindView(value = R.id.button1)
Button button1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@OnClick(value = R.id.button1)
public void button1Click(View view) {
Toast.makeText(MainActivity.this, "button1 clicked", Toast.LENGTH_LONG).show();
}
}
可以看見非常的簡潔,本文用Java的Annontation會(huì)用到反射,影響效率勿璃,顯然不是最好的解決方法,只是起到一個(gè)學(xué)習(xí)的作用而已推汽。
Annontation是Java5引入的补疑,即注解。
用處主要在于:
1歹撒、生成文檔癣丧,比如@param @return
2、起到檢查的作用栈妆,比如@Override
3胁编、依賴配置。
定義注解的時(shí)候使用關(guān)鍵字@interface鳞尔,這個(gè)不是interface嬉橙,而是定義注解類的關(guān)鍵字。注解本質(zhì)是一個(gè)繼承了Annotation的特殊接口寥假,其具體實(shí)現(xiàn)類是Java運(yùn)行時(shí)生成的動(dòng)態(tài)代理類市框。
一、元注解
java.lang.annotation提供了四種元注解糕韧,專門注解其他的注解(在自定義注解的時(shí)候枫振,需要使用到元注解):
1喻圃、@Documented –注解是否將包含在JavaDoc中。
2粪滤、@Retention –什么時(shí)候使用該注解斧拍。
值以及含義如下:
值 | 含義 |
---|---|
RetentionPolicy.SOURCE | 在編譯階段丟棄。這些注解在編譯結(jié)束之后就不再有任何意義杖小,所以它們不會(huì)寫入字節(jié)碼肆汹。 |
RetentionPolicy.CLASS | 在類加載的時(shí)候丟棄。在字節(jié)碼文件的處理中有用予权。 |
RetentionPolicy.RUNTIME | 始終不會(huì)丟棄昂勉,運(yùn)行期也保留該注解,因此可以使用反射機(jī)制讀取該注解的信息扫腺。我們自定義的注解通常使用這種方式岗照。 |
3、@Target – 表示該注解用于什么地方笆环。默認(rèn)值為任何元素攒至。
值以及含義如下:
值 | 含義 |
---|---|
ElementType.CONSTRUCTOR | 用于描述構(gòu)造器。 |
ElementType.FIELD | 成員變量咧织、對(duì)象、屬性(包括enum實(shí)例)籍救。 |
ElementType.LOCAL_VARIABLE | 用于描述局部變量习绢。 |
ElementType.METHOD | 用于描述方法。 |
ElementType.PACKAGE | 用于描述包蝙昙。 |
ElementType.PARAMETER | 用于描述參數(shù)闪萄。 |
ElementType.TYPE | 用于描述類、接口(包括注解類型) 或enum聲明奇颠。 |
4败去、@Inherited - 定義該注釋和子類的關(guān)系
@Inherited 元注解是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類型是被繼承的烈拒。如果一個(gè)使用了@Inherited修飾的annotation類型被用于一個(gè)class圆裕,則這個(gè)annotation將被用于該class的子類。
二荆几、常見的注解
1吓妆、Override
java.lang.Override是一個(gè)標(biāo)記類型注解,它被用作標(biāo)注方法吨铸。能起到檢查的作用行拢,編譯器會(huì)檢查是否覆蓋了父類的方法,如果沒有覆蓋的話會(huì)報(bào)錯(cuò)诞吱。
2舟奠、Deprecated
過時(shí)的方法或者成員竭缝。
3、SuppressWarnings
抑制警告沼瘫。它有一個(gè)類型為String[]的成員抬纸,這個(gè)成員的值為被禁止的警告名。
三晕鹊、自定義注解
介紹了Java的Annontation的一些知識(shí)之后松却,開始自定義我們的注解。首先先說下總體思路:
自定義注解類溅话, 具體的注解解析類里面實(shí)現(xiàn)注解解釋晓锻,通過反射拿到對(duì)應(yīng)的類,然后通過getFields和getMethods遍歷方法進(jìn)行動(dòng)態(tài)注入飞几。
有個(gè)比較重要的方法getAnnotation,這個(gè)是嘗試獲得對(duì)應(yīng)的注釋躁锁。
1、findViewById 注解BindView實(shí)現(xiàn)
使用如下,需要能解析成 Button button1 = (Button)findViewById(R.id.button1);
@BindView(value = R.id.button1)
Button button1;
首先刁标,定義注解。新建BindView類,實(shí)現(xiàn)如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
int value () default 0;
}
解釋一下含義:
(1)Retention指定運(yùn)行時(shí)使用注解狐血。
(2)Target指定注解對(duì)象是成員匈织。
(3)value指定注解參數(shù)缀匕,默認(rèn)值為0乡小。
注解的具體實(shí)現(xiàn)满钟。新建一個(gè)Injection類,然后添加一個(gè)inject方法用以解析注解尊惰。
public class Injection {
public static void inject(Object object) {
bindView(object);
}
}
bindView方法即用來解釋BindView鞋诗,實(shí)現(xiàn)思路為先通過object.getClass()拿到類全庸,然后getFields()拿到類的所有成員啄育,進(jìn)行遍歷酌心,getAnnotation可以拿到注解挑豌,如果有BindView注解,則將value值拿出來并將((Activity)object).findViewById(id))賦值給現(xiàn)在的成員氓英。具體代碼如下:
private static void bindView(Object object) {
Class cls = object.getClass();
Field[] fields = cls.getFields(); //得到所有成員
for(Field field : fields) {
BindView bindView = field.getAnnotation(BindView.class);
if(bindView != null && bindView.value() != 0) {
int id = bindView.value();
try {
field.setAccessible(true);
if(object instanceof Activity) {
field.set(object, ((Activity)object).findViewById(id));
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
同時(shí)需要在MainActivity加載的時(shí)候調(diào)用inject函數(shù)并將this傳入。
Injection.inject(this);
2铝阐、setOnClickListener注解OnClick實(shí)現(xiàn)址貌。
效果如下:
@OnClick(value = R.id.button1)
public void button1Click(View view) {
Toast.makeText(MainActivity.this, "button1 clicked", Toast.LENGTH_LONG).show();
}
首先,還是自定義注解類。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnClick {
int value() default 0;
}
和上面不同的是這個(gè)注解是作用在方法上的,所以Target的值是ElementType.METHOD。
同時(shí)下隧,在方法inject中加入达传。
public class Injection {
public static void inject(Object object) {
bindView(object);
onClick(object);
}
}
onClick方法的實(shí)現(xiàn)即要將注解的方法添加到對(duì)應(yīng)的控件上,因此思路上首先需要先找到所有有這個(gè)注解的方法迫筑。通過object.getClass().getMethods()獲得所有方法宪赶,然后進(jìn)行遍歷如果發(fā)現(xiàn)有OnClick注解的,將注解的value拿出來脯燃,然后通過findViewById(id)拿到View搂妻,然后調(diào)用view.setOnClickListener,在重寫onClick由于需要運(yùn)行現(xiàn)在的方法辕棚,因此用調(diào)用method.invoke(object, view)欲主,第一個(gè)是類,第二個(gè)是參數(shù)逝嚎。
具體代碼如下:
private static void onClick(final Object object) {
Class cls = object.getClass();
Method[] methods = cls.getMethods();
for(final Method method : methods) {
OnClick onClick = method.getAnnotation(OnClick.class);
if (onClick != null && onClick.value() != 0) {
int id = onClick.value();
View view = null;
if (object instanceof Activity) {
view = ((Activity)object).findViewById(id);
if (view != null) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
method.invoke(object, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
}
}
}
}
}
3扁瓢、setContentView的注解ContentView的實(shí)現(xiàn)。
效果如下:
@ContentView(value = R.layout.activity_main)
public class MainActivity extends BaseActivity {
super.onCreate(savedInstanceState);
Injection.inject(this);
}
首先补君,還是定義一個(gè)注解類引几。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContentView {
int value() default 0;
}
作用在類上,因此Target值為ElementType.TYPE挽铁。
同樣的伟桅,加入inject中,注意叽掘,這個(gè)需要是最先被執(zhí)行的楣铁,因?yàn)楹竺嬗衒indViewById,所以頁面必選先加載更扁,所以最先執(zhí)行解釋處理盖腕。
public class Injection {
public static void inject(Object object) {
contentView(object);
bindView(object);
onClick(object);
}
}
contentView實(shí)現(xiàn)其實(shí)就是執(zhí)行了一下setContentView赫冬。實(shí)現(xiàn)如下:
private static void contentView(Object object) {
Class cls = object.getClass();
ContentView contentView = (ContentView)cls.getAnnotation(ContentView.class);
if (object instanceof Activity) {
int id = contentView.value();
if (id != 0) {
((Activity)object).setContentView(id);
}
}
}
4、再稍微進(jìn)一步
每次新建個(gè)Activity都要寫一句Injection.inject(this)其實(shí)挺麻煩的溃列,所以面殖,我們自己新建一個(gè)BaseActivity,在里面執(zhí)行這個(gè)哭廉,然后以后的Activity都繼承自這個(gè)脊僚,也便于管理。
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Injection.inject(this);
}
}
總的來說遵绰,這種通過Java反射機(jī)制的方法自定義注解雖然實(shí)現(xiàn)起來簡單辽幌,但是在Android中反射效率低,對(duì)性能影響比較大椿访,所以只是適合學(xué)習(xí)一種思路乌企,不適合在Android中用來自定義注解。