Hello,大家好,老衲第一次在簡書上發(fā)文章仲器,請大家多多支持煤率。今天給大家?guī)淼氖茿ndroid開發(fā)中常用的AndroidAnnotation(以下簡稱AA)框架的使用及其內(nèi)部的實現(xiàn)流程。
AA在Android開發(fā)者中使用非常廣泛乏冀。他減少了無用代碼的編寫蝶糯。提高了開發(fā)者的效率。讓開發(fā)者將更多的時間放到真正需要關(guān)注的地方辆沦。首先說明下AA的使用方法 昼捍, 這里以AndroidStudio為例
如何在AndroidStudio中使用AA注解框架
首先說明下需要修改的文件
1.在工程的根Build.gradle文件中需要添加如下代碼
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
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'
}
}
添加位置如下
3.修改AndroidManifest.xml文件
修改規(guī)則:需要將加入@EActivity,@EService等注解的類的文件名后加下劃線肢扯,如下圖妒茬,因為通過AA來生成新的類名
是在原有的基礎(chǔ)上加上了下劃線,所以我們聲明的時候也需要加上下劃線蔚晨,否則提示找不到文件乍钻。
常用的注解
以下是我直接從官網(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é)果
總結(jié)下:AndroidAnnotation是一個非常不錯的注解框架禀梳,大大減少了無用代碼的編寫杜窄,核心原理就是在編譯階段,編譯器讀取java文件的所有的注解算途,然后根據(jù)不同的注解來重新生成新的代碼塞耕,將新的代碼組合成新的java類(生成類),在運行的時候嘴瓤,我們實際操作的都是“生成類”荷科。AA總共提供了130個注解,涉及到組件纱注,View,F(xiàn)ragment胆胰,網(wǎng)絡(luò)請求狞贱,各類監(jiān)聽,后臺線程處理蜀涨,數(shù)據(jù)庫瞎嬉,JavaBean等方方面面。
凡事都具有兩面性厚柳,AA的使用所帶來的也不全是好處氧枣,下面來說一下AA的不足。
AndroidAnnotation的問題
使用成本:
如第一部分所介紹的一樣别垮,使用AA時便监,我們需要在不同的配置文件中配置各種信息。-
開發(fā)中的成本:
開發(fā)過程中碳想,盡管缺少了findViewById等無用代碼的書寫烧董,但是又引入了新的問題,我們需要在manifest文件中手動修改activity胧奔,service等組件的類名逊移。以上兩項嚴格意義上來說不算缺陷,寫上去湊個字數(shù)龙填「烊→_→
使用AA所生成的代碼造成dex文件中無用代碼增加:
AA的使用不是避免了無用代碼的書寫拐叉,而是將這部分工作交由代碼來完成,也就是說是通過 “代碼生成代碼”扇商,打個比方凤瘦,要實現(xiàn)一個功能,如果不用注解钳吟,我們可能30行代碼就可以實現(xiàn)廷粒,雖然其中可能有findViewById,各種Listener的聲明等红且。但是這就是我們應(yīng)用運行時所需要的代碼坝茎,而使用注解的話,盡管我們只需要20行代碼就可以實現(xiàn)暇番,但是編譯生成的新的類文件可能會包含有兩倍甚至更多的代碼嗤放。以下是測試的結(jié)果
源文件
生成后的文件
可以看到AA不僅生成了新的java類來繼承原有的組件(activity),并且新的類中的代碼遠多于原始類壁酬。如果是大型的項目次酌,使用AA時需要三思。
不管怎么說舆乔,AndroidAnnotation都是一款優(yōu)秀的注解框架岳服,但是市面上的注解框架也不止AA這個一款,下一章老衲將會給大家?guī)鞟ndroid另一款框架ButterKnife的使用介紹希俩。敬請期待
如有錯誤請指正吊宋,謝謝。