InstantRunDex extends Transform { @Override public void transform(@NonNull TransformInvocation invocation) throws IOException, TransformException, InterruptedException { File outputFolder = InstantRunBuildType.RELOAD.getOutputFolder(variantScope); boolean changesAreCompatible = variantScope.getInstantRunBuildContext().hasPassedVerification(); boolean restartDexRequested = variantScope.getGlobalScope().isActive(OptionalCompilationStep.RESTART_ONLY); if (!changesAreCompatible || restartDexRequested) { FileUtils.cleanOutputDir(outputFolder); return; } File classesJar = new File(outputFolder, "classes.jar"); if (classesJar.exists()) { FileUtils.delete(classesJar); } Files.createParentDirs(classesJar); final JarClassesBuilder jarClassesBuilder = getJarClassBuilder(classesJar); try { for (TransformInput input : invocation.getReferencedInputs()) { for (DirectoryInput directoryInput : input.getDirectoryInputs()) { if (!directoryInput.getContentTypes() .contains(ExtendedContentType.CLASSES_ENHANCED)) { continue; } final File folder = directoryInput.getFile(); if (invocation.isIncremental()) { for (Map.Entry<File, Status> entry : directoryInput.getChangedFiles().entrySet()) { if (entry.getValue() != Status.REMOVED) { File file = entry.getKey(); if (file.isFile()) { jarClassesBuilder.add(folder, file); } } } } else { Iterable<File> files = FileUtils.getAllFiles(folder); for (File inputFile : files) { jarClassesBuilder.add(folder, inputFile); } } } } } finally { jarClassesBuilder.close(); } if (jarClassesBuilder.isEmpty()) { FileUtils.cleanOutputDir(outputFolder); return; } final ImmutableList.Builder<File> inputFiles = ImmutableList.builder(); inputFiles.add(classesJar); try { variantScope.getInstantRunBuildContext().startRecording( InstantRunBuildContext.TaskType.INSTANT_RUN_DEX); convertByteCode(inputFiles.build(), outputFolder); variantScope.getInstantRunBuildContext().addChangedFile( InstantRunBuildContext.FileType.RELOAD_DEX, new File(outputFolder, "classes.dex")); } catch (ProcessException e) { throw new TransformException(e); } finally { variantScope.getInstantRunBuildContext().stopRecording( InstantRunBuildContext.TaskType.INSTANT_RUN_DEX); } } InstantRunDex(
            @NonNull InstantRunVariantScope transformVariantScope,
            @NonNull Supplier<DexByteCodeConverter> dexByteCodeConverter,
            @NonNull DexOptions dexOptions,
            @NonNull Logger logger); @Override void transform(@NonNull TransformInvocation invocation); @NonNull @Override String getName(); @NonNull @Override Set<QualifiedContent.ContentType> getInputTypes(); @NonNull @Override Set<QualifiedContent.Scope> getScopes(); @NonNull @Override Set<QualifiedContent.Scope> getReferencedScopes(); @NonNull @Override Map<String, Object> getParameterInputs(); @NonNull @Override Collection<File> getSecondaryDirectoryOutputs(); @Override boolean isIncremental();  }
@Test public void testVerifierFlaggedClass() throws TransformException, InterruptedException, IOException { when(instantRunBuildContext.hasPassedVerification()).thenReturn(Boolean.FALSE); final List<File> convertedFiles = new ArrayList<>(); InstantRunDex instantRunDex = getTestedDex(convertedFiles); instantRunDex.transform(new TransformInvocationBuilder(context) .addReferencedInputs(ImmutableList.of(getTransformInput(directoryInput))) .addOutputProvider(transformOutputProvider) .build()); assertThat(variantScope.getReloadDexOutputFolder().listFiles()).isEmpty(); convertedFiles.clear(); }
@Test public void testVerifierPassedClassOnLollipopOrAbove() throws TransformException, InterruptedException, IOException { when(instantRunBuildContext.hasPassedVerification()).thenReturn(Boolean.TRUE); List<File> convertedFiles = new ArrayList<>(); InstantRunDex instantRunDex = getTestedDex(convertedFiles); instantRunDex.transform(new TransformInvocationBuilder(context) .addReferencedInputs(ImmutableList.of(getTransformInput(directoryInput))) .addOutputProvider(transformOutputProvider) .build()); assertThat(variantScope.getReloadDexOutputFolder().listFiles()).isNotEmpty(); verify(instantRunBuildContext).addChangedFile( eq(InstantRunBuildContext.FileType.RELOAD_DEX), any(File.class)); }
@Test public void testVerifierPassedClassOnDalvik() throws TransformException, InterruptedException, IOException { when(instantRunBuildContext.hasPassedVerification()).thenReturn(Boolean.TRUE); when(project.getProperties()).then( invocation -> ImmutableMap.of("android.injected.build.api", "15")); InstantRunDex instantRunDex = new InstantRunDex( variantScope, ()-> dexByteCodeConverter, dexOptions, logger); instantRunDex.transform(new TransformInvocationBuilder(context) .addReferencedInputs(ImmutableList.of(getTransformInput(directoryInput))) .addOutputProvider(transformOutputProvider) .build()); assertThat(variantScope.getReloadDexOutputFolder().listFiles()).isNotEmpty(); }
@Test public void testNoChanges() throws TransformException, InterruptedException, IOException { when(instantRunBuildContext.hasPassedVerification()).thenReturn(Boolean.TRUE); when(project.getProperties()).then( invocation -> ImmutableMap.of("android.injected.build.api", "15")); InstantRunDex instantRunDex = new InstantRunDex( variantScope, ()-> dexByteCodeConverter, dexOptions, logger); instantRunDex.transform(new TransformInvocationBuilder(context) .addOutputProvider(transformOutputProvider) .build()); assertThat(variantScope.getReloadDexOutputFolder().listFiles()).isEmpty(); }