加紧apk的打造速度,如何把编译时间从130秒降到17秒

本文已授权微信公众号:鸿洋(hongyangAndroid)原创头阵

商厦的门类代码相比较多,每回调试改动java文件后要将近2分钟才能跑起来,实在受不住。在网上找了一大堆配置参数也不曾很明显的效能,
尝试使用instant
run效果也不怎么样,然后又尝试利用freeline编译速度仍可以只是不安宁,每便失败后全量编译很费用时间,既然没有好的方案就协调尝试做。

项目地址:
https://github.com/typ0520/fastdex

注: 本文对gradle task做的验证都建立在关闭instant run的前提下
注: 本文所有的代码、gradle任务名、职务输出路径、全体施用debug那几个buildType作表达

优化构建速度首先必要找到这个环节造成创设速度这么慢,把下部的代码放进app/build.gradle里把时间成本超过50ms的职责时间打印出来

 public class BuildTimeListener implements TaskExecutionListener, BuildListener {
    private Clock clock
    private times = []

    @Override
    void beforeExecute(Task task) {
        clock = new org.gradle.util.Clock()
    }

    @Override
    void afterExecute(Task task, TaskState taskState) {
        def ms = clock.timeInMs
        times.add([ms, task.path])

        //task.project.logger.warn "${task.path} spend ${ms}ms"
    }

    @Override
    void buildFinished(BuildResult result) {
        println "Task spend time:"
        for (time in times) {
            if (time[0] >= 50) {
                printf "%7sms  %s\n", time
            }
        }
    }

    ......
}

project.gradle.addListener(new BuildTimeListener())

执行./gradlew assembleDebug,经过漫长的等候得到以下输出

Total time: 1 mins 39.566 secs
Task spend time:
     69ms  :app:prepareComAndroidSupportAnimatedVectorDrawable2340Library
    448ms  :app:prepareComAndroidSupportAppcompatV72340Library
     57ms  :app:prepareComAndroidSupportDesign2340Library
     55ms  :app:prepareComAndroidSupportSupportV42340Library
     84ms  :app:prepareComFacebookFrescoImagepipeline110Library
     69ms  :app:prepareComSquareupLeakcanaryLeakcanaryAndroid14Beta2Library
     60ms  :app:prepareOrgXutilsXutils3336Library
     68ms  :app:compileDebugRenderscript
    265ms  :app:processDebugManifest
   1517ms  :app:mergeDebugResources
    766ms  :app:processDebugResources
   2897ms  :app:compileDebugJavaWithJavac
   3117ms  :app:transformClassesWithJarMergingForDebug
   7899ms  :app:transformClassesWithMultidexlistForDebug
  65327ms  :app:transformClassesWithDexForDebug
    151ms  :app:transformNative_libsWithMergeJniLibsForDebug
    442ms  :app:transformResourcesWithMergeJavaResForDebug
   2616ms  :app:packageDebug
    123ms  :app:zipalignDebug

从上边的输出可以发现总的营造时间为100秒左右(下边的出口不是依据真的的履行顺序输出的),transformClassesWithDexForDebug任务是最慢的损耗了65秒,它就是大家要求尊敬优化的任务,首先讲下营造进程中至关主要职分的成效,方便精晓后边的hook点

mergeDebugResources任务的效果是解压所有的aar包输出到app/build/intermediates/exploded-aar,并且把持有的资源文件合并到app/build/intermediates/res/merged/debug目录里

processDebugManifest义务是把所有aar包里的AndroidManifest.xml中的节点,合并到品种的AndroidManifest.xml中,并基于app/build.gradle中当前buildType的manifestPlaceholders配置内容替换manifest文件中的占位符,最终输出到app/build/intermediates/manifests/full/debug/AndroidManifest.xml

processDebugResources的作用

  • 1、调用aapt生成项目和所有aar信赖的R.java,输出到app/build/generated/source/r/debug目录
  • 2、生成资源索引文件app/build/intermediates/res/resources-debug.ap_
  • 3、把符号表输出到app/build/intermediates/symbols/debug/R.txt

