Android注解AndroidAnnotation的使用及實現(xiàn)流程分析

Hello,大家好,老衲第一次在簡書上發(fā)文章仲器,請大家多多支持煤率。今天給大家?guī)淼氖茿ndroid開發(fā)中常用的AndroidAnnotation(以下簡稱AA)框架的使用及其內(nèi)部的實現(xiàn)流程。

AA在Android開發(fā)者中使用非常廣泛乏冀。他減少了無用代碼的編寫蝶糯。提高了開發(fā)者的效率。讓開發(fā)者將更多的時間放到真正需要關(guān)注的地方辆沦。首先說明下AA的使用方法 昼捍, 這里以AndroidStudio為例

如何在AndroidStudio中使用AA注解框架

首先說明下需要修改的文件
Paste_Image.png
1.在工程的根Build.gradle文件中需要添加如下代碼
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
Paste_Image.png
2.在app目錄下的Build.gradle文件中需要添加如下四段代碼
//1.
apply plugin: 'android-apt'
//2.
def AAVersion = '3.3.2'
//3.
apt "org.androidannotations:androidannotations:$AAVersion"
compile "org.androidannotations:androidannotations-api:$AAVersion"
//4.
apt {
arguments {
    androidManifestFile variant.outputs[0].processResources.manifestFile
    resourcePackageName 'com.example.annotation.annotationtest'
}

}
添加位置如下

Paste_Image.png
3.修改AndroidManifest.xml文件
修改規(guī)則:需要將加入@EActivity,@EService等注解的類的文件名后加下劃線肢扯,如下圖妒茬,因為通過AA來生成新的類名
是在原有的基礎(chǔ)上加上了下劃線,所以我們聲明的時候也需要加上下劃線蔚晨,否則提示找不到文件乍钻。
Paste_Image.png

常用的注解

以下是我直接從官網(wǎng)去扒的代碼

@NoTitle    //沒有標題欄
@Fullscreen   //全屏
@EActivity(R.layout.bookmarks)  //布局文件
public class BookmarksToClipboardActivity extends Activity {
 
 @ViewById(R.id.booklist)//綁定View
 ListView bookmarkList;

 @App 綁定application
 BookmarkApplication application;
 
 @RestService
 BookmarkClient restClient;
 
 @AnimationRes //綁定動畫
 Animation fadeIn;
 
 @SystemService  //綁定系統(tǒng)服務(wù)
 ClipboardManager clipboardManager;
 
 @AfterViews
 void initBookmarkList() {
     adapter = new BookmarkAdapter(this);
     bookmarkList.setAdapter(adapter);
 }
 
 @Click({R.id.updateBookmarksButton1,  R.id.updateBookmarksButton2
    })//綁定OnClick事件
 void updateBookmarksClicked() {
     searchAsync(search.getText().toString(), application.getUserId());
 }
 
 @Background//子線程執(zhí)行
 void searchAsync(String searchString, String userId) {
     Bookmarks bookmarks = restClient.getBookmarks(searchString,     userId);
     updateBookmarks(bookmarks);
 }
 
 @UiThread//主線程執(zhí)行
 void updateBookmarks(Bookmarks bookmarks) {
 adapter.updateBookmarks(bookmarks);
 bookmarkList.startAnimation(fadeIn);
 }
 
 @ItemClick //ItemClick監(jiān)聽
 void bookmarkListItemClicked(Bookmark selectedBookmark) {
 clipboardManager.setText(selectedBookmark.getUrl());
 }

@EBean //聲明一個普通的java類(不能是Android中的組件)并且只有一個無參構(gòu)造方法或者帶context的構(gòu)造方法(在AA version2.7)

@EProvider //contentProvider

@EReceiver //BroadcastReceiver

@EIntentService //IntentService

@EService //Service

@EView //View的注解,需注意:使用的時候是 ClassName_

@EViewGroup //ViewGroup的注解

@AfterExtras//activity之間的參數(shù)傳遞完成后調(diào)用

@AfterInject //依賴注入完成后執(zhí)行的方法

@AfterViews //View綁定后執(zhí)行的方法

@Extras //參數(shù)傳遞調(diào)用

  balabala....實在太多太全面了铭腕,建議大家去github上自己去看...— —银择!
   https://github.com/excilys/androidannotations/wiki/AvailableAnnotations
}

