Android注解基礎(chǔ)用法

Android注解基礎(chǔ)用法.png

注解的介紹

注解介紹

注解是在 Java SE5 引入進(jìn)來(lái)的喷户。

注解又稱為標(biāo)注隙轻,用于為代碼提供元數(shù)據(jù)始绍。 作為元數(shù)據(jù)瞳购,注解不直接影響你的代碼執(zhí)行,但也有一些類型的注解實(shí)際上可以用于這一目的亏推⊙可以作用在類、方法吞杭、變量盏浇、參數(shù)和包等上。 你可以通俗的理解成“標(biāo)簽”篇亭,這個(gè)標(biāo)簽可以標(biāo)記類缠捌、方法、變量译蒂、參數(shù)和包曼月。

注解作用

注解單獨(dú)存在時(shí)是沒(méi)有意義的,需要與注解處理器一起柔昼,才能起作用

  1. 注解+APT哑芹,用于生成一些Java 文件
  2. 注解+代碼埋點(diǎn),用戶做日志手機(jī)統(tǒng)計(jì)等
  3. 注解+反射捕透,用于為View 組件 增加事件監(jiān)聽(tīng)等

Java 元注解

名字 描述
@Retention 標(biāo)識(shí)這個(gè)注解怎么保存聪姿,是只在代碼中碴萧,還是編入class文件中,或者是在運(yùn)行時(shí)可以通過(guò)反射訪問(wèn)
@Documented 標(biāo)記這些注解是否包含在用戶文檔中末购,即包含到 Javadoc 中去
@Target 標(biāo)記這個(gè)注解的作用目標(biāo)
@Inherited 標(biāo)記這個(gè)注解是繼承于哪個(gè)注解類
@Repeatable Java 8 開始支持破喻,標(biāo)識(shí)某注解可以在同一個(gè)聲明上使用多次

@Retention
表示注解保留時(shí)間長(zhǎng)短∶肆瘢可選的參數(shù)值在枚舉類型 java.lang.annotation.RetentionPolicy 中曹质,取值為:

  • RetentionPolicy.SOURCE:注解只在源碼階段保留,在編譯器進(jìn)行編譯時(shí)它將被丟棄忽視擎场,不會(huì)寫入 class 文件羽德;
  • RetentionPolicy.CLASS:注解只被保留到編譯進(jìn)行的時(shí)候,會(huì)寫入 class 文件迅办,它并不會(huì)被加載到 JVM 中宅静;
  • RetentionPolicy.RUNTIME:注解可以保留到程序運(yùn)行的時(shí)候,它會(huì)被加載進(jìn)入到 JVM 中站欺,所以在程序運(yùn)行時(shí)可以反射獲取到它們姨夹。

@Target
用于指明被修飾的注解最終可以作用的目標(biāo)是誰(shuí),也就是指明镊绪,你的注解到底是用來(lái)修飾方法的匀伏?修飾類的?還是用來(lái)修飾字段屬性的蝴韭。 可能的值在枚舉類 java.lang.annotation.ElementType 中够颠,包括:

  • ElementType.TYPE:允許被修飾的注解作用在類、接口和枚舉上榄鉴;

  • ElementType.FIELD:允許作用在屬性字段上履磨;

  • ElementType.METHOD:允許作用在方法上;

  • ElementType.PARAMETER:允許作用在方法參數(shù)上庆尘;

  • ElementType.CONSTRUCTOR:允許作用在構(gòu)造器上剃诅;

  • ElementType.LOCAL_VARIABLE:允許作用在本地局部變量上;

  • ElementType.ANNOTATION_TYPE:允許作用在注解上驶忌;

  • ElementType.PACKAGE:允許作用在包上矛辕。

@Target 注解的參數(shù)也可以接收一個(gè)數(shù)組,表示可以作用在多種目標(biāo)類型上付魔,如: @Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})

注解+APT 實(shí)戰(zhàn):簡(jiǎn)單實(shí)現(xiàn)ButterKnife框架

APT是注解處理器聊品,是一種用來(lái)處理注解的工具。JVM會(huì)在編譯期就運(yùn)行APT去掃描處理代碼中的注解几苍。我們?cè)谀玫綄?duì)應(yīng)的注解時(shí)翻屈,可以生成我們需要的模板代碼

image.png

在獲取View空間的時(shí)候,我們往往需要通過(guò)findViewById來(lái)拿到控件的示例妻坝,會(huì)比較繁瑣伸眶,我們可以通過(guò)APT的核心原理惊窖,來(lái)生成findViewById的模板代碼,這樣就不需要在每個(gè)Activity中都通過(guò)這個(gè)方式去執(zhí)行厘贼。

