android 使用apt(编译时注解) 自动生成第三方的狗皮膏药代码
在日常的Android项目开发中,免不了集成大量第三方库,由于各个公司开发风格不一,导致在项目集成过程中东粘一块西粘一块,对于有代码洁癖的人来说无疑是场灾难,面对第三方库如此强大的代码侵入性,我们无所适从,只能尽量整合,用良好的编码结构来规避混乱,不过java也为我们提供了一套在编译期自动生成代码的利器,让我们不再面对那些某些狗皮膏药似的代码,以微信登录为例(抛砖引玉),做微信登录的朋友都知道集成它要强制编写一个WXEntryActivity类,包名还有一定规范,必须是apk包名下的.wxapi下,实在不能接受这种写法的朋友可以继续往下看,当然看完这篇文章,那么你对ButterKnife(通过自动生成代码),XUtils(通过反射实现比ButterKnife逊色)等诸多注解框架的核心原理也就知道了。
首先为什么我们要自动生成微信Activity,直接写上去不是更方便吗?答案是为了抽取到基准库,我们作为开发人员都有自己的库文件,当需要开发项目时直接把库集成进来就可以了,而第三方微信的集成如登录,支付在正常情况下需要已知包名的情况下去书写类文件,而底层库不依赖于具体项目而存在,抽取有诸多不便。大致思路如下:首先我们在底层库中创建一个activity,在这个activity中完成微信登录后的所有操作逻辑并发出回调,对于上层用户(项目开发者)来说,他只关心微信的最终openId是否能得到,至于中间的过程就交给底层框架来处理,框架负责发出登录请求,接收微信返回并回馈给上层项目,最大限度实现解耦,现在切入正题。
第一步:创建整个项目架构,如图:
我们把整个项目拆分成四个部分。
- android application模块—相当于主项目。
- 注解模块— java库模块(定义注解的模块,因为annotation属于java范畴,所以只要是java工程即可)。
- 注解编译模块—(同样是java模块,因为需要使用java及第三方的注解处理api)。
- 核心库模块— (android库,因为核心库涉及到UI,网络以及抽取的基类如BaseActivity等,所以必须是android库)。
对于模块的拆分因人而异,不过在实际项目中模块的拆分我觉得是很有必要的,他可以灵活搭配,适度取舍。甚至以上这种拆分我都闲太集中,我们完全可以把apt-lib拆分为net library,ui library, utils library,还有各种thirdpart library,甚至可以更细,谁都不知道实际项目需要用到什么,当把没必要的库引入到了项目中,不但增加了包体大小,同时也影响了审美,对代码洁癖者来说是无法忍受的,不过在这里我就只用四个模块吧。
第二:梳理依赖关系
- apt-android 依赖 apt-lib
- apt-lib 依赖 apt-annotations
- apt-android 编译期依赖apt-compiler (就是在编译的时候使用apt-compiler来产生java代码在apt-android下的gradle配置中只要加上annotationProcessor project(':apt-compile')
- apt-compiler 依赖apt-annotations 同时也依赖部分第三方jar
compile 'com.squareup:javapoet:1.9.0’ (生成java类的关键库,提供了很多非常简洁的api)
compile 'com.google.auto.service:auto-service:1.0-rc3’
compile 'com.google.auto:auto-common:0.8’
compile project(':apt-annotations’)
第三:编写java代码
在apt-annotations中定义一个WXActivityGenerator 微信生成器注解。
package
com.late.apt.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by xuhongliang on 2017/11/15.
*/
//声明注解作用范围是作用在类,接口,注解,枚举上
@Target(ElementType.TYPE)
//声明注解的有效期为源码期
@Retention(RetentionPolicy.SOURCE)
public
@interface
WXActvityGenerator {
//声明该注解所要生成的包名规则
String getPackageName();
//声明该注解所生成java类需要继承哪个父类
Class<?> getSupperClass();
}
在apt-lib库中定义模板类,继承自AppCompatActivity
package com.late.apt.lib;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
/**
* Created by xuhongliang on 2017/11/15.
*/
public class WXTemplateActivity
extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//执行登录回调逻辑,包括请求token,获取openid,回给真实项目模块
}
}
在apt-android中随便哪个类上使用注解@WXActivityGenerator,这里在MainActivity上使用
package com.xhl.apt;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.late.apt.annotations.WXActvityGenerator;
import com.late.apt.lib.WXTemplateActivity;
@WXActvityGenerator(
getPackageName =
"com.xhl.apt",
getSupperClass = WXTemplateActivity.class
)
public class MainActivity
extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
以上三步是定义并使用了注解,具体的注解处理生成代码还未写,我们在apt-compiler中写相关注解处理代码.
定义一个WXActivityAnnotationProcessor
import com.google.auto.service.AutoService;
import com.late.apt.annotations.WXActvityGenerator;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor7;
/**
* Created by xuhongliang on 2017/11/15.
*/
@AutoService(Processor.class)
public class WXActivityAnnotationProcessorextends AbstractProcessor {
//可以理解为编译期的文档管理员
private Filerfiler;
@Override
public synchronized voidinit(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
this.filer = processingEnvironment.getFiler();
}
//这个函数式注解处理的核心函数,env对象是编译器环境,可获取整个包中的所有元素属性
@Override
public booleanprocess(Set<?extends TypeElement> set, RoundEnvironment env)
{
generatorJavaCode(filer, env);
return true;
}
private void
generatorJavaCode(Filer filer, RoundEnvironment env) {
//首先创建一个注解属性解析器
final WXActvityAnnotationVisitor visitor =new WXActvityAnnotationVisitor(filer);
//获取标注了WXActvityGenerator注解的所有元素集合
final Set<?extends Element> elementsWithWXAnnotation = env.getElementsAnnotatedWith(WXActvityGenerator.class);
//遍历
for (Element element : elementsWithWXAnnotation) {
//取得带有属性的注解元素
final List<?extends AnnotationMirror> annotationMirrors = element.getAnnotationMirrors();
//遍历
for (AnnotationMirror annotationMirror : annotationMirrors) {
//获取注解属性值
final Map<? extends ExecutableElement, ?extends AnnotationValue> map = annotationMirror.getElementValues();
for (AnnotationValue annotationValue : map.values()) {
//把属性值丢给注解解析器处理
annotationValue.accept(visitor, null);
}
}
}
}
@Override
public Set<String>getSupportedAnnotationTypes() {
//因为就一个注解,所以创建只存在一个注解的集合
final Set<String> annotationNames = Collections.singleton(WXActvityGenerator.class.getCanonicalName());
return annotationNames;
}
final static class WXActvityAnnotationVisitorextends SimpleAnnotationValueVisitor7<Void, Void> {
private final Filer
filer;
private String
packageName;
public
WXActvityAnnotationVisitor(Filer filer) {
this.filer = filer;
}
@Override
public VoidvisitString(String s, Void aVoid) {
//解析得到包名
this.packageName = s;
return aVoid;
}
@Override
public VoidvisitType(TypeMirror typeMirror, Void aVoid) {
//生成java类也就是WXEntryActivity
generatorJavaCode(typeMirror);
return aVoid;
}
private void
generatorJavaCode(TypeMirror typeMirror) {
try {
//创建类的描述
final TypeSpec classType = TypeSpec.classBuilder("WXEntryActivity")
.addModifiers(Modifier.FINAL, Modifier.PUBLIC)
.superclass(TypeName.get(typeMirror)).build();
//创建java类文件
final JavaFile javaFile = JavaFile.builder(packageName +".wxapi", classType)
.addFileComment("WXActivity")
.build();
//写入编译文档文件
javaFile.writeTo(filer);
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
因为注释已经很详细了,在这边也不解释具体怎么写了,主要是定义一个注解处理器,这个处理器继承自AbstractProcessor,
这个类是java包中的专门处理注解的抽象类,我们只要实现process方法即可拿到注解上的元素信息,接着丢给一个注解元素访问者即AnnotationVisitor,我们通过他的visit+类型这类方法就可以拿到注解中定义的元素值,也就是packageName与需要继承的类。
最后我们编译工程就会发现我们的WXEntryActivity神不知鬼不觉的出现在了项目中,而且不用被你看见就可以使用他,毕竟眼不见为净。
而相关的处理逻辑我们已经在模板类中执行,这些逻辑是通用的,不管在哪个项目里都一样。等有新项目我们只要引入包,配置下依赖,配置下清单文件,再写个注解即可,根本不用重复在写登录分享逻辑了。
在这里只是以生成微信特有类为例来使用了一下apt,其实在我们的项目中apt还可以干更多的事,比如根据json文件自动生成java类,都是可以在编译期来实现的,再也不用你手写javaBean了,当然网上有这种工具,那如果我们自己实现岂不是很牛逼?
ButterKnife作为非常知名的注解框架也是使用了这一原理,无非就多了些处理逻辑,本质是一样的,我们来简单看下源码。
这就相当于我们的apt-annotations库,只是butterknife定义的注解多了点而已,注解本质就是为了识别属性与提供属性值而存在的。
这个类就是执行绑定的整个逻辑的库,本质最终处理还是会走findViewById(R.id.btn);
public final class ButterKnife {
@NonNull @UiThread
public static Unbinder
bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return
createBinding(target, sourceView);
}
private static Unbinder
createBinding(@NonNull Object target,@NonNull View source) {
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG,"Looking
up binding for " + targetClass.getName());
Constructor<?
extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor ==
null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
return constructor.newInstance(target, source);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InstantiationException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause
instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause
instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException("Unable to create binding instance.", cause);
}
}
}
这句是核心
Constructor<?
extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
我们拿到了compiler生成的java类的构造器生成对象,在生成对象的时候已经将具体view赋值了,以下是具体的生成类。
package com.diabin.fastec.example;
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;
public class ExampleDelegate_ViewBinding
implements Unbinder {
private ExampleDelegate
target;
private View
view2131624084;
@UiThread
publicExampleDelegate_ViewBinding(final ExampleDelegate target, View source)
{
this.target = target;
View view;
view2131624084 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public voiddoClick(View p0) {
target.onClickTest();
}
});
}
@Override
@CallSuper
public voidunbind() {
if (target ==null)throw new IllegalStateException("Bindings already cleared.");
target =null;
view2131624084.setOnClickListener(null);
view2131624084 =null;
}
}
在来看butterknife的compiler包
ButterKnifeProcessor为核心类.
process方法如初一折,目的就是生成像 ExampleDelegate_ViewBinding这样的类(对于Activity来说是中间赋值类)。
@Override public booleanprocess(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;
}
附github地址: https://github.com/lategege/apt-android/tree/master
在本文结尾向ButterKnife的作者致敬,让程序员摆脱了很多重复的没意义的代码,实属丰功伟绩。