I’ve been looking at a few things about the Android build process. Here is a record of the article to share with you. Today, I’ll share some details about how to compile the code. Android code compilation includes Java and Kotlin code compilation. This article looks at the compilation process of Java code.
The compilation process
Android applications are built using Gradle and the Android Gradle Plugin (AGP). Gradle contains the Java Plugin:Related tasks are maintained in AGPTaskManager
The inside. Compile-related will be increateCompileTask
Execute inside:
private void createCompileTask(@NonNull VariantImpl variant) {
ApkCreationConfig apkCreationConfig = (ApkCreationConfig) variant;
TaskProvider<? extends JavaCompile> javacTask = createJavacTask(variant);
addJavacClassesStream(variant);
setJavaCompilerTask(javacTask, variant);
createPostCompilationTasks(apkCreationConfig);
}
// createJavacTask
fun createJavacTask(creationConfig: ComponentCreationConfig): TaskProvider<out JavaCompile> {
taskFactory.register(JavaPreCompileTask.CreationAction(creationConfig))
val javacTask: TaskProvider<out JavaCompile> = taskFactory.register(
JavaCompileCreationAction(creationConfig,project.pluginManager.hasPlugin(KOTLIN_KAPT_PLUGIN_ID)))
postJavacCreation(creationConfig)
return javacTask
}
Copy the code
Responsible for the Java compiler specific class definition in JavaCompileCreationAction:
class JavaCompileCreationAction(a){
override val type: Class<JavaCompile>
get() = JavaCompile::class.java
}
Copy the code
The JavaCompile compile method is responsible for the specific compilation:
@TaskAction
protected void compile(InputChanges inputs) {
DefaultJavaCompileSpec spec = createSpec();
if(! compileOptions.isIncremental()) { performFullCompilation(spec); }else{ performIncrementalCompilation(inputs, spec); }}Copy the code
CompileOptions includes incremental compilation. When isIncremental is true, incremental is supported. Perform performIncrementalCompilation. Here by the execute method the implementation CompileJavaBuildOperationReportingCompiler SelectiveCompiler the execute method. When SelectiveCompiler runs, it does Java compilation,
CurrentCompilation currentCompilation = new CurrentCompilation(spec, classpathSnapshotProvider);
RecompilationSpec recompilationSpec = recompilationSpecProvider.provideRecompilationSpec(currentCompilation, previousCompilation);
if (recompilationSpec.isFullRebuildNeeded()) {
return rebuildAllCompiler.execute(spec);
}
try {
WorkResult result = recompilationSpecProvider.decorateResult(recompilationSpec, cleaningCompiler.getCompiler().execute(spec));
return result.or(WorkResults.didWork(cleanedOutput));
} finally {}
Copy the code
Here torecompilationSpec
Determine that if some condition breaks incremental compilation, then full compilation is triggered. This is going to be calledcompile
Object created for compilation in:We’ll follow you hereDefaultToolchainJavaCompiler
Methods:This Compiler creates the object to be compiled at execute:
public <T extends CompileSpec> WorkResult execute(T spec) {
final Class<T> specType = (Class<T>) spec.getClass();
return compilerFactory.create(specType).execute(spec);
}
Copy the code
The last object compilerFactory create gets isorg.gradle.api.internal.tasks.compile.JdkJavaCompiler
Create the required Task to run to perform compilation. Actually it’s called herejavac
The compilation.
Incremental compilation
So how does Java determine how to do incremental compilation, and which situations trigger full compilation? We can get the Java compiled task change file with the following code:
val services = (project as? ProjectInternal)? .services services? .let { val store = it.get(ExecutionHistoryStore::class.java)
val detector = it.get(ExecutionStateChangeDetector::class.java)
val lastState = store.load(":${project.name}:${Task_Name_Java}").get(a)// compileDebugJavaWithJavac
}
Copy the code
State stores the previous file, and the default object isDefaultAfterPreviousExecutionState
.inputFileProperties
Generics areorg.gradle.internal.fingerprint.FileCollectionFingerprint
Gradle uses file fingerprints to determine which files have changed. The default implementation class isDefaultCurrentFileCollectionFingerprint
There is one inside this classHash
Object to calculate the specific fingerprint of a file: newHasher
The default is MD5:
public static Hasher newHasher() {
return DEFAULT.newHasher();
}
// default
private static final HashFunction DEFAULT = MD5;
Copy the code
When A method signature of class A changes, A dependent class B will be compiled as well. The result is as follows:
So let’s go back to execute for SelectiveCompiler:
RecompilationSpec recompilationSpec = recompilationSpecProvider.provideRecompilationSpec(currentCompilation, previousCompilation);
Copy the code
In this case, the provider is JavaRecompilationSpecProvider:
@Override
public RecompilationSpec provideRecompilationSpec(CurrentCompilation current, PreviousCompilation previous) {
RecompilationSpec spec = new RecompilationSpec(previous);
SourceFileClassNameConverter sourceFileClassNameConverter = getSourceFileClassNameConverter(previous);
processClasspathChanges(current, previous, spec);
processOtherChanges(current, previous, spec, sourceFileClassNameConverter);
Set<String> typesToReprocess = previous.getTypesToReprocess();
spec.addClassesToProcess(typesToReprocess);
return spec;
}
Copy the code
The most critical step here is to handle classpath changes and file changes:
Changes in the classpath:
File changes: File change logic is similar to classpathHere,dependents
The related method is to get the dependent class file. That is, dealing with the class dependencies of incremental compilation mentioned above. Here the logic is more complicated, do not need to be too in-depth tangled, from the name we can analyze the dependency content of the dependency class file and resource file. It is important to note that Gradle has incremental compilation logic, but there are still some processes that trigger full compilationrebuildAllCompiler
The execution of the:
- Full compilation is triggered when the dependency is all dependent, such as a tripartite library dependency change
if (dependents.isDependencyToAll()) {
spec.setFullRebuildCause(dependents.getDescription());
return;
}
Copy the code
- Similarly, if the modified source code meets this condition, it will also trigger full, such as APT that does not support incremental compilation. You’re going to get a
must have exactly one originating element, but had 0
Copy the code
Of cause. If we use compile-time annotations in our code in a variety of ways, full compilation will be triggered every time we compile. So here we need to separate out the use of APT.
Apt to compile
For management of apt in JdkJavaCompiler can be reflected, in creating JavaCompiler.Com pilationTask, for real before compilation logic layers of packaging.
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options, spec.getClasses(), compilationUnits);
if (compiler instanceof IncrementalCompilationAwareJavaCompiler) {
task = ((IncrementalCompilationAwareJavaCompiler) compiler).makeIncremental(task, result.getSourceClassesMapping(), result.getConstantsAnalysisResult(), new CompilationSourceDirs(spec));
}
task = new AnnotationProcessingCompileTask(task, annotationProcessors, spec.getAnnotationProcessorPath(), result.getAnnotationProcessingResult());
task = new ResourceCleaningCompilationTask(task, fileManager);
Copy the code
These include AnnotationProcessing:
@Override
public Boolean call() {
try {
setupProcessors();
return delegate.call();
} finally{ cleanupProcessors(); }}Copy the code
SetupProcessors create our Processor by reflection and execute it. When our APT supports incremental compilation, we continue to use the corresponding wrapper class:
private Processor decorateForIncrementalProcessing(Processor processor, IncrementalAnnotationProcessorType type, AnnotationProcessorResult processorResult) {
switch (type) {
case ISOLATING:
return new IsolatingProcessor(processor, processorResult);
case AGGREGATING:
return new AggregatingProcessor(processor, processorResult);
case DYNAMIC:
return new DynamicProcessor(processor, processorResult);
default:
return newNonIncrementalProcessor(processor, processorResult); }}Copy the code
In order toIsolationProcessor
For example:Apt inputs are recorded in process according to different strategies for incremental compilation. About these incremental strategies. It can be found in the Gradle documentation:Docs.gradle.org/5.0/usergui…This is what Gradle 5 is starting to support. Here is a brief introduction to these incremental APT:
- Isolatiing searches the elements of each annotation tag independently
- Aggregating Multiple source files to one or more output files
- Can determine day or aggregating
conclusion
Here is the Java compilation of the general process on the analysis of the almost. Many of these things can be further studied, interested friends can study on their own. One of the more useful points is that in daily use of APT, we need to pay attention to apt incremental compilation to prevent the deterioration of project compilation speed caused by too many APT.