Android 编译优化探索3

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 编译优化探索3相关的知识,希望对你有一定的参考价值。

前言

本小节讲解编译优化中关于R文件优化与kotlin 1.6.10的问题

我们首先抛出一个来自官方的问题

问题链接

我们发现修改布局中某个控件id,在kotlin 1.6.10中kaptgenerate任务与compile任务会触发全量编译

kotlin 1.6.10 引起KaptGenerate全量问题

要探索这个问题其实要前提了解部分增量源码机制

哪些task的输入可以被增量?
@Incremental与@SkipWhenEmpty标注的输入可以被增量
举例如下:

abstract class MyTask : DefaultTask() 

    //可以被感知增量
    @OutputDirectory
    @Incremental
    abstract fun getOutputDir(): DirectoryProperty?

    //可以被感知增量
    @OutputDirectory
    @SkipWhenEmpty
    abstract fun getOutputDir2(): DirectoryProperty?


    //无法感知
    @InputFiles
    abstract fun getSourceFiles(): ConfigurableFileCollection?

    @TaskAction
    fun action(inputChange: InputChanges) 
        //inputChange 可以感知 getOutputDir getOutputDir2的变化
        //但是如果getSourceFiles改变将触发全量编译
    

Gradle运行时DefaultExecutionStateChangeDetector这个类中会进行增量判断

//DefaultExecutionStateChangeDetector.java
public class DefaultExecutionStateChangeDetector implements ExecutionStateChangeDetector 
	  @Override
    public ExecutionStateChanges detectChanges(AfterPreviousExecutionState lastExecution, BeforeExecutionState thisExecution, Describable executable, IncrementalInputProperties incrementalInputProperties) 
        // Capture changes in execution outcome
        ChangeContainer previousSuccessState = new PreviousSuccessChanges(
            lastExecution.isSuccessful());

        // Capture changes to implementation
        ChangeContainer implementationChanges = new ImplementationChanges(
            lastExecution.getImplementation(), lastExecution.getAdditionalImplementations(),
            thisExecution.getImplementation(), thisExecution.getAdditionalImplementations(),
            executable);

        // Capture non-file input changes
        ChangeContainer inputPropertyChanges = new PropertyChanges(
            lastExecution.getInputProperties(),
            thisExecution.getInputProperties(),
            "Input",
            executable);
        ChangeContainer inputPropertyValueChanges = new InputValueChanges(
            lastExecution.getInputProperties(),
            thisExecution.getInputProperties(),
            executable);

        // Capture input files state
        ChangeContainer inputFilePropertyChanges = new PropertyChanges(
            lastExecution.getInputFileProperties(),
            thisExecution.getInputFileProperties(),
            "Input file",
            executable);
        InputFileChanges nonIncrementalInputFileChanges = incrementalInputProperties.nonIncrementalChanges(
            lastExecution.getInputFileProperties(),
            thisExecution.getInputFileProperties()
        );

        // Capture output files state
        ChangeContainer outputFilePropertyChanges = new PropertyChanges(
            lastExecution.getOutputFileProperties(),
            thisExecution.getOutputFileProperties(),
            "Output",
            executable);
        OutputFileChanges outputFileChanges = new OutputFileChanges(
            lastExecution.getOutputFileProperties(),
            thisExecution.getOutputFileProperties()
        );

        // Collect changes that would trigger a rebuild
        ChangeContainer rebuildTriggeringChanges = errorHandling(executable, new SummarizingChangeContainer(
            previousSuccessState,
            implementationChanges,
            inputPropertyChanges,
            inputPropertyValueChanges,
            outputFilePropertyChanges,
            outputFileChanges,
            inputFilePropertyChanges,
            nonIncrementalInputFileChanges
        ));
        ImmutableList<String> rebuildReasons = collectChanges(rebuildTriggeringChanges);

        if (!rebuildReasons.isEmpty()) 
            return new NonIncrementalDetectedExecutionStateChanges(
                rebuildReasons,
                thisExecution.getInputFileProperties(),
                incrementalInputProperties
            );
         else 
            // Collect incremental input changes
            InputFileChanges directIncrementalInputFileChanges = incrementalInputProperties.incrementalChanges(
                lastExecution.getInputFileProperties(),
                thisExecution.getInputFileProperties()
            );
            InputFileChanges incrementalInputFileChanges = errorHandling(executable, caching(directIncrementalInputFileChanges));
            ImmutableList<String> incrementalInputFileChangeMessages = collectChanges(incrementalInputFileChanges);
            return new IncrementalDetectedExecutionStateChanges(
                incrementalInputFileChangeMessages,
                thisExecution.getInputFileProperties(),
                incrementalInputFileChanges,
                incrementalInputProperties
            );
        
    

