前言
有人说细节决定成败,或者说别人注重的是成果,咱们介意的是过程,roomCompilerProcessing源码读起来感觉好难哦,尤其细节处!!!
下面三浅一深,咳咳咳…由浅入深讲解roomCompilerProcessing源码。
下面每个部分,自己认真写,读者认真看。个人建议,能够在深化一点:在了解大框架的基础上,不要疏忽细节部分(提示自己和读者),不然自己想写出高逼格代码照样很难!!!
APT、KAPT和KSP的了解
简介
- apt:Annotation Processing Tool
- 需求apply相应的android-apt插件,比方apply plugin: ‘com.neenbedankt.android-apt’。android gradle插件版别2.2以下运用,2.2发版时宣布不再保护。只支撑 javac编译办法。
- annotationProcessor:现已替代apt
- 无需apply android-apt插件。android gradle插件版别2.2及以上运用。一同支撑javac和jack编译办法;
- kapt:Kotlin Annotation Processing Tool
-
kotlin注解处理东西。因kotlin-kapt不是android gradle内置插件,需求额外apply plugin: ‘kotlin-kapt’
-
和annotationProcesor的差异是,kapt处理kotlin文件,当然假如是kotlin或java混合,那么也是有必要运用kapt处理的。速度上交apt(或annotationProcessor)肯定要慢的,由于首先会将kotlin解析成Java代码,再经过apt处理;
- ksp:Kotlin Symbol Processing
-
在进行Android利用开发时Kotlin 的编译速度慢,而KAPT 就是拖慢编译的元凶之一。Android的许多库都会运用注解简化模板代码,著名的有 Room、Dagger 等,而默认状况下Kotlin 运用的是 KAPT 来处理注解的。KAPT没有专门的注解处理器,需求借助APT完结的,因而需求先生成 APT 可解析的 stub (Java代码),这拖慢了 Kotlin 的整体编译速度。
-
KSP 正是在这个布景下诞生的,它基于 Kotlin Compiler Plugin(简称KCP) 完结,不需求生成额外的 java代码,编译速度是 KAPT 的 2 倍以上。
以上文字多数抄袭,融入了个人观点,为了让咱们简略了解一下各个编译的差异。
demo
AbstractProcessor
kapt和annotationProcessor的运用完全一致。
@AutoService(Processor.class) //自动生成注解处理器路径文件
public class BindingProcessor extends AbstractProcessor {
init(ProcessingEnvironment processingEnv):初始化操作
getSupportedSourceVersion():设置版别
getSupportedAnnotationTypes():设置支撑的注解
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv):首要的注解元素解析办法
}
没有AbstractProcessor基础的,可参阅Java学习之注解(五)Android按部就班完结高逼格自界说ViewBinder。
SymbolProcessor
interface SymbolProcessor {
//处理节点
fun process(resolver: Resolver): List<KSAnnotated>
//处理结束
fun finish() {}
//处理反常
fun onError() {}
}
详细完结如下:
class RoomKspProcessor @JvmOverloads constructor(
symbolProcessorEnvironment: SymbolProcessorEnvironment
) : SymbolProcessor {
//承继SymbolProcessor三个办法,懒得写。。。
//ksp内部会调用当时类的create办法,用于生成RoomKspProcessor目标
class Provider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return RoomKspProcessor(environment)
}
}
}
由上可知,AbstractProcessor和SymbolProcessor仍是有所差异的:
-
AbstractProcessor中的处理顺序是:先init,再getSupportedAnnotationTypes当时需求处理的注解;然后调用process处理注解;
-
SymbolProcessor:先直接调用process处理;假如完结调用finish,不然调用onError做反常处理;
-
AbstractProcessor判别是否完结悉数处理的办法:在process办法有个参数RoundEnvironment,该参数提供processingOver办法判别当时处理是否结束;
-
SymbolProcessor假如也想要init初始化,那么能够在process办法中调用;
-
AbstractProcessor的运用时承继当时类而且运用@AutoService(Processor::class)注解润饰该类;SymbolProcessor是承继该类,运用SymbolProcessorProvider的create实例化运用该ksp处理注解的类;
-
SymbolProcessor办法中没有getSupportedAnnotationTypes办法(支撑处理的注解),所以能够直接在process办法中界说需求处理的注解;
以上就相当于一个demo的讲解了,由于个人感觉不难,一切没这方面知识的读者能够自行去了解。
roomCompilerProcessing架构按部就班
引子
假如按照以上demo的用法,咱们以AbstractProcessor的运用为例:
假如需求处理的注解十分多,怎么办;而且每个(或者每一类)注解又有自己的意义,咱们不或许一切的注解放一同,在getSupportedAnnotationTypes中一次性表达悉数需求处理的悉数注解,再在process一次性处理;
roomCompilerProcessing的诞生能很好处理这个问题,将不同的注解分类而且对当时分类处理,那么怎么做呢?
甄别注解,而且对不同类型注解处理
以下手敲版,不要过于介意代码,首要是代码中溢出的杀气…咳咳咳,思维才是最重要的。
-
界说step接口用于搜集某一类注解而且对该类注解处理:
public interface Step{ //搜集当时step处理的注解 public Set<String> getAnnotations(); //处理当时搜集到的注解调集的详细事务逻辑,返回当时被回绝处理的节点 public List<TypeElement> process(ProcessingEnv env,Set<Element>); }
-
界说一个抽象类BasicProcessor承继AbstractProcessor,搜集Step:
public abstract BasicProcessor extends AbstractProcessor{ List<Step> processingSteps(); //预留一个注解处理完结的办法,承继者用就用,不必就算了 void postOverRound(MultableSet<TypeElement> annotations,RoundEnvironment rounEnv){} List<Step> steps; final override void init(ProcessingEnvironment processingEnv){ super.init(processingEnv); steps = processingSteps(); } override Set<String> getSupportedAnnotationTypes(){ steps.flatMap { it.annotations() }.toSet(); } override boolean process(MultableSet<TypeElement> annotations,RoundEnvironment rounEnv){ if (roundEnv.processingOver()) { postOverRound(annotations,rounEnv); } steps.stream().forEach(step ->{ step.process(); }); } }
-
Step实例StepInstall:
public StepInstall interface Step { override Set<String> getAnnotations{ //表明当时搜集的是Database注解 mutableSetOf(Database.qualifiedName) } override Set<TypeElement> process(ProcessingEnv env,Set<Element>){ //当时处理的就是Database注解的事务场景 ... //当时被回绝处理的节点 return rejectedElements; } }
-
AbsractProcessor实例:
public RoomProcessor extends BasicProcessor { override List<Step> processingSteps(){ listof(StepInstall()); } override SourceVersion getSupportedSourceVersion(){ return SourceVersion.latest(); } }
写的不是很好,可是那种高逼格意境表达出来了!!!
扩展:推迟或回绝处理的节点
什么情况下注解处理的节点需求推迟处理或回绝处理呢?
-
节点尚没有生成,又称为无效节点,需求延时处理;
-
在Step处理过程中,依据事务需求,被回绝处理的节点;
揍一顿比方给大家看下:
-
@Annotation1注解润饰的类,经过Step1处理生成了@Annotation2润饰的类。Step2刚好能够处理@Annotation2注解(假如这个@Annotation2润饰的节点是一个特定类,很重要,比方就叫SpecialCoolMan,在未生成这个SpecialCoolMan的情况下就属于无效节点)。
-
而又由于每个原因(Step2的process里有个事务场景,或许感觉这个@Annotation2润饰的当时类存在private润饰的变量,感觉它太特么小气,搞的我没面子,那我就不给他处理了,这个@Annotation2润饰的类就被叫做回绝节点),当时Step2回绝处理这个@Annotation2润饰的类。
对以上场景了解一番:
-
奔着尊重代码原则(那假如不尊重Step2在Step1前履行是否完全没问题呢,往下看有答案):Step1仍是先一步履行,再去履行Step2;
-
BasicProcessor类中有两个变量:(1)搜集一切Step中被回绝处理的节点;(2)搜集一切Step中的无效节点;
-
BasicProcessor process办法会顺次履行Step process办法:(1)处理当时注解的事务逻辑;(2)之前堆集的被Step回绝的节点和无效节点是否运用了当时Step的注解,假如是则处理;再搜集当时Step被回绝的节点和无效节点。直到一切Step都处理结束!
-
咱们在BasicProcessor的process办法中依据RoundEnvironment.processingOver判别是否履行结束,假如履行结束,那么将一切无效节点和被Step回绝处理的节点再次在一切Step中处理一遍,会产生两种情况:(1)悉数处理结束,万事大吉;(2)还存在无效节点和被回绝处理节点那么只能报错了;
- 这个过程咱们知道,其实Step的顺序是无所谓的,可是咱们仍是应该尊重一下次序,这样在处理过程中或许会快点!
代码完结
public abstract BasicProcessor extends AbstractProcessor{
//搜集无效节点
private List<Element> deferredElement = new ArrayList();
//搜集被Step回绝处理的节点
private Map<Step,List<String>> rejectedElementBySteps = new Map();
List<Step> processingSteps();
//预留一个注解处理完结的办法,承继者用就用,不必就算了
void postOverRound(MultableSet<TypeElement> annotations,RoundEnvironment rounEnv){}
List<Step> steps;
final override void init(ProcessingEnvironment processingEnv){
super.init(processingEnv);
steps = processingSteps();
}
override Set<String> getSupportedAnnotationTypes(){
steps.flatMap { it.annotations() }.toSet();
}
override boolean process(MultableSet<TypeElement> annotations,RoundEnvironment rounEnv){
//注解处理结束
if (roundEnv.processingOver()) {
postOverRound(annotations,rounEnv);
//处理完结后,对剩下的无效节点和回绝节点再次在一切Step中处理一遍
steps.forEach { step ->
//和下面的相同
...
}
}
steps.forEach(step ->{
//前面无效节点,是否运用了当时step的注解,运用了,则返回这些节点信息
Map<String,Set<Element>> previousRoundDeferredElemens = getSetpElementsByAnnotation(step,deferredElement);
//一切前面step回绝处理的节点
Map<String,Set<Element>> stepDeferredElementsByAnnotation = getSetpElementsByAnnotation(step,rejectedElementBySteps被step回绝处理的一切节点);
//增加被Step回绝节点
rejectedElementBySteps.add(step,step.process(当时step注解运用的节点 + 前面回绝节点(该节点运用当时step注解) + 前面无效节点(该节点运用了当时step注解)));
});
}
//依据传递进来的节点调集,查找当时节点是否运用了当时step的注解,筛选当时注解润饰的节点调集
private Map<String,Set<Element>> getSetpElementsByAnnotation(Step step,Set<String> typeElementNames){
if (typeElementNames.isEmpty()) {
return emptyMap()//空
}
Map<String,List<String>> elementsByAnnotation = new HashMap();
List<String> stepAnnotations = step.annotations();
typeElementNames.forEach { typeElement ->
typeElement.getAllAnnotations().map { it.qualifiedName }
.forEach { annotationName
if(stepAnnotations.contains(annotationName)){
List<Element> elements = elementsByAnnotation.getKey(annotationName) ;
if(elements == null){
elements = new ArrayList();
}
elements.add(typeElement);
elementsByAnnotation.put(annotationName,elements);
}
}
}
return elementsByAnnotation;
}
}
扩展:节点树状图
注解润饰的节点,该节点包括其他节点,节点又包括别的节点,形成一个树状结构。demo:一个注解润饰的类中包括包括变量和办法,变量又是一个类,办法或许包括泛型,别的还有办法运用了新的注解等等。怎么表现这种树状结构。
如下所示,能够依据这个图自行往下深化了解:难度不大,花点心思即可。
上图不做说明,有几点有必要阐明:
当时有必要在了解如Message、Filer、Type、Element等类的基础上,即这儿相当于基础类的X系列;
-
为什么运用X系列,个人了解:(1)RoomCompilerProcessing处理Kapt和ksp两种编译办法,X系列相当于一个接口模式,kapt和ksp对接口的完结只需求承继X系列接口即可,详细完结依据自身kapt和ksp实践也无需求;(2)Message、Filter、Type基本类型或许后续会改成X系列(XMessage、XFiler);
kapt和ksp
ksp运用的是KspBasicAnnotationProcessor类;kapt运用的是KotlinBasicAnnotationProcessor。
KspBasicAnnotationProcessor类承继SymbolProcessor;KotlinBasicAnnotationProcessor承继了AbstractProcessor;
代码方面存在细微差别,可是事务逻辑是一致的,咱们这儿只是针对KotlinBasicAnnotationProcessor做简略讲解。
另:当时源码体积首要仍是表现在节点树状图上面,其他代码体量不大。
KotlinBasicAnnotationProcessor源码简介:
- init:生成JavacProcessingEnv目标,该目标中存在很多操作节点和类型代码,节点和类型经过缓存处理,方便二次处理可直接获取(下面会简略介绍缓存用法);
- XMessage的详细完结类是JavacProcessingEnvMessager,XFile的详细完结类是JavacFiler。运用了十分典型的模式:代理模式。
-
getSupportedAnnotationTypes:经过processingSteps办法搜集一切待处理的注解;
-
process:运用CommonProcessorDelegate目标处理注解信息:
- (1)对steps搜集的注解(还有上个过程回绝处理节点和无效节点判别是否运用了当时注解,假如运用了则一并处理)过程遍历处理;
- (2)搜集steps注解过程中回绝处理节点;
- (3)搜集steps注解过程中的无效节点;
- (4)依据RoundEnvironment的processingOver办法判别是否履行结束,假如履行结束,再对steps剩下的回绝节点和无效节点再次遍历steps去处理;
扩展: 缓存
每次处理的节点都会依据节点称号去XTypeElementStore目标中查找,假如存在直接烦,不然依据当时节点名存储在XTypeElementStore目标中。
当时目标单独列出来,原因之一就是这儿kotlin的写法,值得咱们学习(kotlin我不是太熟悉,这个写法我感觉很酷)。当然原因之二是节点存取都绕不开当时存储目标。
总结
这儿对源码的说明十分酸爽。由于结构性十分显着,代码也十分值得咱们去揣摩。
相对注解有进一步了解的,能够去看Dagger源码系列解析
这儿github源码地址,别忘了是room标签。