compileDebugJavaWithJavac本条职分是用来把java文件编译成class文件,输出的路线是app/build/intermediates/classes/debug
编译的输入目录有

  • 1、项目源码目录,默许路径是app/src/main/java,可以由此sourceSets的dsl配置,允许有多个(打印project.android.sourceSets.main.java.srcDirs可以查阅当前具有的源码路径,具体计划可以参见android-doc
  • 2、app/build/generated/source/aidl
  • 3、app/build/generated/source/buildConfig
  • 4、app/build/generated/source/apt(继承javax.annotation.processing.AbstractProcessor做动态代码生成的有些库,输出在那一个目录,具体能够参照Butterknife

    Tinker)的代码

transformClassesWithJarMergingForDebug的效益是把compileDebugJavaWithJavac职务的输出app/build/intermediates/classes/debug,和app/build/intermediates/exploded-aar中有所的classes.jar和libs里的jar包作为输入,合并起来输出到app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar,大家在支付中凭借第三方库的时候偶然报duplicate
entry:xxx 的谬误,就是因为在联合的长河中在差异jar包里发现了平等路线的类

transformClassesWithMultidexlistForDebug以此职责花费的时日也很长将近8秒,它有多个效率

  • 1、扫描项目标AndroidManifest.xml文件和分析类之间的信赖关系,统计出那么些类必须放在第二个dex里面,最终把分析的结果写到app/build/intermediates/multi-dex/debug/maindexlist.txt文件之中
  • 2、生成混淆配置项输出到app/build/intermediates/multi-dex/debug/manifest_keep.txt文件里

品类里的代码入口是manifest中application节点的属性android.name配置的继续自Application的类,在android5.0原先的版本系统只会加载一个dex(classes.dex),classes2.dex
…….classesN.dex
一般是运用android.support.multidex.MultiDex加载的,所以要是输入的Application类不在classes.dex里5.0之下肯定会挂掉,此外当入口Application依赖的类不在classes.dex时伊始化的时候也会因为类找不到而挂掉,还有若是混淆的时候类名变掉了也会因为对应持续而挂掉,综上所述就是以此职务的功能

transformClassesWithDexForDebug以此任务的成效是把带有所有class文件的jar包转换为dex,class文件愈多变换的越慢
输入的jar包路径是app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar
输出dex的目录是build/intermediates/transforms/dex/debug/folders/1000/1f/main

***小心编写gradle插件时只要急需采纳方面这几个途径不要硬编码的方式写死,最好从Android
gradle api中去得到路径,避免将来暴发变化

组合方面的这一个信息根本需求优化的是transformClassesWithDexForDebug本条任务,我的思绪是率先次全量打包举办完transformClassesWithDexForDebug职务后把变化的dex缓存下来,并且在实践这一个任务前对近期具备的java源文件做快照,未来补丁打包的时候经过当前持有的java文件消息和事先的快照做相比,找出转变的java文件进而得到这些class文件发生变化,然后把app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar中并未生成的class移除掉,仅把变化class送去生成dex,然后拔取一种热修复方案把这几个dex当做补丁dex加载进来,有思路了前面就是抢占各样技术点

==============================

怎样得到transformClassesWithDexForDebug任务执行前后的生命周期

参考了Tinker项目标代码,找到上面的完成

public class ImmutableDexTransform extends Transform {
    Project project
    DexTransform dexTransform
    def variant

    ......

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, IOException, InterruptedException {
        def outputProvider = transformInvocation.getOutputProvider()
        //dex的输出目录
        File outputDir = outputProvider.getContentLocation("main", dexTransform.getOutputTypes(), dexTransform.getScopes(), Format.DIRECTORY);
        if (outputDir.exists()) {
            outputDir.delete()
        }
        println("===执行transform前清空dex输出目录: ${project.projectDir.toPath().relativize(outputDir.toPath())}")
        dexTransform.transform(transformInvocation)
        if (outputDir.exists()) {
            println("===执行transform后dex输出目录不是空的: ${project.projectDir.toPath().relativize(outputDir.toPath())}")
            outputDir.listFiles().each {
                println("===执行transform后: ${it.name}")
            }
        }
    }
}

project.getGradle().getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
    @Override
    public void graphPopulated(TaskExecutionGraph taskGraph) {
        for (Task task : taskGraph.getAllTasks()) {
            if (task instanceof TransformTask && task.name.toLowerCase().contains(variant.name.toLowerCase())) {

                if (((TransformTask) task).getTransform() instanceof DexTransform && !(((TransformTask) task).getTransform() instanceof ImmutableDexTransform)) {
                    project.logger.warn("find dex transform. transform class: " + task.transform.getClass() + " . task name: " + task.name)

                    DexTransform dexTransform = task.transform
                    ImmutableDexTransform hookDexTransform = new ImmutableDexTransform(project,
                            variant, dexTransform)
                    project.logger.info("variant name: " + variant.name)

                    Field field = TransformTask.class.getDeclaredField("transform")
                    field.setAccessible(true)
                    field.set(task, hookDexTransform)
                    project.logger.warn("transform class after hook: " + task.transform.getClass())
                    break;
                }
            }
        }
    }
});

把地点的代码放进app/build.gradle执行./gradlew assembleDebug

:app:transformClassesWithMultidexlistForDebug
ProGuard, version 5.2.1
Reading program jar [/Users/tong/Projects/fastdex/app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar]
Reading library jar [/Users/tong/Applications/android-sdk-macosx/build-tools/23.0.1/lib/shrinkedAndroid.jar]
Preparing output jar [/Users/tong/Projects/fastdex/app/build/intermediates/multi-dex/debug/componentClasses.jar]
  Copying resources from program jar [/Users/tong/Projects/fastdex/app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar]
:app:transformClassesWithDexForDebug
===执行transform前清空dex输出目录: build/intermediates/transforms/dex/debug/folders/1000/1f/main
......
===执行transform后dex输出目录不是空的: build/intermediates/transforms/dex/debug/folders/1000/1f/main
===执行transform后: classes.dex

从地点的日志输出表明这几个hook点是一蹴而就的,在全量打包时实施transform前可以对java源码做快照,执行完未来把dex缓存下来;在补丁打包举办transform之前相比较快照移除没有成形的class,执行完未来合并缓存的dex放进dex输出目录

==============================

什么做快照与对待快照并得到变化的class列表

施行下边的代码可以收获具有的体系源码目录

project.android.sourceSets.main.java.srcDirs.each { srcDir->
    println("==srcDir: ${srcDir}")
}

sample工程尚未配置sourceSets,由此输出的是app/src/main/java

给源码目录做快照,直接通过文件复制的办法,把拥有的srcDir目录下的java文件复制到快照目录下(那里有个坑,不要采纳project.copy
{}它会使文件的lastModified值发生变化,直接运用流copy并且要用源文件的lastModified覆盖目标文件的lastModified)

由此java文件的尺寸和上次修改时间七个元素相比可以摸清同一个文本是或不是暴发变化,通过快照目录没有某个文件而当前目录有某个文件可以查出扩张了文件,通过快照目录有某个文件然而当前目录没有得以摸清删除文件(为了成效能够不处理删除,仅造成缓存里有好几用不到的类而已)
举个例子来说假若项目源码的路径为/Users/tong/fastdex/app/src/main/java,做快照时把这么些目录复制到/Users/tong/fastdex/app/build/fastdex/snapshoot下,当前快照里的文本树为

└── com
    └── dx168
        └── fastdex
            └── sample
                ├── CustomView.java
                ├── MainActivity.java
                └── SampleApplication.java

假如当前源码路径的内容暴发变化,当前的文书树为

└── com
    └── dx168
        └── fastdex
            └── sample
                ├── CustomView.java
                ├── MainActivity.java(内容已经被修改)
                ├── New.java
                └── SampleApplication.java

由此文件遍历相比较可以赢得那几个变化的相对路径列表

  • com/dx168/fastdex/sample/MainActivity.java
  • com/dx168/fastdex/sample/New.java

透过那个列表进而能够查出变化的class有

  • com/dx168/fastdex/sample/MainActivity.class
  • com/dx168/fastdex/sample/New.class

不过java文件编译的时候假使有中间类还会有任何的局地class输出,比如拿R文件做下编译,它的编译输出如下

➜  sample git:(master) ls
R.java
➜  sample git:(master) javac R.java 
➜  sample git:(master) ls
R$attr.class      R$dimen.class     R$id.class        R$layout.class    R$string.class    R$styleable.class R.java
R$color.class     R$drawable.class  R$integer.class   R$mipmap.class    R$style.class     R.class
➜  sample git:(master) 

除此以外借使采用了butterknife,还会生成binder类,比如编译MainActivity.java时生成了
com/dx168/fastdex/sample/MainActivity$$ViewBinder.class

