說到java的apt技術(shù)活合,其實已經(jīng)算不是很陌生了屡穗,在以前閱讀第三方框架butterknife
、Dagger2
等框架的時候许溅,看到過apt的影子温鸽。他是squareup
公司出的javapoet
技術(shù)保屯,通過在java的編譯時期生成類,提高了在運行時期通過反射調(diào)用的效率涤垫。大家試想一下姑尺,如果butterknife
所有的注解在運行時期都通過反射調(diào)用相應的findViewById
的話,那得多慢啊雹姊。所以可以看到butterknife
都是通過apt技術(shù)來生成相應的_ViewBinding股缸,大家可以看下app-->build-->generated-->source-->apt下面找到對應的_ViewBinding衡楞。好了廢話不多說吱雏,咋們下面來直接來擼碼。
實現(xiàn)功能還是跟
butterknife
框架findViewById
的功能一樣瘾境,經(jīng)過前幾篇的學習反射歧杏,注解所以才有今天的apt技術(shù)代碼,所以不熟悉反射跟注解的伙伴們迷守,還是先看下反射和注解如何使用犬绒。
-
android studio中創(chuàng)建一個java library的module,這里我起名字叫binder_annotation兑凿,專門用來放注解的凯力,這里生成后是這個樣子:
- 接著在剛創(chuàng)建的binder_annotation中創(chuàng)建注解
//在編譯期起作用的注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
創(chuàng)建java library的module茵瘾,這里我起名字叫binder_compiler。先創(chuàng)建了后面再說
在app的module的build.gradle通過annotationProcessor添加依賴:
dependencies {
....
annotationProcessor project(':binder_compiler')
implementation project(':binder_annotation')
}
這里注意了咐鹤,在gradle tool>=2.2之后直接用annotationProcessor添加apt的依賴拗秘,如果是在gradle tool<2.2首先得在project添加:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
然后在app的build.gradle添加:
apply plugin: 'com.neenbedankt.android-apt'
添加依賴的地方:
apt project(':binder_compiler')
此處我用的是gradle tools 3.4.1因此直接用annotationProcessor
添加依賴。
說完了整體的架子祈惶,下面來到binder_compiler下面雕旨,我們創(chuàng)建BinderProcessor
類,并且繼承于AbstractProcessor
捧请,該類是在編譯期會進行類掃描的處理類凡涩。咋們需要實現(xiàn),在實現(xiàn)之前需要了解幾個方法:
//該方法指定類處理器是什么java版本疹蛉,一般返回SourceVersion.latestSupported()表示最新的java版本就行
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//指明有哪些注解需要被掃描到活箕,返回注解的路徑
@Override
public Set<String> getSupportedAnnotationTypes() {
//大部分class而已getName、getCanonicalNam這兩個方法沒有什么不同的可款。
//但是對于array或內(nèi)部類等就不一樣了讹蘑。
//getName返回的是[[Ljava.lang.String之類的表現(xiàn)形式,
//getCanonicalName返回的就是跟我們聲明類似的形式筑舅。
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
//因為兼容的原因座慰,特別是針對Android平臺,建議使用重載getSupportedAnnotationTypes()方法替代默認使用注解實現(xiàn)
}
在初始化的時候獲取到掃描對象:
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//processingEnvironment.getElementUtils(); 處理Element的工具類翠拣,用于獲取程序的元素版仔,例如包、類误墓、方法蛮粮。
//processingEnvironment.getTypeUtils(); 處理TypeMirror的工具類,用于取類信息
//processingEnvironment.getFiler(); 文件工具
//processingEnvironment.getMessager(); 錯誤處理工具
//初始化的時候獲取到當前掃描的對象
//processingEnv是父類定義的ProcessingEnvironment對象谜慌,其實就是init方法回傳過來的
mElementUtils = processingEnv.getElementUtils();
}
我們的重頭戲來了process
方法:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//掃描整個工程 找出含有BindView注解的元素
//找到所有帶有BindView注解的類然想,生成對應的****_ViewBinding類
Set<? extends Element> elements =
roundEnvironment.getElementsAnnotatedWith(BindView.class);
//遍歷元素
for (Element element : elements) {
//BindView限定了只能屬性使用,這里強轉(zhuǎn)為VariableElement欣范,如果是在類上面的变泄,那么就是typeElement
VariableElement variableElement = (VariableElement) element;
//返回此元素直接封裝(非嚴格意義上)的元素。
//類或接口被認為用于封裝它直接聲明的字段恼琼、方法妨蛹、構(gòu)造方法和成員類型
//這里就是獲取封裝屬性元素的類元素
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
//獲取簡單類名
String fullClassName = classElement.getQualifiedName().toString();
//里面放的是BinderClassCreator,關鍵生成***_ViewBinding類在里面生成的
BinderClassCreator creator = mCreatorMap.get(fullClassName);
if (creator == null) {
creator = new BinderClassCreator(mElementUtils.getPackageOf(classElement),
classElement);
//生成之后就放到map中晴竞,方便下次使用
mCreatorMap.put(fullClassName, creator);
}
//獲取元素注解
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
//注解值
int id = bindAnnotation.value();
creator.putElement(id, variableElement);
}
for (String key : mCreatorMap.keySet()) {
BinderClassCreator binderClassCreator = mCreatorMap.get(key);
//通過javapoet構(gòu)建生成Java類文件
//第一個參數(shù)傳入包名
//第二個參數(shù)傳入TypeSpec
JavaFile javaFile = JavaFile.builder(binderClassCreator.getPackageName(),
binderClassCreator.generateJavaCode()).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
process方法里面主要做了幾件事:
- 掃描工程里面帶有
BindView
注解的類 - 通過注解的類拿到類信息蛙卤,生成
BinderClassCreator
對象,然后放到map中,將id和VariableElement
對象給BinderClassCreator
對象颤难,后面會用到 - 最后生成javaFile對象神年,通過writeTo生成對應的***_ViewBinding。
在上面代碼中將每一個要生成***_ViewBinding類的工作都交給了BinderClassCreator
類行嗤,其實最關心的還是該類:
首先看構(gòu)造方法:
public static final String ParamName = "view";
private TypeElement mTypeElement;
private String mPackageName;
private String mBinderClassName;
//key是view在xml中的id瘤袖,value是作用在類上面的element對象
private Map<Integer, VariableElement> mVariableElements = new HashMap<>();
/**
* @param packageElement 包元素
* @param classElement 類元素
*/
public BinderClassCreator(PackageElement packageElement, TypeElement classElement) {
this.mTypeElement = classElement;
mPackageName = packageElement.getQualifiedName().toString();
mBinderClassName = classElement.getSimpleName().toString() + "_ViewBinding";
}
構(gòu)造器基本沒做什么,主要是初始化包名和class類名昂验。
存儲了id和作用在類上面的element對象
public void putElement(int id, VariableElement variableElement) {
mVariableElements.put(id, variableElement);
}
//生成類的代碼
public TypeSpec generateJavaCode() {
return TypeSpec.classBuilder(mBinderClassName)
//public 修飾類
.addModifiers(Modifier.PUBLIC)
//添加類的方法
.addMethod(generateMethod())
//構(gòu)建Java類
.build();
}
//生成bindView方法的代碼
private MethodSpec generateMethod() {
//獲取所有注解的類的類名
ClassName className = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
//構(gòu)建方法--方法名
return MethodSpec.methodBuilder("bindView")
//public方法
.addModifiers(Modifier.PUBLIC)
//返回void
.returns(void.class)
//方法傳參(參數(shù)全類名捂敌,參數(shù)名)
.addParameter(className, ParamName)
//方法代碼
.addCode(generateMethodCode())
.build();
}
//生成bindView方法里面代碼的代碼
private String generateMethodCode() {
StringBuilder code = new StringBuilder();
for (int id : mVariableElements.keySet()) {
VariableElement variableElement = mVariableElements.get(id);
//使用注解的屬性的名稱
String name = variableElement.getSimpleName().toString();
//使用注解的屬性的類型
String type = variableElement.asType().toString();
//view.name = (type)view.findViewById(id)
String findViewCode = ParamName + "." + name + "=(" + type + ")" + ParamName +
".findViewById(" + id + ");\n";
code.append(findViewCode);
}
return code.toString();
}
上面定義了三個方法,一個生成類既琴,另外兩個是對bindView方法的代碼生成占婉,相信大家細心點看還是看得懂的。
所有的代碼工作做好了后甫恩,緊接著需要去注冊和啟動BinderProcessor
此處是google提供的AutoService注解逆济,用來掃描工程注解的掃描器,第二個要注意的地方是啟動掃描器:
在main下面生成
javax.annotation.processing.Processor
文件磺箕,里面寫上要被啟動的掃描器:
com.single.router_compiler.BinderProcessor
使用
由于我們生成的APT代碼奖慌,肯定只有在運行期才能使用的,所以在編譯之前肯定是找不到***_ViewBinding類的松靡,因此咋們得需要寫個反射調(diào)用該類的工具類:
public class BinderViewTools {
public static void init(Activity activity) {
Class clazz = activity.getClass();
try {
Class<?> bindClass = Class.forName(clazz.getName() + "_ViewBinding");
Method bind = bindClass.getMethod("bindView", class);
bind.invoke(bindClass.newInstance(), activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
不熟悉反射的伙伴們可以看下反射如何使用简僧,這里就不多說了。更多反射知識雕欺,在activity中直接使用:
BinderViewTools.init(this);
接著編譯下工程岛马,在app目錄的build下面可以看到生成了activity對應的*** _ViewBinding類: