【Android】Android自定義注解——利用Java的Annotation

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中用來自定義注解。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末成玫,一起剝皮案震驚了整個(gè)濱河市加酵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哭当,老刑警劉巖猪腕,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異钦勘,居然都是意外死亡陋葡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門彻采,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腐缤,“玉大人,你說我怎么就攤上這事肛响×朐粒” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵特笋,是天一觀的道長剃浇。 經(jīng)常有香客問我,道長雹有,這世上最難降的妖魔是什么偿渡? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任臼寄,我火速辦了婚禮霸奕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吉拳。我一直安慰自己质帅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著煤惩,像睡著了一般嫉嘀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上魄揉,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天剪侮,我揣著相機(jī)與錄音,去河邊找鬼洛退。 笑死瓣俯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的兵怯。 我是一名探鬼主播彩匕,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼媒区!你這毒婦竟也來了驼仪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤袜漩,失蹤者是張志新(化名)和其女友劉穎绪爸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宙攻,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毡泻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粘优。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仇味。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖雹顺,靈堂內(nèi)的尸體忽然破棺而出丹墨,到底是詐尸還是另有隱情,我是刑警寧澤嬉愧,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布贩挣,位于F島的核電站,受9級(jí)特大地震影響没酣,放射性物質(zhì)發(fā)生泄漏王财。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一裕便、第九天 我趴在偏房一處隱蔽的房頂上張望绒净。 院中可真熱鬧,春花似錦偿衰、人聲如沸挂疆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缤言。三九已至宝当,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胆萧,已是汗流浹背庆揩。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留跌穗,地道東北人盾鳞。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像瞻离,于是被迫代替她去往敵國和親腾仅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容