核心原理:

  1. 聲明控件變量界酒,并添加BindView注解,綁定控件對(duì)應(yīng)的ID
  2. 在apt注解處理器嘴秸,處理BindView注解盾计,生成對(duì)應(yīng)的模板代碼Java文件(用到Writer去生成文件)
  3. 調(diào)用模板代碼,實(shí)現(xiàn)findViewById 功能

實(shí)現(xiàn)步驟

1. 創(chuàng)建annotation庫(kù)

創(chuàng)建一個(gè)java-library庫(kù)赁遗,命名為:annotation。這個(gè)庫(kù)用來(lái)存放自定義注解族铆。

image.png

在此庫(kù)中創(chuàng)建一個(gè)注解BindView

image.png

BindView代碼如下:

@Retention(RetentionPolicy.CLASS)//編譯時(shí)起效
@Target(ElementType.FIELD)//針對(duì)的是屬性
public @interface BindView {
    int value(); //定義輸入?yún)?shù)為整形岩四,例如:@BindView(R.id.xxx)
}

2. 創(chuàng)建annotation_compiler庫(kù)

此庫(kù)是APT的核心處理庫(kù),用來(lái)在編譯時(shí)生成處模板代碼

注意: 這也是一個(gè)java-library庫(kù)哥攘,繼承的 AbstractProcessor 在 javax包下才能引入

在此之前剖煌,我們需要在App 模塊中定義一個(gè)IBinder接口,讓即將生成的模板類繼承此接口

public interface IBinder<T> {
    void bind(T target);
}

這個(gè)接口的作用是逝淹,當(dāng)我們通過(guò)反射的方式生成模板類實(shí)例耕姊,直接調(diào)用bind方法來(lái)實(shí)現(xiàn)View綁定

String name = activity.getClass().getName() + "_ViewBinding";//這個(gè)是模板類的類名
try {
    Class<?> aClass = Class.forName(name);
    IBinder iBinder = (IBinder) aClass.newInstance();//生成模板類實(shí)列
    iBinder.bind(activity);//實(shí)現(xiàn)findViewbyId功能
} catch (Exception e) {
    e.printStackTrace();
}

注意:記住這個(gè)IBinder所在包目錄,下面生成模板時(shí)用到

創(chuàng)建完成后栅葡,在build.gradle中茉兰,引入如下:

plugins {
    id 'java-library'
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
}

dependencies {

    compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7' //Google 注解處理器

    implementation project(path: ':annotation') //引用剛剛創(chuàng)建的注解
}

定義處理器代碼 BindViewProcessor.java ,這個(gè)處理器的目的是生成如下模板代碼

package com.example.annotation;
import com.example.annotation.IBinder;
public class MainActivity_ViewBinding implements IBinder<com.example.annotation.MainActivity> {
    @Override
    public void bind(com.example.annotation.MainActivity target) {
        target.mTvText = (android.widget.TextView) target.findViewById(2131231186);
    }
}

以上的 IBinder就是我們?cè)贏pp中創(chuàng)建的欣簇,在com.example.annotation包下面规脸,我們需要一點(diǎn)點(diǎn)將以上目標(biāo)代碼拼裝在一起