其中我们来拆解一下这个类中重要的代码段

        //收集会触发全量编译的原因
        ChangeContainer rebuildTriggeringChanges = errorHandling(executable, new SummarizingChangeContainer(
            previousSuccessState,//之前的运行状态是否成功等
            implementationChanges,//实现类
            inputPropertyChanges,//task输入的属性字段是否改变(这里指代的非文件类型的输入)
            inputPropertyValueChanges,//task输入的属性字段对应的数值是否改变(这里指代的非文件类型的输入)
            outputFilePropertyChanges,//task输出的属性字段是否改变(这里说的输出字段是文件类型)
            outputFileChanges,//task输出的文件是否改变(这里说的输出是文件类型)
            inputFilePropertyChanges,//task 输入文件类型字段是否改变(文件类型字段)
            nonIncrementalInputFileChanges//task 非增量类型的字段与数值是否改变
        ));
        
        //注意上面并没有收集增量类型文件对应的value的改变
        
        //如果上面的比较发现有不一样的状态会返回对应字符串从而触发全量编译
        ImmutableList<String> rebuildReasons = collectChanges(rebuildTriggeringChanges);
        if (!rebuildReasons.isEmpty()) 
            return new NonIncrementalDetectedExecutionStateChanges(
                rebuildReasons,
                thisExecution.getInputFileProperties(),
                incrementalInputProperties
            );
         else 
            // 收集增量信息 这里就是我们常说的产生增量数据的地方
            InputFileChanges directIncrementalInputFileChanges = incrementalInputProperties.incrementalChanges(
                lastExecution.getInputFileProperties(),
                thisExecution.getInputFileProperties()
            );
            InputFileChanges incrementalInputFileChanges = errorHandling(executable, caching(directIncrementalInputFileChanges));
            ImmutableList<String> incrementalInputFileChangeMessages = collectChanges(incrementalInputFileChanges);
            return new IncrementalDetectedExecutionStateChanges(
                incrementalInputFileChangeMessages,
                thisExecution.getInputFileProperties(),
                incrementalInputFileChanges,
                incrementalInputProperties
            );

在kotlin 1.6.10会将R.jar文件改动放入nonIncrementalInputFileChanges中,且对应的字段为$1,如果搜索整个源码会发现找不到这个字段,具体原因是这个字段是匿名动态赋值到KaptGernerate任务中如下代码:

Task myTask =tasks.maybeCreate("hello")
myTask.inputs.file("xxx/R.jar")

解决方案字节码修改相关代码即可,但是我最后发现1.6.20修复了这个问题

R 文件引起的compile全量问题

在AGP 3.6版本后不在生成R.java而是直接生成R.jar这样会加速整个编译过程,但是Gradle当前无法感知内部jar文件变化(答案来自官方论坛)。
在最新版本的kotlin中也没有解决相关问题。

因此笔者自己用了奇淫技巧去规避整个问题。

AGP的GenerateLibraryRFileTask生产出R.jar文件与R.txt.您可以直接修改这个Task还原会AGP之前的版本即可,在赋值给Compile任务的输入即可

但是这个方案实现比较麻烦最后因为没有时间就放弃了。于是我们可以利用R.jarR.txt自己生产一个R.java即可,但是需要中间Task作为桥接:

修正后的基准测试对比图

以上是关于Android 编译优化探索3的主要内容,如果未能解决你的问题,请参考以下文章

Android 编译优化探索2 Hack字节码

Android编译提速黑科技—Wade Plugin

探索 Android 内存优化方法

《Android深度探索》卷1 第八章笔记 1314 王宁

《Android开发艺术探索》之Android性能优化ListView和RecyclerView(十七)

android 7.0 (nougat)的编译优化-ninja