前言

有人说细节决定成败,或者说别人注重的是成果,咱们介意的是过程,roomCompilerProcessing源码读起来感觉好难哦,尤其细节处!!!

下面三浅一深,咳咳咳…由浅入深讲解roomCompilerProcessing源码。

下面每个部分,自己认真写,读者认真看。个人建议,能够在深化一点:在了解大框架的基础上,不要疏忽细节部分(提示自己和读者),不然自己想写出高逼格代码照样很难!!!

APT、KAPT和KSP的了解

简介

  1. apt:Annotation Processing Tool
  • 需求apply相应的android-apt插件,比方apply plugin: ‘com.neenbedankt.android-apt’。android gradle插件版别2.2以下运用,2.2发版时宣布不再保护。只支撑 javac编译办法。
  1. annotationProcessor:现已替代apt
  • 无需apply android-apt插件。android gradle插件版别2.2及以上运用。一同支撑javac和jack编译办法;
  1. 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处理;

  1. 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仍是有所差异的:

  1. AbstractProcessor中的处理顺序是:先init,再getSupportedAnnotationTypes当时需求处理的注解;然后调用process处理注解;

  2. SymbolProcessor:先直接调用process处理;假如完结调用finish,不然调用onError做反常处理;

  3. AbstractProcessor判别是否完结悉数处理的办法:在process办法有个参数RoundEnvironment,该参数提供processingOver办法判别当时处理是否结束;

  4. SymbolProcessor假如也想要init初始化,那么能够在process办法中调用;

  5. AbstractProcessor的运用时承继当时类而且运用@AutoService(Processor::class)注解润饰该类;SymbolProcessor是承继该类,运用SymbolProcessorProvider的create实例化运用该ksp处理注解的类;

  6. SymbolProcessor办法中没有getSupportedAnnotationTypes办法(支撑处理的注解),所以能够直接在process办法中界说需求处理的注解;

以上就相当于一个demo的讲解了,由于个人感觉不难,一切没这方面知识的读者能够自行去了解。

roomCompilerProcessing架构按部就班

引子

假如按照以上demo的用法,咱们以AbstractProcessor的运用为例:

假如需求处理的注解十分多,怎么办;而且每个(或者每一类)注解又有自己的意义,咱们不或许一切的注解放一同,在getSupportedAnnotationTypes中一次性表达悉数需求处理的悉数注解,再在process一次性处理;

roomCompilerProcessing的诞生能很好处理这个问题,将不同的注解分类而且对当时分类处理,那么怎么做呢

甄别注解,而且对不同类型注解处理

以下手敲版,不要过于介意代码,首要是代码中溢出的杀气…咳咳咳,思维才是最重要的。

  1. 界说step接口用于搜集某一类注解而且对该类注解处理:

     public interface Step{
     	//搜集当时step处理的注解
     	public Set<String> getAnnotations();
     	//处理当时搜集到的注解调集的详细事务逻辑,返回当时被回绝处理的节点
     	public List<TypeElement> process(ProcessingEnv env,Set<Element>);	
     }
    
  2. 界说一个抽象类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();
     		});
     	}
     }
    
  3. 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;
     	}
     }
    
  4. AbsractProcessor实例:

     public RoomProcessor extends BasicProcessor {
     	override List<Step> processingSteps(){
     		listof(StepInstall());
     	}
     	override SourceVersion getSupportedSourceVersion(){
     		return SourceVersion.latest();
     	}
     }
    

写的不是很好,可是那种高逼格意境表达出来了!!!

扩展:推迟或回绝处理的节点

什么情况下注解处理的节点需求推迟处理或回绝处理呢?

  1. 节点尚没有生成,又称为无效节点,需求延时处理;

  2. 在Step处理过程中,依据事务需求,被回绝处理的节点;

揍一顿比方给大家看下:

  1. @Annotation1注解润饰的类,经过Step1处理生成了@Annotation2润饰的类。Step2刚好能够处理@Annotation2注解(假如这个@Annotation2润饰的节点是一个特定类,很重要,比方就叫SpecialCoolMan,在未生成这个SpecialCoolMan的情况下就属于无效节点)。

  2. 而又由于每个原因(Step2的process里有个事务场景,或许感觉这个@Annotation2润饰的当时类存在private润饰的变量,感觉它太特么小气,搞的我没面子,那我就不给他处理了,这个@Annotation2润饰的类就被叫做回绝节点),当时Step2回绝处理这个@Annotation2润饰的类。

对以上场景了解一番:

  1. 奔着尊重代码原则(那假如不尊重Step2在Step1前履行是否完全没问题呢,往下看有答案):Step1仍是先一步履行,再去履行Step2;

  2. BasicProcessor类中有两个变量:(1)搜集一切Step中被回绝处理的节点;(2)搜集一切Step中的无效节点;

  3. BasicProcessor process办法会顺次履行Step process办法:(1)处理当时注解的事务逻辑;(2)之前堆集的被Step回绝的节点和无效节点是否运用了当时Step的注解,假如是则处理;再搜集当时Step被回绝的节点和无效节点。直到一切Step都处理结束!

  4. 咱们在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源码简介:

  1. init:生成JavacProcessingEnv目标,该目标中存在很多操作节点和类型代码,节点和类型经过缓存处理,方便二次处理可直接获取(下面会简略介绍缓存用法);
  • XMessage的详细完结类是JavacProcessingEnvMessager,XFile的详细完结类是JavacFiler。运用了十分典型的模式:代理模式。
  1. getSupportedAnnotationTypes:经过processingSteps办法搜集一切待处理的注解;

  2. process:运用CommonProcessorDelegate目标处理注解信息:

  • (1)对steps搜集的注解(还有上个过程回绝处理节点和无效节点判别是否运用了当时注解,假如运用了则一并处理)过程遍历处理;
  • (2)搜集steps注解过程中回绝处理节点;
  • (3)搜集steps注解过程中的无效节点;
  • (4)依据RoundEnvironment的processingOver办法判别是否履行结束,假如履行结束,再对steps剩下的回绝节点和无效节点再次遍历steps去处理;

扩展: 缓存

每次处理的节点都会依据节点称号去XTypeElementStore目标中查找,假如存在直接烦,不然依据当时节点名存储在XTypeElementStore目标中。

当时目标单独列出来,原因之一就是这儿kotlin的写法,值得咱们学习(kotlin我不是太熟悉,这个写法我感觉很酷)。当然原因之二是节点存取都绕不开当时存储目标。

总结

这儿对源码的说明十分酸爽。由于结构性十分显着,代码也十分值得咱们去揣摩。

相对注解有进一步了解的,能够去看Dagger源码系列解析

这儿github源码地址,别忘了是room标签。