AA實現(xiàn)的流程。

AA在使用時累舷,無需增加其他的額外代碼浩考。。只增加必要的注解被盈。因此它的邏輯全部都在AndroidAnnotationProcessor的process方法中析孽。

1.注解處理的初始化操作:
  AndroidAnnotationProcessor.java

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
      super.init(processingEnv);
      androidAnnotationsEnv = new InternalAndroidAnnotationsEnvironment(processingEnv);

      //定義新生成的java文件的后綴,默認的是在原類名的基礎(chǔ)上添加下劃線“_”
      ModelConstants.init(androidAnnotationsEnv);
      //日志處理
      LoggerContext loggerContext = LoggerContext.getInstance();
      loggerContext.setEnvironment(androidAnnotationsEnv);
      try {
        //CorePlugin中包含了所有的AA能夠處理的注解類的處理邏輯只怎,XXXHandler 
        //比如EActivityHandler
        AndroidAnnotationsPlugin corePlugin = new CorePlugin();

        ...

        List<AndroidAnnotationsPlugin> plugins = loadPlugins();
        plugins.add(0, corePlugin);
        //添加能夠處理的注解到Env(可以理解為上下文)
        androidAnnotationsEnv.setPlugins(plugins);
        } catch (Exception e) {
            LOGGER.error("Can't load plugins", e);
        }
}
2.接下來就是正式的處理流程:
 @Override
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    ...

    Set<? extends Element> rootElements = roundEnv.getRootElements();
    ...

    try {
        checkApiAndProcessorVersions();
        //the point
        processThrowing(annotations, roundEnv);

    } catch (Exception e) {
        ...
    }
    ...
    return true;
}
2.1 processThrowing
private void processThrowing(Set<? extends TypeElement> annotations, RoundEnvironment 
    roundEnv) throws Exception {
    ...

    //提取自身及父類注解
    AnnotationElementsHolder extractedModel = extractAnnotations(annotations, roundEnv);
    //注解的校驗(是否合法)
    AnnotationElementsHolder validatingHolder = extractedModel.validatingHolder();
    //注解環(huán)境中放入合法的注解
    androidAnnotationsEnv.setValidatedElements(validatingHolder);
    try {
        //解析AndroidAnnotationManifest文件
        AndroidManifest androidManifest = extractAndroidManifest();
        //創(chuàng)建R文件對象
        IRClass rClass = findRClasses(androidManifest);
        //將R文件和AndroidManifest放到Context中去
        androidAnnotationsEnv.setAndroidEnvironment(rClass, androidManifest);

    } catch (Exception e) {
        return;
    }
    //注解的校驗
    //這里不會進行所有情況的校驗绿淋,而是假設(shè)父類已經(jīng)校驗過了。
    AnnotationElements validatedModel = validateAnnotations(extractedModel, validatingHolder);
    //注解的處理
    //processResult中包含了某一個類創(chuàng)建所需要的全部信息尝盼,名稱吞滞,屬性和方法,然后我們根據(jù)信息生成.java
    ModelProcessor.ProcessResult processResult = processAnnotations(validatedModel);

    generateSources(processResult);
}
2.2 extractAndroidManifest AndroidManifest的解析
第一步是判斷該項目是否是一個libraryProject盾沫,如果不是則執(zhí)行下面的方法

/**
 * 解析AndroidManifest文件
 *
 * @param androidManifestFile
 * @param libraryProject
 * @return
 * @throws AndroidManifestNotFoundException
 */