组合方面几点可以得到具有变化class的匹配形式

  • com/dx168/fastdex/sample/MainActivity.class
  • com/dx168/fastdex/sample/MainActivity$*.class
  • com/dx168/fastdex/sample/New.class
  • com/dx168/fastdex/sample/New$*.class

有了地点的分外方式就可以在补丁打包举行transform前把app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar中绝非成形的class全部移除掉

project.copy {
    from project.zipTree(combinedJar)
        for (String pattern : patterns) {
            include pattern
        }
    }
    into tmpDir
}
project.ant.zip(baseDir: tmpDir, destFile: patchJar)

下一场就可以利用patchJar作为输入jar生成补丁dex

注:
那种映射方案一经打开了歪曲就对应不上了,需求分析混淆将来暴发的mapping文件才能解决,但是大家也尚未必要在打开混淆的buildType下做开发支出调试,所以暂时可以不做这些事情

==============================
有了补丁dex,就足以选拔一种热修复方案把补丁dex加载进来,那里方案有某些种,为了简单直接接纳android.support.multidex.MultiDex以dex插桩的法门来加载,只须求把dex根据google标准(classes.dex、classes2.dex、classesN.dex)排列好就行了,那里有七个技术点

鉴于patch.dex和缓存下来dex里面有再一次的类,当加载引用了再也类的类时会造成pre-verify的失实,具体请参见QQ空间社团写的安卓App热补丁动态修复技术介绍
,那篇文章详细分析了造成pre-verify荒谬的原故,文章里给的缓解方案是往所有引用被修复类的类中插入一段代码,并且被插入的那段代码所在的类的dex必须是一个独立的dex,这么些dex大家先行准备好,叫做fastdex-runtime.dex,它的代码结构是

└── com
    └── dx168
        └── fastdex
            └── runtime
                ├── FastdexApplication.java
                ├── antilazyload
                │   └── AntilazyLoad.java
                └── multidex
                    ├── MultiDex.java
                    ├── MultiDexApplication.java
                    ├── MultiDexExtractor.java
                    └── ZipUtil.java

AntilazyLoad.java就是在注入时被引述的类
MultiDex.java是用来加载classes2.dex –
classesN.dex的包,为了防止项目尚未爱惜MultiDex,所以把MultiDex的代码copy到了俺们的package下
法斯特dexApplication.java的功能前边在说

组合大家的档次需要在全量打包前把app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar中所有的序列代码的class全部动态插入代码(第三方库由于不在大家的修补范围内之所以为了功效忽略掉),具体的做法是往所有的构造方法中添加对com.dx168.fastdex.runtime.antilazyload.AntilazyLoad的依赖,如上面的代码所示

//source class:
public class MainActivity {
}

==>

//dest class:
import com.dx168.fastdex.runtime.antilazyload.AntilazyLoad;
public class MainActivity {
    public MainActivity() {
        System.out.println(Antilazyload.str);
    }
}

