Hello,大家好,老衲第一次在上发文章,请大家多多支持。今天给大家带来的是Android开发中常用的AndroidAnnotation(以下简称AA)框架的使用及其内部的实现流程。
AA在Android开发者中使用非常广泛。他减少了无用代码的编写。提高了开发者的效率。让开发者将更多的时间放到真正需要关注的地方。首先说明下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文件
修改规则:需要将加入@EActivity,@EService等注解的类的文件名后加下划线,如下图,因为通过AA来生成新的类名
是在原有的基础上加上了下划线,所以我们声明的时候也需要加上下划线,否则提示找不到文件。
常用的注解
以下是我直接从官网去扒的代码
@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 //绑定系统服务
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//子线程执行
void searchAsync(String searchString, String userId) {
Bookmarks bookmarks = restClient.getBookmarks(searchString, userId);
updateBookmarks(bookmarks);
}
@UiThread//主线程执行
void updateBookmarks(Bookmarks bookmarks) {
adapter.updateBookmarks(bookmarks);
bookmarkList.startAnimation(fadeIn);
}
@ItemClick //ItemClick监听
void bookmarkListItemClicked(Bookmark selectedBookmark) {
clipboardManager.setText(selectedBookmark.getUrl());
}
@EBean //声明一个普通的java类(不能是Android中的组件)并且只有一个无参构造方法或者带context的构造方法(在AA version2.7)
@EProvider //contentProvider
@EReceiver //BroadcastReceiver
@EIntentService //IntentService
@EService //Service
@EView //View的注解,需注意:使用的时候是 ClassName_
@EViewGroup //ViewGroup的注解
@AfterExtras//activity之间的参数传递完成后调用
@AfterInject //依赖注入完成后执行的方法
@AfterViews //View绑定后执行的方法
@Extras //参数传递调用
balabala....实在太多太全面了,建议大家去github上自己去看...— —!
https://github.com/excilys/androidannotations/wiki/AvailableAnnotations
}
AA实现的流程。
AA在使用时,无需增加其他的额外代码。。只增加必要的注解。因此它的逻辑全部都在AndroidAnnotationProcessor的process方法中。
1.注解处理的初始化操作:
AndroidAnnotationProcessor.java
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
androidAnnotationsEnv = new InternalAndroidAnnotationsEnvironment(processingEnv);
//定义新生成的java文件的后缀,默认的是在原类名的基础上添加下划线“_”
ModelConstants.init(androidAnnotationsEnv);
//日志处理
LoggerContext loggerContext = LoggerContext.getInstance();
loggerContext.setEnvironment(androidAnnotationsEnv);
try {
//CorePlugin中包含了所有的AA能够处理的注解类的处理逻辑,XXXHandler
//比如EActivityHandler
AndroidAnnotationsPlugin corePlugin = new CorePlugin();
...
List 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();
//注解环境中放入合法的注解
androidAnnotationsEnv.setValidatedElements(validatingHolder);
try {
//解析AndroidAnnotationManifest文件
AndroidManifest androidManifest = extractAndroidManifest();
//创建R文件对象
IRClass rClass = findRClasses(androidManifest);
//将R文件和AndroidManifest放到Context中去
androidAnnotationsEnv.setAndroidEnvironment(rClass, androidManifest);
} catch (Exception e) {
return;
}
//注解的校验
//这里不会进行所有情况的校验,而是假设父类已经校验过了。
AnnotationElements validatedModel = validateAnnotations(extractedModel, validatingHolder);
//注解的处理
//processResult中包含了某一个类创建所需要的全部信息,名称,属性和方法,然后我们根据信息生成.java
ModelProcessor.ProcessResult processResult = processAnnotations(validatedModel);
generateSources(processResult);
}
2.2 extractAndroidManifest AndroidManifest的解析
第一步是判断该项目是否是一个libraryProject,如果不是则执行下面的方法
/**
* 解析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节点
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节点
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的名字
//因为部分开发者在开发时,manifeist文件中定义activity或者service 习惯不写全路径而是以.开头,这种特殊情况需要处理
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");
}
}
//解析四大组件的节点,并将每一种都组成一个list,用在后面创建AndroidManifest对象
NodeList activityNodes = documentElement.getElementsByTagName("activity");
List activityQualifiedNames = extractComponentNames(applicationPackage, activityNodes);
NodeList serviceNodes = documentElement.getElementsByTagName("service");
List serviceQualifiedNames = extractComponentNames(applicationPackage, serviceNodes);
NodeList receiverNodes = documentElement.getElementsByTagName("receiver");
List receiverQualifiedNames = extractComponentNames(applicationPackage, receiverNodes);
NodeList providerNodes = documentElement.getElementsByTagName("provider");
List providerQualifiedNames = extractComponentNames(applicationPackage, providerNodes);
List 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");
/*
* 循环生成类文件,包括内部类和内部类中的内部类..
*/
while (generateElements(validatedModel, processHolder)) {
// CHECKSTYLE:OFF
;
// CHECKSTYLE:ON
}
//处理父类上的注解
for (AnnotationHandler annotationHandler : environment.getDecoratingHandlers()) {
String annotationName = annotationHandler.getTarget();
Set 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);
/*
* 其实是调用annotationHandler的各种实现类来执行
*/
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 void processThrowing(AnnotationHandler
handler, Element element, T generatedClassHolder) throws ProcessingException {
try {
handler.process(element, generatedClassHolder);
} catch (Exception e) {
throw new ProcessingException(e, element);
}
}
2.5 以EActivity为例,看下process的实现
@Override
public void process(Element element, EActivityHolder holder) {
List 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方法执行setContentView,
//参数为contentViewId
JBlock onCreateBody = holder.getOnCreate().body();
JMethod setContentView = holder.getSetContentViewLayout();
onCreateBody.invoke(setContentView).arg(contentViewId);
}
}
2.6 以EActivity为例,看下EActivityHolder的实现
/**
* 新生成的java文件中onCreate方法的创建过程
*/
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);
//执行init方法
onCreateBody.invoke(getInit()).arg(onCreateSavedInstanceState);
//super.oncreate
onCreateBody.invoke(_super(), onCreate).arg(onCreateSavedInstanceState);
viewNotifierHelper.resetPreviousNotifier(onCreateBody, previousNotifier);
}
/**
* onCreate方法中setContentView的实现
*/
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_文件,验证下结果
总结下:AndroidAnnotation是一个非常不错的注解框架,大大减少了无用代码的编写,核心原理就是在编译阶段,编译器读取java文件的所有的注解,然后根据不同的注解来重新生成新的代码,将新的代码组合成新的java类(生成类),在运行的时候,我们实际操作的都是“生成类”。AA总共提供了130个注解,涉及到组件,View,Fragment,网络请求,各类监听,后台线程处理,数据库,JavaBean等方方面面。
凡事都具有两面性,AA的使用所带来的也不全是好处,下面来说一下AA的不足。
AndroidAnnotation的问题
使用成本:
如第一部分所介绍的一样,使用AA时,我们需要在不同的配置文件中配置各种信息。-
开发中的成本:
开发过程中,尽管缺少了findViewById等无用代码的书写,但是又引入了新的问题,我们需要在manifest文件中手动修改activity,service等组件的类名。以上两项严格意义上来说不算缺陷,写上去凑个字数。→_→
使用AA所生成的代码造成dex文件中无用代码增加:
AA的使用不是避免了无用代码的书写,而是将这部分工作交由代码来完成,也就是说是通过 “代码生成代码”,打个比方,要实现一个功能,如果不用注解,我们可能30行代码就可以实现,虽然其中可能有findViewById,各种Listener的声明等。但是这就是我们应用运行时所需要的代码,而使用注解的话,尽管我们只需要20行代码就可以实现,但是编译生成的新的类文件可能会包含有两倍甚至更多的代码。以下是测试的结果
源文件
生成后的文件
可以看到AA不仅生成了新的java类来继承原有的组件(activity),并且新的类中的代码远多于原始类。如果是大型的项目,使用AA时需要三思。
不管怎么说,AndroidAnnotation都是一款优秀的注解框架,但是市面上的注解框架也不止AA这个一款,下一章老衲将会给大家带来Android另一款框架ButterKnife的使用介绍。敬请期待
如有错误请指正,谢谢。