關(guān)于Android注解的淺要分析

一、注解是用來干嘛的湿硝?

  • 便于生成文檔薪前。
  • 用于編譯時的檢查。
  • 用于簡潔化代碼关斜。

首先示括,生成文檔這個最常見,如果你看過一些android源碼就會發(fā)現(xiàn)

   /**
     * Same as {@link #startActivity(Intent, Bundle)} with no options
     * specified.
     *
     * @param intent The intent to start.
     *
     * @throws android.content.ActivityNotFoundException
     *
     * @see {@link #startActivity(Intent, Bundle)}
     * @see #startActivityForResult
     */
    @Override
    public void startActivity(Intent intent) {
        this.startActivity(intent, null);
    }

像里面的@link痢畜,@param例诀,@see等等這些,主要為了方便用戶閱讀裁着。

其次,編譯時檢查拱她,例如:

@Override
public String toString() {
    return "This is String Representation of current object.";
}

我們知道@Override代表重寫二驰,那么如果加不加這個注解有什么關(guān)系呢,實際上就程序運行而言秉沼,你加不加一點關(guān)系都沒有桶雀,都不影響運行,但是跟運行結(jié)果有關(guān)唬复。試想一下矗积,如果你重寫父類的這個方法,但是你沒有加上@Override敞咧,你手一滑又把toString寫成tostring棘捣,或者toStirng之類的,也能正常運行休建,but乍恐,運行結(jié)果卻跟預(yù)期大相徑庭,怎么辦测砂,檢查來檢查去茵烈,檢查到懷疑人生都可能難以發(fā)現(xiàn)這個bug,我重寫了啊砌些,怎么沒作用呢呜投?如果你加上@Override,編譯器在編譯的時候就會自動檢查你重寫的這個類在父類中到底存不存在,你說你重寫了仑荐,但是父類中根本沒有雕拼,騙機(jī)!

最后释漆,簡潔化代碼悲没,通常是自定義注解,第三方注解庫存在的目的男图,也是本文的重點所在示姿。關(guān)于第三點的作用,我這里講的會跟其他的博客文章不太一樣逊笆,主要原因是本文的標(biāo)題已經(jīng)說明了栈戳,"關(guān)于android"、"淺要分析"难裆。關(guān)于java的注解子檀,以及注解的起源,機(jī)制等資料乃戈,歡迎大家查看文下或其他的資料褂痰。自定義注解不難,有興趣的可以自行了解症虑,本文主要說下三方注解缩歪,現(xiàn)在帶有注解或?qū)iT注解的三方庫越來越多,像xutils谍憔,ButterKnife匪蝙,AndroidAnnotations,Dagger2等等习贫,這些框架或者說工具所要達(dá)到的效果主要就是簡潔化代碼逛球,使一鍋大雜燴變得有可讀性,方便以后或者他人的維護(hù)苫昌,譬如說颤绕,不用注解,你的代碼往往是這樣式的:

public class MainActivity extends FragmentActivity implements OnClickListener {
    /** 消息界面布局 */
    private View home_main_layout;
    /** 聯(lián)系人界面布局 */
    private View home_nearby_layout;
    /** 設(shè)置界面布局 */
    private View home_choice_layout;
    /** 動態(tài)界面布局 */
    private View home_msg_layout;
    /** 設(shè)置界面布局 */
    private View home_user_layout;
    /** 在Tab布局上顯示消息圖標(biāo)的控件 */
    private ImageView home_main_image;
    /** 在Tab布局上顯示聯(lián)系人圖標(biāo)的控件 */
    private ImageView home_nearby_image;
    /** 在Tab布局上顯示動態(tài)圖標(biāo)的控件 */
    private ImageView home_msg_image;
    /** 在Tab布局上顯示設(shè)置圖標(biāo)的控件 */
    private ImageView home_user_image;
    /** 在Tab布局上顯示消息標(biāo)題的控件 */
    private TextView home_main_text;
    /** 在Tab布局上顯示聯(lián)系人標(biāo)題的控件 */
    private TextView home_nearby_text;
    /** 在Tab布局上顯示動態(tài)標(biāo)題的控件 */
    private TextView home_msg_text;
    /** 在Tab布局上顯示設(shè)置標(biāo)題的控件 */
    private TextView home_user_text;
    /** 在Tab布局上顯示設(shè)置圖標(biāo)的控件 */
    private ImageView home_choice_image;
    /** 在Tab布局上顯示消息標(biāo)題的控件 */
    private TextView home_choice_text;