所有代碼處理,都在 process方法中實(shí)現(xiàn)

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

    private static final String TAG = BindViewProcessor.class.getSimpleName();

    //1.支持的版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    //2.能用來(lái)處理哪些注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    //3.定義一個(gè)用來(lái)生成APT目錄下面的文件的對(duì)象
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        //filter 用于后續(xù)寫文件
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //打印測(cè)試
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "===>" + set);
        //獲取APP中所有用到了BindView注解的對(duì)象
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);

        // TypeElement -->類
        // ExecutableElement --> 方法
        // VariableElement--> 屬性
        //開始對(duì)elementsAnnotatedWith進(jìn)行分類:我們可能很多activity中都定義有 BindView注解熊咽,用activity作為key莫鸭,List作為注解列表
        Map<String, List<VariableElement>> map = new HashMap<>();
        for (Element element : elementsAnnotatedWith) {
            VariableElement variableElement = (VariableElement) element;
            //例如MainActivity
            String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
            Class aClass = variableElement.getEnclosingElement().getClass();
            List<VariableElement> variableElements = map.get(activityName);
            if (variableElements == null) {
                variableElements = new ArrayList<>();
                map.put(activityName, variableElements); //Activity跟注解列表是一對(duì)多的關(guān)系
            }
            variableElements.add(variableElement);
        }

        //開始遍歷map, 生成每個(gè)activity對(duì)應(yīng)的模板代碼
        if (map.size() > 0) {
            Writer writer = null;
            Iterator<String> iterator = map.keySet().iterator();
            while (iterator.hasNext()) {
                String activityName = iterator.next();
                //對(duì)應(yīng)Activity下面的注解列表
                List<VariableElement> variableElements = map.get(activityName);
                //獲取Activity所在包名
                TypeElement enclosingElement = (TypeElement) variableElements.get(0).getEnclosingElement();
                String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
                try {
                    //文件名:MainActivity_ViewBinding
                    JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
                    writer = sourceFile.openWriter();
                    //        package com.example.annotation;
                    writer.write("package " + packageName + ";\n");
                    //        import com.example.annotation.IBinder;
                    writer.write("import " + packageName + ".IBinder;\n");
                    //        public class MainActivity_ViewBinding implements IBinder<com.example.annotation.MainActivity> {
                    writer.write("public class " + activityName + "_ViewBinding implements IBinder<" +
                            packageName + "." + activityName + ">{\n");
                    //            @Overrid
                    //            public void bind(com.example.annotation.MainActivity target) {
                    writer.write(" @Override\n" +
                            " public void bind(" + packageName + "." + activityName + " target){");

                    // target.mTvText = (android.widget.TextView) target.findViewById(2131231186);
                    for (VariableElement variableElement : variableElements) { //這里可能有多個(gè)注解的View控件,因此需要循環(huán)遍歷
                        //得到名字
                        String variableName = variableElement.getSimpleName().toString();
                        //得到ID
                        int id = variableElement.getAnnotation(BindView.class).value();
                        //得到類型
                        TypeMirror typeMirror = variableElement.asType();
                        writer.write("target." + variableName + "=(" + typeMirror + ")target.findViewById(" + id + ");\n");
                    }

                    writer.write("\n}}");

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (writer != null) {
                        try {
                            writer.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        return false;
    }
}

創(chuàng)建完成處理器后横殴,執(zhí)行build方法被因,會(huì)生成模板代碼:

image.png

3. 創(chuàng)建輔助類,完成一鍵綁定

轉(zhuǎn)到App模塊衫仑,App的build.gradle中做如下依賴:

implementation project(path: ':annotation') //注解
annotationProcessor  project(path: ':annotation_compiler') //注解處理器

創(chuàng)建MyButterknife輔助類梨与,其中IBinder是我們第2步驟中定義的接口,所有模板類都會(huì)繼承此接口

public class MyButterknife {
    public static void bind(Activity activity) {
        String name = activity.getClass().getName() + "_ViewBinding";
        try {
            Class<?> aClass = Class.forName(name);
            IBinder iBinder = (IBinder) aClass.newInstance(); //通過(guò)反射惑畴,生成模板類的實(shí)例
            iBinder.bind(activity); //調(diào)用這個(gè)方法蛋欣,會(huì)去執(zhí)行對(duì)應(yīng)模板類的bind方法,實(shí)現(xiàn)findViewById的功能
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在MainActivity中實(shí)現(xiàn)代碼如下:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tvText)
    TextView mTvText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyButterknife.bind(this); //一鍵實(shí)現(xiàn)findViewById功能,
        mTvText.setText("Test"); 
    }
}

以上代碼已上傳 GitHub

參考

Android 注解知多少

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末如贷,一起剝皮案震驚了整個(gè)濱河市陷虎,隨后出現(xiàn)的幾起案子到踏,更是在濱河造成了極大的恐慌,老刑警劉巖尚猿,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窝稿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡凿掂,警方通過(guò)查閱死者的電腦和手機(jī)伴榔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)庄萎,“玉大人踪少,你說(shuō)我怎么就攤上這事】诽危” “怎么了援奢?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)忍捡。 經(jīng)常有香客問(wèn)我集漾,道長(zhǎng),這世上最難降的妖魔是什么砸脊? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任具篇,我火速辦了婚禮,結(jié)果婚禮上凌埂,老公的妹妹穿的比我還像新娘驱显。我一直安慰自己,他們只是感情好瞳抓,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布秒紧。 她就那樣靜靜地躺著,像睡著了一般挨下。 火紅的嫁衣襯著肌膚如雪熔恢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天臭笆,我揣著相機(jī)與錄音叙淌,去河邊找鬼。 笑死愁铺,一個(gè)胖子當(dāng)著我的面吹牛鹰霍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茵乱,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼茂洒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了瓶竭?” 一聲冷哼從身側(cè)響起督勺,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤渠羞,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后智哀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體次询,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年瓷叫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屯吊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡摹菠,死狀恐怖盒卸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情次氨,我是刑警寧澤世落,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站糟需,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谷朝。R本人自食惡果不足惜洲押,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望圆凰。 院中可真熱鬧杈帐,春花似錦、人聲如沸专钉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)跃须。三九已至站叼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間菇民,已是汗流浹背尽楔。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留第练,地道東北人阔馋。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像娇掏,于是被迫代替她去往敵國(guó)和親呕寝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

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