动态往class文件中插入代码应用的是asm,我把做测试的时候找到的一些唇揭齿寒材料和代码都放到了github上边点我翻看,代码相比五只贴出来一部分,具体请查看ClassInject.groovy

 private static class MyClassVisitor extends ClassVisitor {
    public MyClassVisitor(ClassVisitor classVisitor) {
        super(Opcodes.ASM5, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access,
                                     String name,
                                     String desc,
                                     String signature,
                                     String[] exceptions) {
        //判断是否是构造方法
        if ("<init>".equals(name)) {
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            MethodVisitor newMethod = new AsmMethodVisit(mv);
            return newMethod;
        } else {
            return super.visitMethod(access, name, desc, signature, exceptions);
        }
    }
}

static class AsmMethodVisit extends MethodVisitor {
    public AsmMethodVisit(MethodVisitor mv) {
        super(Opcodes.ASM5, mv);
    }

    @Override
    public void visitInsn(int opcode) {
        if (opcode == Opcodes.RETURN) {
            //访问java/lang/System的静态常量out
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            //访问AntilazyLoad的静态变量
            mv.visitFieldInsn(GETSTATIC, "com/dx168/fastdex/runtime/antilazyload/AntilazyLoad", "str", "Ljava/lang/String;");
            //调用out的println打印AntilazyLoad.str的值
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }
        super.visitInsn(opcode);
    }
}

===============
拍卖完pre-verify难题,接下去又并发坑了,当补丁dex打好后只要缓存的dex有八个(classes.dex
classes2.dex),那么合并dex后的逐一就是
fastdex-runtime.dex 、patch.dex、classes.dex 、classes2.dex
(patch.dex必须放在缓存的dex以前才能被修复)

fastdex-runtime.dex  => classes.dex
patch.dex            => classes2.dex
classes.dex          => classes3.dex
classes2.dex         => classes4.dex

在讲解transformClassesWithMultidexlistForDebug义务时有说经过序入口Application的题材,假使patch.dex中不含有入口Application,apk启动的时候一定会报类找不到的谬误,那么怎么解决那个难点吗

    1. 先是个方案:
      transformClassesWithMultidexlistForDebug职分中输出的maindexlist.txt中享有的class都加入patch.dex的扭转
    1. 第两种方案:
      对项目标入口Application做代理,并把那么些代理类放在第二个dex里面,项目的dex按照顺序放在前边

首先种方案方案由于必须让maindexlist.txt中大批量的类加入了补丁的变迁,与事先尽量裁减class文件插足dex生成的研商是相冲突的,效能相对于第四个方案比较低,其余一个缘故是无能为力担保项目标Application中行使了MultiDex;

其次种方案并未上述难题,不过假若项目代码中有利用getApplication()做强转就会出难点(参考issue#2),instant
run也会有同等的难题,它的做法是hook系统的api运行期把Application还原回来,所以强转就不会有标题了,请参见MonkeyPatcher.java(必要翻墙才能打开,要是看不住就参照FastdexApplication.java的monkeyPatchApplication方法)

归咎最后摘取了第三种方案以下是fastdex-runtime.dex中代理Application的代码

public class FastdexApplication extends Application {
    public static final String LOG_TAG = "Fastdex";
    private Application realApplication;

    //从manifest文件的meta_data中获取真正的项目Application类
    private String getOriginApplicationName(Context context) {
        ApplicationInfo appInfo = null;
        try {
            appInfo = context.getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        String msg = appInfo.metaData.getString("FASTDEX_ORIGIN_APPLICATION_CLASSNAME");
        return msg;
    }

    private void createRealApplication(Context context) {
        String applicationClass = getOriginApplicationName(context);
        if (applicationClass != null) {
            Log.d(LOG_TAG, new StringBuilder().append("About to create real application of class name = ").append(applicationClass).toString());

            try {
                Class realClass = Class.forName(applicationClass);
                Constructor constructor = realClass.getConstructor(new Class[0]);
                this.realApplication = ((Application) constructor.newInstance(new Object[0]));
                Log.v(LOG_TAG, new StringBuilder().append("Created real app instance successfully :").append(this.realApplication).toString());
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        } else {
            this.realApplication = new Application();
        }
    }

    protected void attachBaseContext(Context context) {
        super.attachBaseContext(context);
        MultiDex.install(context);
        createRealApplication(context);

        if (this.realApplication != null)
            try {
                Method attachBaseContext = ContextWrapper.class
                        .getDeclaredMethod("attachBaseContext", new Class[]{Context.class});

                attachBaseContext.setAccessible(true);
                attachBaseContext.invoke(this.realApplication, new Object[]{context});
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
    }

    public void onCreate() {
        super.onCreate();

        if (this.realApplication != null) {
            this.realApplication.onCreate();
        }
    }
    ......
}

根据从前的义务表明生成manifest文件的天职是processDebugManifest,我们只要求在那么些职务履行完之后做拍卖,创设一个兑现类为FastdexManifestTask的职分,宗旨代码如下

def ns = new Namespace("http://schemas.android.com/apk/res/android", "android")
def xml = new XmlParser().parse(new InputStreamReader(new FileInputStream(manifestPath), "utf-8"))
def application = xml.application[0]
if (application) {
    QName nameAttr = new QName("http://schemas.android.com/apk/res/android", 'name', 'android');
    def applicationName = application.attribute(nameAttr)
    if (applicationName == null || applicationName.isEmpty()) {
        applicationName = "android.app.Application"
    }
    //替换application的android.name节点
    application.attributes().put(nameAttr, "com.dx168.fastdex.runtime.FastdexApplication")
    def metaDataTags = application['meta-data']
    // remove any old FASTDEX_ORIGIN_APPLICATION_CLASSNAME elements
    def originApplicationName = metaDataTags.findAll {
        it.attributes()[ns.name].equals(FASTDEX_ORIGIN_APPLICATION_CLASSNAME)
    }.each {
        it.parent().remove(it)
    }
    // Add the new FASTDEX_ORIGIN_APPLICATION_CLASSNAME element
    //把原来的Application写入到meta-data中
    application.appendNode('meta-data', [(ns.name): FASTDEX_ORIGIN_APPLICATION_CLASSNAME, (ns.value): applicationName])
    // Write the manifest file
    def printer = new XmlNodePrinter(new PrintWriter(manifestPath, "utf-8"))
    printer.preserveWhitespace = true
    printer.print(xml)
}
File manifestFile = new File(manifestPath)
if (manifestFile.exists()) {
    File buildDir = FastdexUtils.getBuildDir(project,variantName)
    FileUtils.copyFileUsingStream(manifestFile, new File(buildDir,MANIFEST_XML))
    project.logger.error("fastdex gen AndroidManifest.xml in ${MANIFEST_XML}")
}

应用上面的代码把这一个职分加进去并有限襄助在processDebugManifest任务履行落成后执行

project.afterEvaluate {
    android.applicationVariants.all { variant ->
        def variantOutput = variant.outputs.first()
        def variantName = variant.name.capitalize()

        //替换项目的Application为com.dx168.fastdex.runtime.FastdexApplication
        FastdexManifestTask manifestTask = project.tasks.create("fastdexProcess${variantName}Manifest", FastdexManifestTask)
        manifestTask.manifestPath = variantOutput.processManifest.manifestOutputFile
        manifestTask.variantName = variantName
        manifestTask.mustRunAfter variantOutput.processManifest

        variantOutput.processResources.dependsOn manifestTask
    }
}

处理完以后manifest文件application节点android.name属性的值就成为了com.dx168.fastdex.runtime.FastdexApplication,并且把原本项目标Application的名字写入到meta-data中,用来运行期给法斯特dexApplication去读取

<meta-data android:name="FASTDEX_ORIGIN_APPLICATION_CLASSNAME" android:value="com.dx168.fastdex.sample.SampleApplication"/>

==============================

开发完以上成效后做下边的一次打包做时间相比较(其实只做几遍并不是太标准,做几十次测试取时间的平均值那样才最准)
  • 1、删除build目录第几次全量打包(不开启fastdex)

    BUILD SUCCESSFUL
    
      Total time: 1 mins 46.678 secs
      Task spend time:
        437ms  :app:prepareComAndroidSupportAppcompatV72340Library
         50ms  :app:prepareComAndroidSupportDesign2340Library
         66ms  :app:prepareComAndroidSupportSupportV42340Library
         75ms  :app:prepareComFacebookFrescoImagepipeline110Library
         56ms  :app:prepareOrgXutilsXutils3336Library
        870ms  :app:mergeDebugResources
         93ms  :app:processDebugManifest
        777ms  :app:processDebugResources
       1200ms  :app:compileDebugJavaWithJavac
       3643ms  :app:transformClassesWithJarMergingForDebug
       5520ms  :app:transformClassesWithMultidexlistForDebug
      61770ms  :app:transformClassesWithDexForDebug
         99ms  :app:transformNative_libsWithMergeJniLibsForDebug
        332ms  :app:transformResourcesWithMergeJavaResForDebug
       2083ms  :app:packageDebug
        202ms  :app:zipalignDebug
    
  • 2、删除build目录第五回全量打包(开启fastdex)

    BUILD SUCCESSFUL
    
      Total time: 1 mins 57.764 secs
      Task spend time:
        106ms  :app:prepareComAndroidSupportAnimatedVectorDrawable2340Library
        107ms  :runtime:transformClassesAndResourcesWithSyncLibJarsForDebug
        416ms  :app:prepareComAndroidSupportAppcompatV72340Library
         67ms  :app:prepareComAndroidSupportSupportV42340Library
         76ms  :app:prepareComFacebookFrescoImagepipeline110Library
         53ms  :app:prepareOrgXutilsXutils3336Library
        111ms  :app:processDebugManifest
        929ms  :app:mergeDebugResources
        697ms  :app:processDebugResources
       1227ms  :app:compileDebugJavaWithJavac
       3237ms  :app:transformClassesWithJarMergingForDebug
       6225ms  :app:transformClassesWithMultidexlistForDebug
      78990ms  :app:transformClassesWithDexForDebug
        122ms  :app:transformNative_libsWithMergeJniLibsForDebug
        379ms  :app:transformResourcesWithMergeJavaResForDebug
       2050ms  :app:packageDebug
         77ms  :app:zipalignDebug
    
  • 3、在拉开fastdex首回全量打包完结后,关掉fastdex修改sample工程的MainActivity.java

    BUILD SUCCESSFUL
    
    Total time: 1 mins 05.394 secs
    Task spend time:
       52ms  :app:mergeDebugResources
     2583ms  :app:compileDebugJavaWithJavac
    60718ms  :app:transformClassesWithDexForDebug
      101ms  :app:transformNative_libsWithMergeJniLibsForDebug
      369ms  :app:transformResourcesWithMergeJavaResForDebug
     2057ms  :app:packageDebug
       75ms  :app:zipalignDebug
    
  • 4、在拉开fastdex第五次全量打包达成后,如故开启fastdex修改sample工程的MainActivity.java

    BUILD SUCCESSFUL
    
      Total time: 16.5 secs
      Task spend time:
        142ms  :app:processDebugManifest
       1339ms  :app:compileDebugJavaWithJavac
       3291ms  :app:transformClassesWithJarMergingForDebug
       4865ms  :app:transformClassesWithMultidexlistForDebug
       1005ms  :app:transformClassesWithDexForDebug
       2112ms  :app:packageDebug
         76ms  :app:zipalignDebug
    
打包编号 总时间 transform时间
1 1 mins 46.678s 61770 ms
2 1 mins 57.764s 78990 ms
3 1 mins 05.394s 60718 ms
4 16.5s 1005 ms

经过1和2相对而言发现,开启fastdex进行第四回全量的打包时的时辰开销比不开启多了10秒左右,那个重中之重是流入代码和IO上的付出

因而2和3相比发现,开启fastdex进行补丁打包时的时光开支比不开启快了60秒左右,那就是期待已久的营造速度啊\_

==============================
刚激动一会就尼玛报了一个荒谬,当修改activity_main.xml时往里面增加一个控件

<TextView
    android:id="@+id/tv2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

打出去的包启动的时候就平昔crash掉了

Caused by: java.lang.IllegalStateException: 
Required view 'end_padder' with ID 2131493007 for field 'tv1' was not found.
If this view is optional add '@Nullable' (fields) or '@Optional' (methods) annotation.
     at butterknife.internal.Finder.findRequiredView(Finder.java:51)
     at com.dx168.fastdex.sample.CustomView$$ViewBinder.bind(CustomView$$ViewBinder.java:17)
     at com.dx168.fastdex.sample.CustomView$$ViewBinder.bind(CustomView$$ViewBinder.java:12)
     at butterknife.ButterKnife.bind(ButterKnife.java:187)
     at butterknife.ButterKnife.bind(ButterKnife.java:133) 
     at com.dx168.fastdex.sample.CustomView.<init>(CustomView.java:20) 
     ......
     at dalvik.system.NativeStart.main(Native Method) 

错误信息里的意思是为CustomView的tv1字段,寻找id=2131493007的view前卫未找到,先反编译报错的apk,�找到报错的地点CustomView$$ViewBinder.bind

public class CustomView$$ViewBinder<T extends CustomView>
        implements ViewBinder<T>
{
    public CustomView$$ViewBinder()
    {
        System.out.println(AntilazyLoad.str);
    }

    public Unbinder bind(Finder paramFinder, T paramT, Object paramObject)
    {
        InnerUnbinder localInnerUnbinder = createUnbinder(paramT);
        paramT.tv1 = ((TextView)paramFinder.castView((View)paramFinder
                .findRequiredView(paramObject, 2131493007, "field 'tv1'"), 2131493007, "field 'tv1'"));
        paramT.tv3 = ((TextView)paramFinder.castView((View)paramFinder
                .findRequiredView(paramObject, 2131493008, "field 'tv3'"), 2131493008, "field 'tv3'"));
        return localInnerUnbinder;
    }
    ......
}

CustomView$$ViewBinder那个类是ButterKnife动态生成的,那个值的来源于是CustomView的tv1字段上面的诠释,CustomView.class反编译后如下

public class CustomView extends LinearLayout 
{
    @BindView(2131493007)
    TextView tv1;
    @BindView(2131493008)
    TextView tv3;

    public CustomView(Context paramContext, AttributeSet paramAttributeSet)
    {
        super(paramContext, paramAttributeSet);
        inflate(paramContext, 2130968632, this);
        ButterKnife.bind(this);
        this.tv3.setText(2131099697);
        MainActivity.aa();
        System.out.println(AntilazyLoad.str);
    }
}

观看那里是否认为意外,CustomView的源码明明是

public class CustomView extends LinearLayout {
    @BindView(R.id.tv1)  TextView tv1;
    @BindView(R.id.tv3)  TextView tv3;

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        inflate(context,R.layout.view_custom,this);
        ButterKnife.bind(this);

        tv3.setText(R.string.s3);
        MainActivity.aa();
    }
}

�在编译将来R.id.tv1怎么就改为数字2131493007了啊,原因是java编译器做了一个属性优化,如若发现源文件引用的是一个包括final描述符的常量,会直接做值copy

反编译最终几次编译成功时的R.class结果如下(
app/build/intermediates/classes/debug/com/dx168/fastdex/sample/R.class)

public static final R {
    public static final class id {
        ......

        public static final int tv1 = 2131493008;
        public static final int tv2 = 2131492977;
        public static final int tv3 = 2131493009;

        ......

        public id() {
        }
    }
}

经过分析,当全量打包时R.id.tv1 =
2131493007,由于R文件中的id都是final的,所以引用R.id.tv1的地点都被轮换为它对应的值2131493007了;当在activity_layout.xml中添加名字为tv2的控件,然后开展补丁打包时R.id.tv1的值变成了2131493008,而缓存的dex对应节点的值仍旧2131493007,所以在查找id为2131493007对应的控件时因为找不到而挂掉

我的率先个想法是只要在实践完processDebugResources任务后,把R文件里id类的所有字段的final描述符去掉就可以把值copy那些编译优化绕过去
=>

public static final R {
    public static final class id {
        ......

        public static int tv1 = 2131493008;
        public static int tv2 = 2131492977;
        public static int tv3 = 2131493009;

        ......

        public id() {
        }
    }
}

解除未来在实践compileDebugJavaWithJavac时编译出错了

2.png

出错的因由是声明只好引用带final描述符的常量,除此之外switch语句的case也无法不引用常量,具体请查看oracle对常量表明式的说明

设若拔取那些方案,对id的引用就无法运用常量表明式,像ButterKnife那样的view看重注入的框架都不可以用了,限制性太大那些想法就丢弃了

还有一个思路就是修改aapt的源码�,使多次包装时名字一样id的值保持一致,那几个肯定能化解不过工作量太大了就从未那样做,之后选拔了一个折中的办法,就是每一回把项目中的所有类(除去第三方库)都踏足dex的变型,即便缓解了那几个题材但功用一下子下跌好多,必要接近40秒才能跑起来如故很慢

==============================
本条难点苦恼了长久,直到tinker开源后阅读它的源码TinkerResourceIdTask.groovy时,发现它们也境遇了一致的题材,并有了一个解决方案,我们的现象和tinker场景在那一个标题上是一模一样的,直接照抄代码就解决了那些题材,紧要的事情说一回,感谢tinker、感谢tinker、感谢tinker!!

tinker的解决方案是,打补丁时按照用户配置的resourceMapping文件(每便创设成功后输出的app/build/intermediates/symbols/debug/R.txt),生成public.xml和ids.xml然后放进app/build/intermediates/res/merged/debug/values目录里,aapt在拍卖的时候会根据文件里的陈设规则去变通,具体那块的法则请看罗永浩的稿子Android应用程序资源的编译和包装进程分析(在里面搜索public.xml)那中间有详尽的辨证

同上并构成大家的面貌,第三遍全量打包成功之后把app/build/intermediates/symbols/debug/R.txt缓存下来,补丁打包在履行processResources义务前,根据缓存的记号表R.txt去生成public.xml和ids.xml然后放进app/build/intermediates/res/merged/debug/values目录里,那样平等名字的id前后的四次打造值就能保持一致了,代码如下FastdexResourceIdTask.groovy

public class FastdexResourceIdTask extends DefaultTask {
    static final String RESOURCE_PUBLIC_XML = "public.xml"
    static final String RESOURCE_IDX_XML = "idx.xml"

    String resDir
    String variantName

    @TaskAction
    def applyResourceId() {
        File buildDir = FastdexUtils.getBuildDir(project,variantName)
        String resourceMappingFile = new File(buildDir,Constant.R_TXT)
        // Parse the public.xml and ids.xml
        if (!FileUtils.isLegalFile(resourceMappingFile)) {
            project.logger.error("==fastdex apply resource mapping file ${resourceMappingFile} is illegal, just ignore")
            return
        }
        File idsXmlFile = new File(buildDir,RESOURCE_IDX_XML)
        File publicXmlFile = new File(buildDir,RESOURCE_PUBLIC_XML)
        if (FileUtils.isLegalFile(idsXmlFile) && FileUtils.isLegalFile(publicXmlFile)) {
            project.logger.error("==fastdex public xml file and ids xml file already exist, just ignore")
            return
        }
        String idsXml = resDir + "/values/ids.xml";
        String publicXml = resDir + "/values/public.xml";
        FileUtils.deleteFile(idsXml);
        FileUtils.deleteFile(publicXml);
        List<String> resourceDirectoryList = new ArrayList<String>()
        resourceDirectoryList.add(resDir)

        project.logger.error("==fastdex we build ${project.getName()} apk with apply resource mapping file ${resourceMappingFile}")
        Map<RDotTxtEntry.RType, Set<RDotTxtEntry>> rTypeResourceMap = PatchUtil.readRTxt(resourceMappingFile)

        AaptResourceCollector aaptResourceCollector = AaptUtil.collectResource(resourceDirectoryList, rTypeResourceMap)
        PatchUtil.generatePublicResourceXml(aaptResourceCollector, idsXml, publicXml)
        File publicFile = new File(publicXml)

        if (publicFile.exists()) {
            FileUtils.copyFileUsingStream(publicFile, publicXmlFile)
            project.logger.error("==fastdex gen resource public.xml in ${RESOURCE_PUBLIC_XML}")
        }
        File idxFile = new File(idsXml)
        if (idxFile.exists()) {
            FileUtils.copyFileUsingStream(idxFile, idsXmlFile)
            project.logger.error("==fastdex gen resource idx.xml in ${RESOURCE_IDX_XML}")
        }
    }
}

project.afterEvaluate {
    android.applicationVariants.all { variant ->
        def variantOutput = variant.outputs.first()
        def variantName = variant.name.capitalize()

        //保持补丁打包时R文件中相同的节点和第一次打包时的值保持一致
        FastdexResourceIdTask applyResourceTask = project.tasks.create("fastdexProcess${variantName}ResourceId", com.dx168.fastdex.build.task.FastdexResourceIdTask)
        applyResourceTask.resDir = variantOutput.processResources.resDir
        applyResourceTask.variantName = variantName
        variantOutput.processResources.dependsOn applyResourceTask
    }
}

比方项目中的资源尤其多,第四遍补丁打包生成public.xml和ids.xml时会占用部分岁月,最好做三遍缓存,未来的补丁打包直接动用缓存的public.xml和ids.xml**

==============================
焚林而猎了地点的原理性问题后,接下去继续做优化,上面有讲到*
transformClassesWithMultidexlistForDebug*职务的意义,由于选拔了隔离Application的做法,所有的序列代码都不在classes.dex中,这么些用来分析那个项目中的类要求放在classes.dex的义务就一向不意思了,直接禁掉它

project.afterEvaluate {
    android.applicationVariants.all { variant ->
        def variantName = variant.name.capitalize()

        def multidexlistTask = null
        try {
            multidexlistTask = project.tasks.getByName("transformClassesWithMultidexlistFor${variantName}")
        } catch (Throwable e) {
            //没有开启multiDexEnabled的情况下,会报这个任务找不到的异常
        }
        if (multidexlistTask != null) {
            multidexlistTask.enabled = false
        }
    }
}

禁掉未来,执行./gradle assembleDebug,在创设进度中挂掉了

:app:transformClassesWithMultidexlistForDebug SKIPPED
:app:transformClassesWithDexForDebug
Running dex in-process requires build tools 23.0.2.
For faster builds update this project to use the latest build tools.
UNEXPECTED TOP-LEVEL ERROR:
java.io.FileNotFoundException: /Users/tong/Projects/fastdex/app/build/intermediates/multi-dex/debug/maindexlist.txt (No such file or directory)
      at java.io.FileInputStream.open0(Native Method)
      at java.io.FileInputStream.open(FileInputStream.java:195)
      at java.io.FileInputStream.<init>(FileInputStream.java:138)
      at java.io.FileInputStream.<init>(FileInputStream.java:93)
      at java.io.FileReader.<init>(FileReader.java:58)
      at com.android.dx.command.dexer.Main.readPathsFromFile(Main.java:436)
      at com.android.dx.command.dexer.Main.runMultiDex(Main.java:361)
      at com.android.dx.command.dexer.Main.run(Main.java:275)
      at com.android.dx.command.dexer.Main.main(Main.java:245)
      at com.android.dx.command.Main.main(Main.java:106)
:app:transformClassesWithDexForDebug FAILED

FAILURE: Build failed with an exception.
......
BUILD FAILED

从上边的日记的第一行发现transformClassesWithMultidexlistForDebug职责真正禁止掉了,后边随着一个SKIPPED的输出,可是执行transformClassesWithDexForDebug职分时报app/build/intermediates/multi-dex/debug/maindexlist.txt
(No such file or directory)
,原因是transformClassesWithDexForDebug义务会检讨那个文件是不是留存,既然那样就在实施transformClassesWithDexForDebug职分前创办一个空文件,看是不是还会报错,代码如下

public class FastdexCreateMaindexlistFileTask extends DefaultTask {
    def applicationVariant

    @TaskAction
    void createFile() {
        if (applicationVariant != null) {
            File maindexlistFile = applicationVariant.getVariantData().getScope().getMainDexListFile()
            File parentFile = maindexlistFile.getParentFile()
            if (!parentFile.exists()) {
                parentFile.mkdirs()
            }

            if (!maindexlistFile.exists() || maindexlistFile.isDirectory()) {
                maindexlistFile.createNewFile()
            }
        }
    }
}

project.afterEvaluate {
    android.applicationVariants.all { variant ->
        def variantName = variant.name.capitalize()

        def multidexlistTask = null
        try {
            multidexlistTask = project.tasks.getByName("transformClassesWithMultidexlistFor${variantName}")
        } catch (Throwable e) {
            //没有开启multiDexEnabled的情况下,会报这个任务找不到的异常
        }
        if (multidexlistTask != null) {
            FastdexCreateMaindexlistFileTask createFileTask = project.tasks.create("fastdexCreate${variantName}MaindexlistFileTask", FastdexCreateMaindexlistFileTask)
            createFileTask.applicationVariant = variant

            multidexlistTask.dependsOn createFileTask
            multidexlistTask.enabled = false
        }
    }
}

再次执行./gradle assembleDebug

:app:transformClassesWithJarMergingForDebug UP-TO-DATE
:app:collectDebugMultiDexComponents UP-TO-DATE
:app:fastdexCreateDebugMaindexlistFileTask
:app:transformClassesWithMultidexlistForDebug SKIPPED
:app:transformClassesWithDexForDebug UP-TO-DATE
:app:mergeDebugJniLibFolders UP-TO-DATE
:app:transformNative_libsWithMergeJniLibsForDebug UP-TO-DATE
:app:processDebugJavaRes UP-TO-DATE
:app:transformResourcesWithMergeJavaResForDebug UP-TO-DATE
:app:validateConfigSigning
:app:packageDebug UP-TO-DATE
:app:zipalignDebug UP-TO-DATE
:app:assembleDebug UP-TO-DATE

BUILD SUCCESSFUL

Total time: 16.201 secs

这一次打造成功验证创制空文件的那种艺术可行

=========

俺们商家的序列在接纳的经过中,发现补丁打包时固然只改了一个java类,但创设时实施compileDebugJavaWithJavac义务照旧花了13秒

BUILD SUCCESSFUL

Total time: 28.222 secs
Task spend time:
    554ms  :app:processDebugManifest
    127ms  :app:mergeDebugResources
   3266ms  :app:processDebugResources
  13621ms  :app:compileDebugJavaWithJavac
   3654ms  :app:transformClassesWithJarMergingForDebug
   1354ms  :app:transformClassesWithDexForDebug
    315ms  :app:transformNative_libsWithMergeJniLibsForDebug
    220ms  :app:transformResourcesWithMergeJavaResForDebug
   2684ms  :app:packageDebug

通过分析由于我们运用了butterknife和tinker,那八个里面都用到了javax.annotation.processing.AbstractProcessor这些接口做代码动态变化,所以项目中的java文件若是过多,挨个扫描所有的java文件同时做操作会导致大批量的时光浪费,其实她们每一次变更的代码大概都是如出一辙的,因而一旦补丁打包时能把这么些职分换成温馨的完毕,仅编译和快照比较变化的java文件,并把结果输出到app/build/intermediates/classes/debug,覆盖原来的class,能大大升高效能,部分代码如下,详情看FastdexCustomJavacTask.groovy

public class FastdexCustomJavacTask extends DefaultTask {
    ......

    @TaskAction
    void compile() {
        ......
        File androidJar = new File("${project.android.getSdkDirectory()}/platforms/${project.android.getCompileSdkVersion()}/android.jar")
        File classpathJar = FastdexUtils.getInjectedJarFile(project,variantName)
        project.logger.error("==fastdex androidJar: ${androidJar}")
        project.logger.error("==fastdex classpath: ${classpathJar}")
        project.ant.javac(
                srcdir: patchJavaFileDir,
                source: '1.7',
                target: '1.7',
                encoding: 'UTF-8',
                destdir: patchClassesFileDir,
                bootclasspath: androidJar,
                classpath: classpathJar
        )
        compileTask.enabled = false
        File classesDir = applicationVariant.getVariantData().getScope().getJavaOutputDir()
        Files.walkFileTree(patchClassesFileDir.toPath(),new SimpleFileVisitor<Path>(){
            @Override
            FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Path relativePath = patchClassesFileDir.toPath().relativize(file)
                File destFile = new File(classesDir,relativePath.toString())
                FileUtils.copyFileUsingStream(file.toFile(),destFile)
                return FileVisitResult.CONTINUE
            }
        })
    }
}
project.afterEvaluate {
    android.applicationVariants.all { variant ->
        def variantName = variant.name.capitalize()
        Task compileTask = project.tasks.getByName("compile${variantName}JavaWithJavac")
        Task customJavacTask = project.tasks.create("fastdexCustomCompile${variantName}JavaWithJavac", com.dx168.fastdex.build.task.FastdexCustomJavacTask)
        customJavacTask.applicationVariant = variant
        customJavacTask.variantName = variantName
        customJavacTask.compileTask = compileTask
        compileTask.dependsOn customJavacTask
    }
}

执行./gradlew assembleDebug ,再来两遍

BUILD SUCCESSFUL

Total time: 17.555 secs
Task spend time:
   1142ms  :app:fastdexCustomCompileDebugJavaWithJavac
     59ms  :app:generateDebugBuildConfig
    825ms  :app:processDebugManifest
    196ms  :app:mergeDebugResources
   3540ms  :app:processDebugResources
   3045ms  :app:transformClassesWithJarMergingForDebug
   1505ms  :app:transformClassesWithDexForDebug
    391ms  :app:transformNative_libsWithMergeJniLibsForDebug
    253ms  :app:transformResourcesWithMergeJavaResForDebug
   3413ms  :app:packageDebug

一弹指快了10秒左右,good

=========
既然有缓存,就有缓存过期的标题,若是大家添加了某个第三方库的借助(器重关系暴发变化),并且在档次代码中援引了它,若是不消除缓存打出来的包运行起来后自然会包类找不到,所以必要处理这几个工作。
首先怎么得到依靠关系呢?通过以下代码可以得到一个借助列表

project.afterEvaluate {
    project.configurations.all.findAll { !it.allDependencies.empty }.each { c ->
        if (c.name.toString().equals("compile")
                || c.name.toString().equals("apt")
                || c.name.toString().equals("_debugCompile".toString())) {
            c.allDependencies.each { dep ->
                String depStr =  "$dep.group:$dep.name:$dep.version"
                println("${depStr}")
            }
        }
    }
}

输入如下

com.dialonce:dialonce-android:2.3.1
com.facebook.fresco:fresco:1.1.0
com.google.guava:guava:18.0
......
com.android.support:design:23.4.0
com.bigkoo:alertview:1.0.2
com.bigkoo:pickerview:2.0.8

可以在率先次全量打包时,和转移项目源码目录快照的同一个时间点,获取一份当前的体贴性列表并保存下去,当补丁打包时在获得一份当前的依赖列表,与事先封存的作对照,若是暴发变化就把缓存清除掉

其余最好提供一个积极消除缓存的天职

public class FastdexCleanTask extends DefaultTask {
    String variantName

    @TaskAction
    void clean() {
        if (variantName == null) {
            FastdexUtils.cleanAllCache()
        }
        else {
            FastdexUtils.cleanCache(project,variantName)
        }
    }
}

先来一个清除所有缓存的义务

project.tasks.create("fastdexCleanAll", FastdexCleanTask)

接下来在根据buildType、flavor成立对应的消除职分

android.applicationVariants.all { variant ->
    def variantName = variant.name.capitalize()
    //创建清理指定variantName缓存的任务(用户触发)
    FastdexCleanTask cleanTask = project.tasks.create("fastdexCleanFor${variantName}", FastdexCleanTask)
    cleanTask.variantName = variantName
}

==============================

延续的优化安排

  • 1、提升稳定性和容错性,这些是最重点的
  • 2、方今补丁打包的时候,是把尚未变动的类从app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar中移除,借使能hook掉transformClassesWithJarMergingForDebug那几个任务,仅把暴发变化的class参预combined.jar的变化,可以在IO上省出众多的小时
  • 3、近年来给品种源码目录做快照,使用的是文本copy的法门,要是能单纯只把需求的音讯写在文书文件里,可以在IO上省出部分时刻
  • 4、近日还一贯不对libs目录中发生变化做监控,后续须要补上这一块
  • 5、apk的安装速度比较慢(更加是ART下是因为在装置时对接纳做AOT编译,所以导致安装速度更加慢,具体请参考张邵文大神的篇章Android
    N混合编译与对热补丁影响解析
    ),通过socket把代码补丁和资源补丁发送给app,做到免安装

==============================

此间对包裹的流程做下总计

打包流程

全量打包时的流水线:
  • 1、合并所有的class文件生成一个jar包
  • 2、扫描所有的类型代码并且在构造方法里添加对fastdex.runtime.antilazyload.AntilazyLoad类的借助
    这样做的目标是为着化解class verify的题目,
    详情请看
    安卓App热补丁动态修复技术介绍
  • 3、对品种代码做快照,为了未来补丁打包时相比较那个java文件发出了变化
  • 4、对方今项目标所以看重做快照,为了将来补丁打包时相比较依赖是还是不是暴发了扭转,假设生成要求免去缓存
  • 5、调用真正的transform生成dex
  • 6、缓存生成的dex,并且把fastdex-runtime.dex插入到dex列表中,假设生成了四个dex,classes.dex
    classes2.dex 必要做一下操作
    fastdex-runtime.dex => classes.dex
    classes.dex => classes2.dex
    classes2.dex => classes3.dex
    接下来运行期在入口Application(fastdex.runtime.法斯特dexApplication)使用MultiDex把富有的dex加载进来
  • @see
    fastdex.build.transform.FastdexDexTransform
  • 7、保存资源映射表,为了维持id的值一致,详情看
  • @see
    fastdex.build.task.FastdexResourceIdTask
补丁打包时的流程
  • 1、检查缓存的可行
  • @see
    fastdex.build.variant.FastdexVariant
    的prepareEnv方法求证
  • 2、扫描所有变更的java文件并编译成class
  • @see
    fastdex.build.task.FastdexCustomJavacTask
  • 3、合并所有变更的class并生成jar包
  • 4、生成补丁dex
  • 5、把装有的dex依照一定规律放在transformClassesWithMultidexlistFor${variantName}义务的输出目录
    fastdex-runtime.dex => classes.dex
    patch.dex => classes2.dex
    dex_cache.classes.dex => classes3.dex
    dex_cache.classes2.dex => classes4.dex
    dex_cache.classesN.dex => classes(N + 2).dex

=============

漫天项目的代码近日早已开源了 https://github.com/typ0520/fastdex

倘若您喜爱本文就来给大家star吧

=============
加速apk的营造速度,怎么样把编译时间从130秒降到17秒
增速apk的营造速度,如何把编译时间从130秒降到17秒(二)

参照的序列与作品

Instant
Run

Tinker

安卓App热补丁动态修复技术介绍

Android应用程序资源的编译和包裹进度分析

关键字:
加快apk编译速度
加紧app编译速度
加速android编译速度
加快android studio 编译速度
android 加速编译速度
android studio编译慢
android studio编译速度优化
android studio gradle 编译慢