在日常的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 WXActivityAnnotationProcessor
extends AbstractProcessor {
//可以理解为编译期的文档管理员
private Filer
filer
;
@Override
public synchronized void
init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment)
;
this.
filer = processingEnvironment.getFiler()
;
}
//这个函数式注解处理的核心函数,env对象是编译器环境,可获取整个包中的所有元素属性
@Override
public boolean
process(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 WXActvityAnnotationVisitor
extends SimpleAnnotationValueVisitor7<Void
, Void> {
private final Filer
filer
;
private String
packageName
;
public
WXActvityAnnotationVisitor(Filer filer) {
this.
filer = filer
;
}
@Override
public Void
visitString(String s
, Void aVoid) {
//解析得到包名
this.
packageName = s
;
return aVoid
;
}
@Override
public Void
visitType(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
public
ExampleDelegate_ViewBinding(
final ExampleDelegate target
, View source) {
this.
target = target
;
View view
;
view2131624084 = view
;
view.setOnClickListener(
new DebouncingOnClickListener() {
@Override
public void
doClick(View p0) {
target.onClickTest()
;
}
})
;
}
@Override
@CallSuper
public void
unbind() {
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 boolean
process(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的作者致敬,让程序员摆脱了很多重复的没意义的代码,实属丰功伟绩。