    /** 初始化控件 */
    private void initViews() {
        home_main_layout = findViewById(R.id.home_main_layout);
        home_nearby_layout = findViewById(R.id.home_nearby_layout);
        home_msg_layout = findViewById(R.id.home_msg_layout);
        home_user_layout = findViewById(R.id.home_user_layout);
        home_choice_layout = findViewById(R.id.home_choice_layout);

        home_main_image = (ImageView) findViewById(R.id.home_main_image);
        home_nearby_image = (ImageView) findViewById(R.id.home_nearby_image);
        home_msg_image = (ImageView) findViewById(R.id.home_msg_image);
        home_user_image = (ImageView) findViewById(R.id.home_user_image);
        home_choice_image = (ImageView) findViewById(R.id.home_choice_image);

        home_main_text = (TextView) findViewById(R.id.home_main_text);
        home_nearby_text = (TextView) findViewById(R.id.home_nearby_text);
        home_msg_text = (TextView) findViewById(R.id.home_msg_text);
        home_user_text = (TextView) findViewById(R.id.home_user_text);
        home_choice_text = (TextView) findViewById(R.id.home_choice_text);
    }

看著還行哈祟身,但如果加上注解之后呢屋厘,

@ContentView(R.layout.activity_main)
public class MainActivity extends FragmentActivity implements OnClickListener {
    
    /** 消息界面布局 */
    @ViewInject(R.id.home_main_layout)
    private View home_main_layout;
    
    /** 聯(lián)系人界面布局 */
    @ViewInject(R.id.home_nearby_layout)
    private View home_nearby_layout;
    
    /** 設(shè)置界面布局 */
    @ViewInject(R.id.home_choice_layout)
    private View home_choice_layout;
    
    /** 動態(tài)界面布局 */
    @ViewInject(R.id.home_msg_layout)
    private View home_msg_layout;
    
    /** 設(shè)置界面布局 */
    @ViewInject(R.id.home_user_layout)
    private View home_user_layout;
    
    /** 在Tab布局上顯示消息圖標(biāo)的控件 */
    @ViewInject(R.id.home_main_image)
    private ImageView home_main_image;
    
    /** 在Tab布局上顯示聯(lián)系人圖標(biāo)的控件 */
    @ViewInject(R.id.home_nearby_image)
    private ImageView home_nearby_image;
    
    /** 在Tab布局上顯示動態(tài)圖標(biāo)的控件 */
    @ViewInject(R.id.home_msg_image)
    private ImageView home_msg_image;
    
    /** 在Tab布局上顯示設(shè)置圖標(biāo)的控件 */
    @ViewInject(R.id.home_user_image)
    private ImageView home_user_image;
    
    /** 在Tab布局上顯示消息標(biāo)題的控件 */
    @ViewInject(R.id.home_main_text)
    private TextView home_main_text;
    
    /** 在Tab布局上顯示聯(lián)系人標(biāo)題的控件 */
    @ViewInject(R.id.home_nearby_text)
    private TextView home_nearby_text;
    
    /** 在Tab布局上顯示動態(tài)標(biāo)題的控件 */
    @ViewInject(R.id.home_msg_text)
    private TextView home_msg_text;
    
    /** 在Tab布局上顯示設(shè)置標(biāo)題的控件 */
    @ViewInject(R.id.home_user_text)
    private TextView home_user_text;
    
    /** 在Tab布局上顯示設(shè)置圖標(biāo)的控件 */
    @ViewInject(R.id.home_choice_image)
    private ImageView home_choice_image;
    
    /** 在Tab布局上顯示消息標(biāo)題的控件 */
    @ViewInject(R.id.home_choice_text)
    private TextView home_choice_text;

可以看到,這樣注解后月而,上面initViews() 方法可以去除了汗洒,整體的代碼是不是更清爽了,而且需要綁定的控件越多父款,使用注解的優(yōu)勢越明顯溢谤。

那么瞻凤,使用注解這樣簡化代碼,又加了一個三方庫世杀,會不會影響效率呢阀参,答案是不一定,這個接著來看瞻坝。

二蛛壳、注解的生命

J2SE5.0版本在 java.lang.annotation提供了四種元注解,專門注解其他的注解:

@Documented –注解是否將包含在JavaDoc中
@Retention –什么時候使用該注解
@Target –注解用于什么地方
@Inherited – 是否允許子類繼承該注解

其中第一所刀、三衙荐、四條并不要求一定要實現(xiàn),第二條屬于注解的生命周期浮创,則必須要指出忧吟,如果想搞懂注解的這幾個要記住(敲黑板斩披,這道題前兩年都沒考溜族,今年肯定考,三十分垦沉,愛記不記哈)煌抒,@Retention包含三個生命周期:

