關(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,編譯器在編譯的時候就會自動檢查你重寫的這個類在父類中到底存不存在,你說你重寫了,但是父類中根本沒有,騙機!

最后,簡潔化代碼,通常是自定義注解,第三方注解庫存在的目的,也是本文的重點所在。關(guān)于第三點的作用,我這里講的會跟其他的博客文章不太一樣,主要原因是本文的標(biāo)題已經(jīng)說明了,"關(guān)于android"、"淺要分析"。關(guān)于java的注解,以及注解的起源,機制等資料,歡迎大家查看文下或其他的資料。自定義注解不難,有興趣的可以自行了解,本文主要說下三方注解,現(xiàn)在帶有注解或?qū)iT注解的三方庫越來越多,像xutils,ButterKnife,AndroidAnnotations,Dagger2等等,這些框架或者說工具所要達到的效果主要就是簡潔化代碼,使一鍋大雜燴變得有可讀性,方便以后或者他人的維護,譬如說,不用注解,你的代碼往往是這樣式的:

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),第二條屬于注解的生命周期,則必須要指出,如果想搞懂注解的這幾個要記?。?strong>敲黑板,這道題前兩年都沒考,今年肯定考,三十分,愛記不記哈),@Retention包含三個生命周期:

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

說到這里需要回顧下注解的作用,上面說了主要有三個作用,生成文檔、編譯時檢查、簡化代碼這三條大致對應(yīng)于這三個生命周期,就是說一些主要用于生成文檔的注解,生命周期注明RetentionPolicy.SOURCE就可以了,依次類推,但是,你們看到我"大致"兩個字加黑沒,早期的android注解框架基本都是在運行期通過反射機制來讀取注解信息,并加以解釋的,比如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”里我們把所有注解過的控件一 一進行綁定,相當(dāng)于什么呢,相當(dāng)于我們封裝了一個類,這個類專門用來處理view的聲明和事件綁定,如果有人用過Afinal這個框架應(yīng)該知道這個。下面簡要看看ButterKnife關(guān)于注解的機制:

/**
 * 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 文件進行編譯時,ButterKnifeProcessor 的 process() 方法被調(diào)用,生成相關(guān)的 ViewBinder 類,用于將 View 或者 Listener 進行綁定。

@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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容