private AndroidManifest parse(File androidManifestFile, boolean libraryProject) throws AndroidManifestNotFoundException {
    DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();

    Document doc;
    try {
        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
        doc = docBuilder.parse(androidManifestFile);
    } catch (Exception e) {
        ...
    }

    Element documentElement = doc.getDocumentElement();
    documentElement.normalize();
    //拿到包名
    String applicationPackage = documentElement.getAttribute("package");

    ...
    //解析uses-sdk節(jié)點
    NodeList sdkNodes = documentElement.getElementsByTagName("uses-sdk");
    if (sdkNodes.getLength() > 0) {
        Node sdkNode = sdkNodes.item(0);
        minSdkVersion = extractAttributeIntValue(sdkNode, "android:minSdkVersion", -1);
        ...
    }
    //該項目是否是lib
    if (libraryProject) {
        return AndroidManifest.createLibraryManifest(applicationPackage, minSdkVersion, maxSdkVersion, targetSdkVersion);
    }
    //解析application節(jié)點
    NodeList applicationNodes = documentElement.getElementsByTagName("application");

    String applicationClassQualifiedName = null;
    boolean applicationDebuggableMode = false;

    if (applicationNodes.getLength() > 0) {
        Node applicationNode = applicationNodes.item(0);
        Node nameAttribute = applicationNode.getAttributes().getNamedItem("android:name");
        //前綴(包名)裁赠,用來后面拼上activity或者service的名字
        //因為部分開發(fā)者在開發(fā)時,manifeist文件中定義activity或者service 習(xí)慣不寫全路徑而是以.開頭赴精,這種特殊情況需要處理
        applicationClassQualifiedName = manifestNameToValidQualifiedName(applicationPackage, nameAttribute);

        if (applicationClassQualifiedName == null) {
            if (nameAttribute != null) {
                LOGGER.warn("");
            }
        }

        Node debuggableAttribute = applicationNode.getAttributes().getNamedItem("android:debuggable");
        if (debuggableAttribute != null) {
            applicationDebuggableMode = debuggableAttribute.getNodeValue().equalsIgnoreCase("true");
        }
    }
    //解析四大組件的節(jié)點佩捞,并將每一種都組成一個list,用在后面創(chuàng)建AndroidManifest對象

    NodeList activityNodes = documentElement.getElementsByTagName("activity");
    List<String> activityQualifiedNames = extractComponentNames(applicationPackage, activityNodes);

    NodeList serviceNodes = documentElement.getElementsByTagName("service");
    List<String> serviceQualifiedNames = extractComponentNames(applicationPackage, serviceNodes);

    NodeList receiverNodes = documentElement.getElementsByTagName("receiver");
    List<String> receiverQualifiedNames = extractComponentNames(applicationPackage, receiverNodes);

    NodeList providerNodes = documentElement.getElementsByTagName("provider");
    List<String> providerQualifiedNames = extractComponentNames(applicationPackage, providerNodes);

    List<String> componentQualifiedNames = new ArrayList<>();
    componentQualifiedNames.addAll(activityQualifiedNames);
    componentQualifiedNames.addAll(serviceQualifiedNames);
    componentQualifiedNames.addAll(receiverQualifiedNames);
    componentQualifiedNames.addAll(providerQualifiedNames);

    ...

    return AndroidManifest.createManifest(applicationPackage, applicationClassQualifiedName, componentQualifiedNames, permissionQualifiedNames,
            minSdkVersion, maxSdkVersion, targetSdkVersion, applicationDebuggableMode);
}
2.3 processAnnotations 注解的解析處理
/**
 * ProcessResult的生成蕾哟,用來在下面生成java類的代碼
 * 
 * @param validatedModel
 * @return
 * @throws Exception
 */
