一、注解是用來干嘛的湿硝?
- 便于生成文檔薪前。
- 用于編譯時的檢查。
- 用于簡潔化代碼关斜。
首先示括,生成文檔這個最常見,如果你看過一些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í)注解的一篇筆記,如果文中有任何錯誤之處聚磺,請諸位不吝賜教坯台。
引用
- https://www.zhihu.com/question/20679872/answer/65565699
- http://tutorials.jenkov.com/java/annotations.html
- http://wiki.jikexueyuan.com/project/java-reflection/java-at.html
- http://blog.csdn.net/beyond0851/article/details/8520993
- http://www.importnew.com/10294.html
- http://www.reibang.com/p/2ca6d51cd967