  • RetentionPolicy.SOURCE – 在編譯階段丟棄。這些注解在編譯結(jié)束之后就不再有任何意義厕倍,所以它們不會寫入字節(jié)碼寡壮。@Override, @SuppressWarnings都屬于這類注解。
  • RetentionPolicy.CLASS – 在類加載的時候丟棄绑青。在字節(jié)碼文件的處理中有用。注解默認(rèn)使用這種方式屋群。
  • RetentionPolicy.RUNTIME – 始終不會丟棄闸婴,運行期也保留該注解,因此可以使用反射機(jī)制讀取該注解的信息芍躏。我們自定義的注解通常使用這種方式邪乍。

說到這里需要回顧下注解的作用,上面說了主要有三個作用对竣,生成文檔庇楞、編譯時檢查、簡化代碼這三條大致對應(yīng)于這三個生命周期否纬,就是說一些主要用于生成文檔的注解吕晌,生命周期注明RetentionPolicy.SOURCE就可以了,依次類推临燃,但是睛驳,你們看到我"大致"兩個字加黑沒烙心,早期的android注解框架基本都是在運行期通過反射機(jī)制來讀取注解信息,并加以解釋的乏沸,比如Xutils淫茵,它的contentview的注解是這樣的:

package org.xutils.view.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    int value();
}

當(dāng)然了,不可能光是這樣蹬跃,你聲明個注解匙瘪,程序就能搞懂你這是綁定contentView,它還需要個解釋器來解釋蝶缀,你這個注解到底干了啥丹喻,這一點跟接口是一樣的,你不能就寫個接口放那不去實現(xiàn)扼劈,也沒有用驻啤。Xutils關(guān)于這部分的解釋器是這么寫的:

@Override
    public void inject(Activity activity) {
        //獲取Activity的ContentView的注解
        Class<?> handlerType = activity.getClass();
        try {
            ContentView contentView = findContentView(handlerType);
            if (contentView != null) {
                int viewId = contentView.value();
                if (viewId > 0) {
                    Method setContentViewMethod = handlerType.getMethod("setContentView", int.class);
                    setContentViewMethod.invoke(activity, viewId);
                }
            }
        } catch (Throwable ex) {
            LogUtil.e(ex.getMessage(), ex);
        }

        injectObject(activity, handlerType, new ViewFinder(activity));
    }

主要還是利用反射的方式,調(diào)用setContentView()這個方法荐吵。這么著是沒有問題的骑冗,但是寫代碼就唯恐多,需要綁定的控件多了的話在這里就會影響一定的效率先煎,本來使用android原生的findViewById方法贼涩,這些控件資源在編譯期就能確定了,運行時可以直接使用薯蝎,但是通過這種注解遥倦,控件要在運行時才會被綁定,而且每次運行這個頁面都要走一遍反射占锯。所以袒哥,有沒有一種既可以簡化代碼,又不會影響效率的注解呢消略?人們做事總是又想快又想省堡称,這在程序界尤其嚴(yán)重,對于這樣的問題艺演,我們這些小白可能就束手無策了却紧,但是對大牛就是小菜一碟了。

所以上面加黑了“大致”兩個字胎撤,大致就是一般對應(yīng)晓殊,還有不對應(yīng)的,比如說現(xiàn)在伤提,像一般比較專門的注解框架巫俺,如ButterKnife,Dagger2等就采用了動態(tài)生成代碼這樣一種方式來解決這個問題肿男,注解時把@Retention(RetentionPolicy.RUNTIME)改成@Retention(RetentionPolicy.CLASS)识藤,把注解的生命周期改到編譯期砚著,在編譯時動態(tài)生成一個類,我們簡稱“類A”痴昧,在“類A”里我們把所有注解過的控件一 一進(jìn)行綁定稽穆,相當(dāng)于什么呢,相當(dāng)于我們封裝了一個類赶撰,這個類專門用來處理view的聲明和事件綁定舌镶,如果有人用過Afinal這個框架應(yīng)該知道這個。下面簡要看看ButterKnife關(guān)于注解的機(jī)制:

/**
 * Bind a field to the view for the specified ID. The view will automatically be cast to the field
 * type.
 * <pre><code>
 * {@literal @}BindView(R.id.title) TextView title;
 * </code></pre>
 */
@Retention(CLASS) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

最重要看什么豪娜,看周期餐胀,看@Retention,接著有篇文章講的已經(jīng)很到位了瘤载,比如小明同學(xué)分析的

ButterKnife 中所有的注解都使用 Retention 為 CLASS 保留否灾。所以在 ButterKnife 中,有個很重要的 ButterKnifeProcessor鸣奔。當(dāng) java 文件進(jìn)行編譯時墨技,ButterKnifeProcessor 的 process() 方法被調(diào)用,生成相關(guān)的 ViewBinder 類挎狸,用于將 View 或者 Listener 進(jìn)行綁定扣汪。

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