public ProcessResult process(AnnotationElements validatedModel) throws Exception {
    ProcessHolder processHolder = new ProcessHolder(environment.getProcessingEnvironment());

    environment.setProcessHolder(processHolder);

    LOGGER.info("Processing root elements");

    /*
     * 循環(huán)生成類文件一忱,包括內(nèi)部類和內(nèi)部類中的內(nèi)部類..
     */
    while (generateElements(validatedModel, processHolder)) {
        // CHECKSTYLE:OFF
        ;
        // CHECKSTYLE:ON
    }

     //處理父類上的注解
    for (AnnotationHandler annotationHandler : environment.getDecoratingHandlers()) {
        String annotationName = annotationHandler.getTarget();


        Set<AnnotatedAndRootElements> ancestorAnnotatedElements = validatedModel
          .getAncestorAnnotatedElements(annotationName);

        for (AnnotatedAndRootElements elements : ancestorAnnotatedElements) {
            GeneratedClassHolder holder = processHolder
                    .getGeneratedClassHolder(elements.rootTypeElement);

            if (holder != null) {
                processThrowing(annotationHandler, elements.annotatedElement, holder);
            }
        }

        Set<? extends Element> rootAnnotatedElements = validatedModel.getRootAnnotatedElements(annotationName);

        for (Element annotatedElement : rootAnnotatedElements) {

            Element enclosingElement;
            if (annotatedElement instanceof TypeElement) {//類或接口
                enclosingElement = annotatedElement;
            } else {
                enclosingElement = annotatedElement.getEnclosingElement();

                if (enclosingElement instanceof ExecutableElement) { //方法
                    enclosingElement = enclosingElement.getEnclosingElement();
                }
            }


            if (!isAbstractClass(enclosingElement)) {
                GeneratedClassHolder holder = processHolder.getGeneratedClassHolder(enclosingElement);
                
                /*
                 * 其實是調(diào)用annotationHandler的各種實現(xiàn)類來執(zhí)行
                 */
                if (holder != null) {
                    processThrowing(annotationHandler, annotatedElement, holder);
                }
            } else {
                LOGGER.trace("Skip element {} because enclosing element {} is abstract", annotatedElement, enclosingElement);
            }
        }

    }

    return new ProcessResult(//
            processHolder.codeModel(), //
            processHolder.getOriginatingElements());
}
2.4 processThrowing
private <T extends GeneratedClassHolder> void processThrowing(AnnotationHandler<T> 
      handler, Element element, T generatedClassHolder) throws ProcessingException {
    try {
        handler.process(element, generatedClassHolder);
    } catch (Exception e) {
        throw new ProcessingException(e, element);
    }
}
2.5 以EActivity為例莲蜘,看下process的實現(xiàn)
@Override
public void process(Element element, EActivityHolder holder) {

    List<JFieldRef> fieldRefs = annotationHelper.extractAnnotationFieldRefs(element, IRClass.Res.LAYOUT, false);

    JFieldRef contentViewId = null;
    if (fieldRefs.size() == 1) {
        contentViewId = fieldRefs.get(0);
    }

    if (contentViewId != null) {
        //JBlock 是 CodeModel jar包中的一個類,CodeModel是生成 Java 代碼的 Java 庫

        //以下代碼的功能純屬猜想帘营,EActivityHolder生成onCreate方法票渠,并在onCreate方法執(zhí)行setContentView,
        //參數(shù)為contentViewId
        JBlock onCreateBody = holder.getOnCreate().body();
        JMethod setContentView = holder.getSetContentViewLayout();
        onCreateBody.invoke(setContentView).arg(contentViewId);
    }
}
2.6 以EActivity為例芬迄,看下EActivityHolder的實現(xiàn)
/**
 * 新生成的java文件中onCreate方法的創(chuàng)建過程
 */
private void setOnCreate() {
    onCreate = generatedClass.method(PUBLIC, getCodeModel().VOID, "onCreate");
    //Override
    onCreate.annotate(Override.class);
    AbstractJClass bundleClass = getClasses().BUNDLE;
    JVar onCreateSavedInstanceState = onCreate.param(bundleClass, "savedInstanceState");
    JBlock onCreateBody = onCreate.body();
    JVar previousNotifier = viewNotifierHelper.replacePreviousNotifier(onCreateBody);
    //執(zhí)行init方法
    onCreateBody.invoke(getInit()).arg(onCreateSavedInstanceState);
    //super.oncreate
    onCreateBody.invoke(_super(), onCreate).arg(onCreateSavedInstanceState);
    viewNotifierHelper.resetPreviousNotifier(onCreateBody, previousNotifier);
}

/**
 *  onCreate方法中setContentView的實現(xiàn)
 */
private void setSetContentView() {
    getOnCreate();

    AbstractJClass layoutParamsClass = getClasses().VIEW_GROUP_LAYOUT_PARAMS;

    setContentViewLayout = setContentViewMethod(new AbstractJType[] { getCodeModel().INT }, new String[] { "layoutResID" });
    setContentViewMethod(new AbstractJType[] { getClasses().VIEW, layoutParamsClass }, new String[] { "view", "params" });
    setContentViewMethod(new AbstractJType[] { getClasses().VIEW }, new String[] { "view" });
}

我們反編譯一下生成的APK问顷,拿到MainActivity_文件,驗證下結(jié)果

Paste_Image.png

總結(jié)下:AndroidAnnotation是一個非常不錯的注解框架禀梳,大大減少了無用代碼的編寫杜窄,核心原理就是在編譯階段,編譯器讀取java文件的所有的注解算途,然后根據(jù)不同的注解來重新生成新的代碼塞耕,將新的代碼組合成新的java類(生成類),在運行的時候嘴瓤,我們實際操作的都是“生成類”荷科。AA總共提供了130個注解,涉及到組件纱注,View,F(xiàn)ragment胆胰,網(wǎng)絡(luò)請求狞贱,各類監(jiān)聽,后臺線程處理蜀涨,數(shù)據(jù)庫瞎嬉,JavaBean等方方面面。

凡事都具有兩面性厚柳,AA的使用所帶來的也不全是好處氧枣,下面來說一下AA的不足。

AndroidAnnotation的問題

  1. 使用成本:
    如第一部分所介紹的一樣别垮,使用AA時便监,我們需要在不同的配置文件中配置各種信息。

  2. 開發(fā)中的成本:
    開發(fā)過程中碳想,盡管缺少了findViewById等無用代碼的書寫烧董,但是又引入了新的問題,我們需要在manifest文件中手動修改activity胧奔,service等組件的類名逊移。

     以上兩項嚴格意義上來說不算缺陷,寫上去湊個字數(shù)龙填「烊→_→
    
  3. 使用AA所生成的代碼造成dex文件中無用代碼增加:
    AA的使用不是避免了無用代碼的書寫拐叉,而是將這部分工作交由代碼來完成,也就是說是通過 “代碼生成代碼”扇商,打個比方凤瘦,要實現(xiàn)一個功能,如果不用注解钳吟,我們可能30行代碼就可以實現(xiàn)廷粒,雖然其中可能有findViewById,各種Listener的聲明等红且。但是這就是我們應(yīng)用運行時所需要的代碼坝茎,而使用注解的話,盡管我們只需要20行代碼就可以實現(xiàn)暇番,但是編譯生成的新的類文件可能會包含有兩倍甚至更多的代碼嗤放。以下是測試的結(jié)果

源文件

Paste_Image.png

生成后的文件

Paste_Image.png

可以看到AA不僅生成了新的java類來繼承原有的組件(activity),并且新的類中的代碼遠多于原始類壁酬。如果是大型的項目次酌,使用AA時需要三思。

不管怎么說舆乔,AndroidAnnotation都是一款優(yōu)秀的注解框架岳服,但是市面上的注解框架也不止AA這個一款,下一章老衲將會給大家?guī)鞟ndroid另一款框架ButterKnife的使用介紹希俩。敬請期待

如有錯誤請指正吊宋,謝謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颜武,一起剝皮案震驚了整個濱河市璃搜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鳞上,老刑警劉巖这吻,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異篙议,居然都是意外死亡唾糯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門鬼贱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趾断,“玉大人,你說我怎么就攤上這事吩愧∮笞茫” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵雁佳,是天一觀的道長脐帝。 經(jīng)常有香客問我同云,道長,這世上最難降的妖魔是什么堵腹? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任炸站,我火速辦了婚禮,結(jié)果婚禮上疚顷,老公的妹妹穿的比我還像新娘旱易。我一直安慰自己,他們只是感情好腿堤,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布阀坏。 她就那樣靜靜地躺著,像睡著了一般笆檀。 火紅的嫁衣襯著肌膚如雪忌堂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天酗洒,我揣著相機與錄音士修,去河邊找鬼。 笑死樱衷,一個胖子當著我的面吹牛棋嘲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矩桂,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼沸移,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了耍鬓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤流妻,失蹤者是張志新(化名)和其女友劉穎牲蜀,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绅这,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡涣达,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了证薇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片度苔。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖浑度,靈堂內(nèi)的尸體忽然破棺而出寇窑,到底是詐尸還是另有隱情,我是刑警寧澤箩张,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布甩骏,位于F島的核電站窗市,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏饮笛。R本人自食惡果不足惜咨察,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望福青。 院中可真熱鬧摄狱,春花似錦、人聲如沸无午。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽指厌。三九已至刊愚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間踩验,已是汗流浹背鸥诽。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留箕憾,地道東北人牡借。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像袭异,于是被迫代替她去往敵國和親钠龙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

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