      JavaFile javaFile = binding.brewJava(sdk);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
  }

具體可以查看他的文章參看更多。

總之锨匆,通過編譯期生成代碼而非運行期反射的方式崭别,確保了運行時的效率,又能保持代碼的簡潔可讀性恐锣,這是注解發(fā)展到現(xiàn)在被越來越多人喜愛的重要原因茅主。

這篇文章的主旨在于淺要的解釋注解在android中的作用,以及很多注解庫之所以日益活躍的原因土榴。寫作過程中參閱了諸多大牛的文章诀姚,一 一列在文末,如果大家參閱本文后依然對注解不甚明了鞭衩,歡迎繼續(xù)參考以下引文学搜。文筆簡陋娃善,知識淺薄论衍,這篇文章僅作為學(xué)習(xí)注解的一篇筆記,如果文中有任何錯誤之處聚磺,請諸位不吝賜教坯台。


引用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瘫寝,隨后出現(xiàn)的幾起案子蜒蕾,更是在濱河造成了極大的恐慌稠炬,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咪啡,死亡現(xiàn)場離奇詭異首启,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)撤摸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門毅桃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人准夷,你說我怎么就攤上這事钥飞。” “怎么了衫嵌?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵读宙,是天一觀的道長。 經(jīng)常有香客問我楔绞,道長谓罗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任拯钻,我火速辦了婚禮躁倒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘耻讽。我一直安慰自己察纯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布针肥。 她就那樣靜靜地躺著饼记,像睡著了一般。 火紅的嫁衣襯著肌膚如雪慰枕。 梳的紋絲不亂的頭發(fā)上具则,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天,我揣著相機(jī)與錄音具帮,去河邊找鬼博肋。 笑死,一個胖子當(dāng)著我的面吹牛蜂厅,可吹牛的內(nèi)容都是我干的匪凡。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼掘猿,長吁一口氣:“原來是場噩夢啊……” “哼病游!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起稠通,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤衬衬,失蹤者是張志新(化名)和其女友劉穎买猖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滋尉,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡玉控,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了狮惜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奸远。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖讽挟,靈堂內(nèi)的尸體忽然破棺而出懒叛,到底是詐尸還是另有隱情,我是刑警寧澤耽梅,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布薛窥,位于F島的核電站,受9級特大地震影響眼姐,放射性物質(zhì)發(fā)生泄漏诅迷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一众旗、第九天 我趴在偏房一處隱蔽的房頂上張望罢杉。 院中可真熱鬧,春花似錦贡歧、人聲如沸滩租。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽律想。三九已至,卻和暖如春绍弟,著一層夾襖步出監(jiān)牢的瞬間技即,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工樟遣, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留而叼,地道東北人。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓豹悬,卻偏偏與公主長得像葵陵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子屿衅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

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

  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一種元程序中的元素關(guān)聯(lián)任何信息和...
    九尾喵的薛定諤閱讀 3,179評論 0 2
  • 前面寫了Android 開發(fā):由模塊化到組件化(一),很多小伙伴來問怎么沒有Demo啊?之所以沒有立刻放demo的...
    涅槃1992閱讀 8,040評論 4 37
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,332評論 25 707
  • 竹林裸地埃难,尖塔出頭莹弊,丈夫無緣由涤久。 云隱電閃涡尘,雨融寒山,休把前途走响迂。 就兒女情長事考抄,不談往日,來日有蔗彤。 何將酒與空腹...
    魚蓋閱讀 158評論 2 2
  • 我在想7/28肯定是一個大吉大利的日子川梅,不然不會那么湊巧三個客戶把大節(jié)點定在這一天。萬科28號開新聞發(fā)布會然遏,保...
    伍瑤瑤閱讀 420評論 0 0