当前位置: 首页 > 工具软件 > Wechat-Matrix > 使用案例 >

matrix-gradle-plugin 方法前后插桩实现代码分析

宣煜
2023-12-01

1.gradle文件配置

apply plugin: 'com.tencent.matrix-plugin'
matrix {

    logLevel "D"

    trace {
        enable = true
        baseMethodMapFile = "${project.projectDir}/matrixTrace/methodMapping.txt"
        blackListFile = "${project.projectDir}/matrixTrace/blackMethodList.txt"
    }
    removeUnusedResources {
        variant = "debug"

        v2 = removeUnusedResourcesV2Enable

        if (!v2) {
            unusedResources = project.ext.unusedResourcesSet
        }

        enable true
        needSign true
        shrinkArsc true
        shrinkDuplicates true
        use7zip = true
        zipAlign = true
        embedResGuard true

        apkCheckerPath = "${project.configurations.apkCheckerDependency.resolve().find { it.name.startsWith("matrix-apk-canary") }.getAbsolutePath()}"
        sevenZipPath = "${project.configurations.sevenZipDependency.resolve().getAt(0).getAbsolutePath()}"
        //Notice: You need to modify the  value of $apksignerPath on different platform. the value below only suitable for Mac Platform,
        //if on Windows, you may have to  replace apksigner with apksigner.bat.
        apksignerPath = "${android.getSdkDirectory().getAbsolutePath()}/build-tools/${android.getBuildToolsVersion()}/apksigner"
        zipAlignPath = "${android.getSdkDirectory().getAbsolutePath()}/build-tools/${android.getBuildToolsVersion()}/zipalign"
        ignoreResources = ["R.id.*", "R.bool.*", "R.layout.unused_layout"]
    }
}

2.MatrixPlugin插件起点

class MatrixPlugin : Plugin<Project> {
    companion object {//伴生对象,里边的值都是static的
        const val TAG = "Matrix.Plugin"
    }

    override fun apply(project: Project) {
//apply plugin: 'com.tencent.matrix-plugin'
//matrix {
//
//    logLevel "D"
//
//    trace {
//        enable = true
//        baseMethodMapFile = "${project.projectDir}/matrixTrace/methodMapping.txt"
//        blackListFile = "${project.projectDir}/matrixTrace/blackMethodList.txt"
//    }
//    removeUnusedResources {
//        variant = "debug"
//
//        v2 = removeUnusedResourcesV2Enable
//
//        if (!v2) {
//            unusedResources = project.ext.unusedResourcesSet
//        }
//
//        enable true
//        needSign true
//        shrinkArsc true
//        shrinkDuplicates true
//        use7zip = true
//        zipAlign = true
//        embedResGuard true
//
//        apkCheckerPath = "${project.configurations.apkCheckerDependency.resolve().find { it.name.startsWith("matrix-apk-canary") }.getAbsolutePath()}"
//        sevenZipPath = "${project.configurations.sevenZipDependency.resolve().getAt(0).getAbsolutePath()}"
//        //Notice: You need to modify the  value of $apksignerPath on different platform. the value below only suitable for Mac Platform,
//        //if on Windows, you may have to  replace apksigner with apksigner.bat.
//        apksignerPath = "${android.getSdkDirectory().getAbsolutePath()}/build-tools/${android.getBuildToolsVersion()}/apksigner"
//        zipAlignPath = "${android.getSdkDirectory().getAbsolutePath()}/build-tools/${android.getBuildToolsVersion()}/zipalign"
//        ignoreResources = ["R.id.*", "R.bool.*", "R.layout.unused_layout"]
//    }
//}
        //创建MatrixExtension对象
        val matrix = project.extensions.create("matrix", MatrixExtension::class.java)
        //创建MatrixTraceExtension对象
        val traceExtension = (matrix as ExtensionAware).extensions.create("trace", MatrixTraceExtension::class.java)
        //创建MatrixRemoveUnusedResExtension对象,todo
        val removeUnusedResourcesExtension = matrix.extensions.create("removeUnusedResources", MatrixRemoveUnusedResExtension::class.java)

        //只支持application
        if (!project.plugins.hasPlugin("com.android.application")) {
            throw GradleException("Matrix Plugin, Android Application plugin required.")
        }

        project.afterEvaluate {
            Log.setLogLevel(matrix.logLevel)//设置level
        }

        //创建Matrix的tasks
        MatrixTasksManager().createMatrixTasks(
                project.extensions.getByName("android") as AppExtension,
                project,
                traceExtension,
                removeUnusedResourcesExtension
        )
    }
}

3.MatrixExtension外部配置一些信息,logLevel

open class MatrixExtension(
        var clientVersion: String = "",//todo
        var uuid: String = "",//todo
        var output: String = "",//todo
        var logLevel: String = "I"//log的level
) {

    override fun toString(): String {
        return """| log vevel = $logLevel
//                  | uuid = $uuid
                """.trimMargin()
    }
}

4.MatrixTraceExtension配置

//Extension
public class MatrixTraceExtension {
    boolean transformInjectionForced;   //强制transfrom注入
    String baseMethodMapFile;           //base的mapping文件
    String blackListFile;               //不插桩的路径
    String customDexTransformName;      //自定义transform名字

    boolean enable;                     //开关

//    public void setEnable(boolean enable) {
//        this.enable = enable;
//        onTraceEnabled(enable);
//    }

    public String getBaseMethodMapFile() {
        return baseMethodMapFile;
    }

    public String getBlackListFile() {
        return blackListFile;
    }

    public String getCustomDexTransformName() {
        return customDexTransformName;
    }

    //Injection 注射; 大量资金的投入; (液体)注入,喷入;
    public boolean isTransformInjectionForced() {
        return transformInjectionForced;
    }

    public boolean isEnable() {
        return enable;
    }
}

5.MatrixTasksManager createMatrixTraceTask创建task

class MatrixTasksManager {

    companion object {
        const val TAG = "Matrix.TasksManager"
    }

    fun createMatrixTasks(android: AppExtension,
                          project: Project,
                          traceExtension: MatrixTraceExtension,
                          removeUnusedResourcesExtension: MatrixRemoveUnusedResExtension) {

        createMatrixTraceTask(android, project, traceExtension)

        createRemoveUnusedResourcesTask(android, project, removeUnusedResourcesExtension)//todo
    }

    private fun createMatrixTraceTask(
            android: AppExtension,
            project: Project,
            traceExtension: MatrixTraceExtension) {
        //MatrixTraceCompat调用inject方法
        MatrixTraceCompat().inject(android, project, traceExtension)
    }

    private fun createRemoveUnusedResourcesTask(
            android: AppExtension,
            project: Project,
            removeUnusedResourcesExtension: MatrixRemoveUnusedResExtension) {

        project.afterEvaluate {

            if (!removeUnusedResourcesExtension.enable) {
                return@afterEvaluate
            }

            android.applicationVariants.all { variant ->
                if (Util.isNullOrNil(removeUnusedResourcesExtension.variant) ||
                        variant.name.equals(removeUnusedResourcesExtension.variant, true)) {
                    Log.i(TAG, "RemoveUnusedResourcesExtension: %s", removeUnusedResourcesExtension)

                    val removeUnusedResourcesTaskProvider = if (removeUnusedResourcesExtension.v2) {
                        val action = RemoveUnusedResourcesTaskV2.CreationAction(
                                CreationConfig(variant, project), removeUnusedResourcesExtension
                        )
                        project.tasks.register(action.name, action.type, action)
                    } else {
                        val action = RemoveUnusedResourcesTask.CreationAction(
                                CreationConfig(variant, project), removeUnusedResourcesExtension
                        )
                        project.tasks.register(action.name, action.type, action)
                    }

                    variant.assembleProvider?.configure {
                        it.dependsOn(removeUnusedResourcesTaskProvider)
                    }

                    removeUnusedResourcesTaskProvider.configure {
                        it.dependsOn(variant.packageApplicationProvider)
                    }
                }
            }
        }
    }
}

6.MatrixTraceCompat //Compat兼容性;

class MatrixTraceCompat : ITraceSwitchListener {//Compat兼容性;

    companion object {
        const val TAG = "Matrix.TraceCompat"

        const val LEGACY_FLAG = "matrix_trace_legacy"//legacy遗产; 遗赠财物; 遗留; 后遗症;
    }

    var traceInjection: MatrixTraceInjection? = null

    init {
        if (VersionsCompat.greatThanOrEqual(AGPVersion.AGP_4_0_0)) {
            traceInjection = MatrixTraceInjection()
        }
    }

    override fun onTraceEnabled(enable: Boolean) {
        traceInjection?.onTraceEnabled(enable)
    }

    fun inject(appExtension: AppExtension, project: Project, extension: MatrixTraceExtension) {
        when {
            VersionsCompat.lessThan(AGPVersion.AGP_3_6_0) ->
                legacyInject(appExtension, project, extension)
            VersionsCompat.greatThanOrEqual(AGPVersion.AGP_4_0_0) -> {
                if (project.extensions.extraProperties.has(LEGACY_FLAG) &&
                    (project.extensions.extraProperties.get(LEGACY_FLAG) as? String?) == "true") {
                    legacyInject(appExtension, project, extension)
                } else {
                    traceInjection!!.inject(appExtension, project, extension)
                }
            }
            else -> Log.e(TAG, "Matrix does not support Android Gradle Plugin " +
                    "${VersionsCompat.androidGradlePluginVersion}!.")
        }
    }

    //legacy遗产; 遗赠财物; 遗留; 后遗症;
    private fun legacyInject(appExtension: AppExtension,
                             project: Project,
                             extension: MatrixTraceExtension) {

        project.afterEvaluate {

            if (!extension.isEnable) {
                return@afterEvaluate
            }

            appExtension.applicationVariants.all {
                MatrixTraceLegacyTransform.inject(extension, project, it)
            }
        }
    }
}

7.MatrixTraceInjection 开始注入的类

class MatrixTraceInjection : ITraceSwitchListener {

    companion object {
        const val TAG = "Matrix.TraceInjection"
    }

    private var traceEnable = false

    override fun onTraceEnabled(enable: Boolean) {
        traceEnable = enable
    }

    //step 1
    fun inject(appExtension: AppExtension,
               project: Project,
               extension: MatrixTraceExtension) {
        injectTransparentTransform(appExtension, project, extension)
        project.afterEvaluate {
            if (extension.isEnable) {
                doInjection(appExtension, project, extension)
            }
        }
    }

    private var transparentTransform: MatrixTraceTransform? = null

    //step 2
    private fun injectTransparentTransform(appExtension: AppExtension,
                                           project: Project,
                                           extension: MatrixTraceExtension) {
        //创建一个MatrixTraceTransform
        transparentTransform = MatrixTraceTransform(project, extension)
        //注册MatrixTraceTransform
        appExtension.registerTransform(transparentTransform!!)
    }

    //step 3
    private fun doInjection(appExtension: AppExtension,
                            project: Project,
                            extension: MatrixTraceExtension) {
        appExtension.applicationVariants.all { variant ->
            //判断哪种 todo
            if (injectTaskOrTransform(project, extension, variant) == InjectionMode.TransformInjection) {
                // Inject transform
                transformInjection()
            } else {
                // Inject task
                taskInjection(project, extension, variant)
            }
        }
    }

    //step 4.2
    private fun taskInjection(project: Project,
                              extension: MatrixTraceExtension,
                              variant: BaseVariant) {

        Log.i(TAG, "Using trace task mode.")

        project.afterEvaluate {

            val creationConfig = CreationConfig(variant, project)
            val action = MatrixTraceTask.CreationAction(creationConfig, extension)
            val traceTaskProvider = project.tasks.register(action.name, action.type, action)

            val variantName = variant.name

            val minifyTasks = arrayOf(
                    BaseCreationAction.computeTaskName("minify", variantName, "WithProguard")
            )

            var minify = false
            for (taskName in minifyTasks) {
                val taskProvider = BaseCreationAction.findNamedTask(project.tasks, taskName)
                if (taskProvider != null) {
                    minify = true
                    //找到位置Proguard
                    traceTaskProvider.dependsOn(taskProvider)//todo
                }
            }

            if (minify) {
                val dexBuilderTaskName = BaseCreationAction.computeTaskName("dexBuilder", variantName, "")
                val taskProvider = BaseCreationAction.findNamedTask(project.tasks, dexBuilderTaskName)

                taskProvider?.configure { task: Task ->
                    //找到位置dexBuilder
                    traceTaskProvider.get().wired(task as DexArchiveBuilderTask)//todo
                }

                if (taskProvider == null) {
                    Log.e(TAG, "Do not find '$dexBuilderTaskName' task. Inject matrix trace task failed.")
                }
            }
        }
    }

    //step 4.1
    private fun transformInjection() {

        Log.i(TAG, "Using trace transform mode.")

        transparentTransform!!.enable()
    }

    enum class InjectionMode {
        TaskInjection,
        TransformInjection,
    }

    //step 3.1
    private fun injectTaskOrTransform(project: Project,
                                      extension: MatrixTraceExtension,
                                      variant: BaseVariant): InjectionMode {

        if (!variant.buildType.isMinifyEnabled
                || extension.isTransformInjectionForced
                || getCodeShrinker(project) == CodeShrinker.R8) {
            return InjectionMode.TransformInjection
        }

        return InjectionMode.TaskInjection
    }


}

8.CreationConfig getCodeShrinker获取CodeShrinker

class CreationConfig(
        val variant: BaseVariant,
        val project: Project
) {
    companion object {

        fun getCodeShrinker(project: Project): CodeShrinker {
            //代码压缩工具
            var enableR8: Boolean = when (val property = project.properties["android.enableR8"]) {
                null -> true//?todo 为什么null的时候return true
                else -> (property as String).toBoolean()
            }

            return when {
                enableR8 -> CodeShrinker.R8
                else -> CodeShrinker.PROGUARD
            }
        }
    }
}

9.BaseCreationAction

interface ICreationAction<TaskT> {
    val name: String
    val type: Class<TaskT>
}

abstract class BaseCreationAction<TaskT>(
        private val creationConfig: CreationConfig
) : ICreationAction<TaskT> {

    companion object {
        @JvmField
        val computeTaskName = { prefix: String, name: String, suffix: String
            ->
            prefix.appendCapitalized(name, suffix)//前缀加后续的首字母大写的
        }

        fun findNamedTask(taskContainer: TaskContainer, name: String): TaskProvider<Task>? {
            try {
                return taskContainer.named(name)
            } catch (t: Throwable) {} finally {}

            return null
        }
    }

    fun computeTaskName(prefix: String, suffix: String): String =
            computeTaskName(prefix, creationConfig.variant.name, suffix)
}

10.MatrixTraceTransform一个转换器

class MatrixTraceTransform(
        private val project: Project,
        private val extension: MatrixTraceExtension,
        private var transparent: Boolean = true
) : Transform() {

    companion object {
        const val TAG = "Matrix.TraceTransform"
    }

    //step 1
    fun enable() {
        transparent = false
    }

    fun disable() {
        transparent = true
    }

    override fun getName(): String {
        return "MatrixTraceTransform"
    }

    override fun getInputTypes(): Set<QualifiedContent.ContentType> {
        return TransformManager.CONTENT_CLASS
    }

    override fun getScopes(): MutableSet<in QualifiedContent.Scope>? {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    override fun isIncremental(): Boolean {
        return true
    }

    //step 2
    override fun transform(transformInvocation: TransformInvocation) {
        super.transform(transformInvocation)

        if (transparent) {
            //不加任何东西
            transparent(transformInvocation)
        } else {
            //修改吧
            transforming(transformInvocation)
        }
    }

    /**
     * Passes all inputs throughout.
     * TODO: How to avoid this trivial work?
     */
    //step 3 啥也不干,不做任何东西
    private fun transparent(invocation: TransformInvocation) {

        val outputProvider = invocation.outputProvider!!

        if (!invocation.isIncremental) {
            outputProvider.deleteAll()
        }

        for (ti in invocation.inputs) {
            for (jarInput in ti.jarInputs) {
                val inputJar = jarInput.file
                val outputJar = outputProvider.getContentLocation(
                        jarInput.name,
                        jarInput.contentTypes,
                        jarInput.scopes,
                        Format.JAR)

                if (invocation.isIncremental) {
                    when (jarInput.status) {
                        Status.NOTCHANGED -> {
                        }
                        Status.ADDED, Status.CHANGED -> {
                            copyFileAndMkdirsAsNeed(inputJar, outputJar)
                        }
                        Status.REMOVED -> FileUtils.delete(outputJar)
                        else -> {}
                    }
                } else {
                    copyFileAndMkdirsAsNeed(inputJar, outputJar)
                }
            }
            for (directoryInput in ti.directoryInputs) {
                val inputDir = directoryInput.file
                val outputDir = outputProvider.getContentLocation(
                        directoryInput.name,
                        directoryInput.contentTypes,
                        directoryInput.scopes,
                        Format.DIRECTORY)

                if (invocation.isIncremental) {
                    for (entry in directoryInput.changedFiles.entries) {
                        val inputFile = entry.key
                        when (entry.value) {
                            Status.NOTCHANGED -> {
                            }
                            Status.ADDED, Status.CHANGED -> if (!inputFile.isDirectory) {
                                val outputFile = toOutputFile(outputDir, inputDir, inputFile)
                                //直接copy
                                copyFileAndMkdirsAsNeed(inputFile, outputFile)
                            }
                            Status.REMOVED -> {
                                val outputFile = toOutputFile(outputDir, inputDir, inputFile)
                                FileUtils.deleteIfExists(outputFile)
                            }
                            else -> {}
                        }
                    }
                } else {
                    //todo 啥语法
                    //https://blog.csdn.net/u012165769/article/details/106593363
                    //out 声明我们称之为协变,就是可以兼容自己及其子类,相当于 Java 的 ? extend E
                    //in 声明我们称之为逆协变,就是可以兼容自己及其父类,相当于 Java 的 ? super E
                    for (`in` in FileUtils.getAllFiles(inputDir)) {
                        val out = toOutputFile(outputDir, inputDir, `in`)
                        copyFileAndMkdirsAsNeed(`in`, out)
                    }
                }
            }
        }
    }

    //step 3.2
    private fun copyFileAndMkdirsAsNeed(from: File, to: File) {
        if (from.exists()) {
            to.parentFile.mkdirs()
            FileUtils.copyFile(from, to)
        }
    }

    //step 3.1
    private fun toOutputFile(outputDir: File, inputDir: File, inputFile: File): File {
        return File(outputDir, FileUtils.relativePossiblyNonExistingPath(inputFile, inputDir))
    }

    //step 4.1
    private fun configure(transformInvocation: TransformInvocation): Configuration {

        val buildDir = project.buildDir.absolutePath
        val dirName = transformInvocation.context.variantName

        //文件输出路径
        val mappingOut = Joiner.on(File.separatorChar).join(
                buildDir,
                FD_OUTPUTS,//outputs
                "mapping",
                dirName)

        return Configuration.Builder()
                .setBaseMethodMap(extension.baseMethodMapFile)
                .setBlockListFile(extension.blackListFile)
                .setMethodMapFilePath("$mappingOut/methodMapping.txt")
                .setIgnoreMethodMapFilePath("$mappingOut/ignoreMethodMapping.txt")
                .setMappingPath(mappingOut)
                .build()
    }

    //step 4
    private fun transforming(invocation: TransformInvocation) {

        val start = System.currentTimeMillis()

        val outputProvider = invocation.outputProvider!!
        val isIncremental = invocation.isIncremental && this.isIncremental

        if (!isIncremental) {
            outputProvider.deleteAll()
        }

        val config = configure(invocation)

        val changedFiles = ConcurrentHashMap<File, Status>()//
        val inputToOutput = ConcurrentHashMap<File, File>()
        val inputFiles = ArrayList<File>()

        var transformDirectory: File? = null//todo

        for (input in invocation.inputs) {
            for (directoryInput in input.directoryInputs) {
                changedFiles.putAll(directoryInput.changedFiles)
                val inputDir = directoryInput.file
                inputFiles.add(inputDir)
                val outputDirectory = outputProvider.getContentLocation(
                        directoryInput.name,
                        directoryInput.contentTypes,
                        directoryInput.scopes,
                        Format.DIRECTORY)

                inputToOutput[inputDir] = outputDirectory
                if (transformDirectory == null) transformDirectory = outputDirectory.parentFile
            }

            for (jarInput in input.jarInputs) {
                val inputFile = jarInput.file
                changedFiles[inputFile] = jarInput.status
                inputFiles.add(inputFile)
                val outputJar = outputProvider.getContentLocation(
                        jarInput.name,
                        jarInput.contentTypes,
                        jarInput.scopes,
                        Format.JAR)

                inputToOutput[inputFile] = outputJar
                if (transformDirectory == null) transformDirectory = outputJar.parentFile
            }
        }

        if (inputFiles.size == 0 || transformDirectory == null) {
            Log.i(TAG, "Matrix trace do not find any input files")
            return
        }

        // Get transform root dir.
        val outputDirectory = transformDirectory

        MatrixTrace(
                ignoreMethodMapFilePath = config.ignoreMethodMapFilePath,
                methodMapFilePath = config.methodMapFilePath,
                baseMethodMapPath = config.baseMethodMapPath,
                blockListFilePath = config.blockListFilePath,
                mappingDir = config.mappingDir
        ).doTransform(
                classInputs = inputFiles,//ArrayList<File>()
                changedFiles = changedFiles,//ConcurrentHashMap<File, Status>()
                isIncremental = isIncremental,
                traceClassDirectoryOutput = outputDirectory,//output文件夹
                inputToOutput = inputToOutput,//ConcurrentHashMap<File, File>()
                legacyReplaceChangedFile = null,
                legacyReplaceFile = null)

        val cost = System.currentTimeMillis() - start
        Log.i(TAG, " Insert matrix trace instrumentations cost time: %sms.", cost)
    }

}

11.Configuration配置

public class Configuration {

    public String packageName;//没有,老的有
    public String mappingDir;
    public String baseMethodMapPath;
    public String methodMapFilePath;
    public String ignoreMethodMapFilePath;
    public String blockListFilePath;
    public String traceClassOut;没有,老的有
    //todo
    public HashSet<String> blockSet = new HashSet<>();

    public Configuration() {
    }

    Configuration(String packageName, String mappingDir, String baseMethodMapPath, String methodMapFilePath,
                  String ignoreMethodMapFilePath, String blockListFilePath, String traceClassOut) {
        this.packageName = packageName;
        this.mappingDir = Util.nullAsNil(mappingDir);
        this.baseMethodMapPath = Util.nullAsNil(baseMethodMapPath);
        this.methodMapFilePath = Util.nullAsNil(methodMapFilePath);
        this.ignoreMethodMapFilePath = Util.nullAsNil(ignoreMethodMapFilePath);
        this.blockListFilePath = Util.nullAsNil(blockListFilePath);
        this.traceClassOut = Util.nullAsNil(traceClassOut);
    }

    public int parseBlockFile(MappingCollector processor) {//todo
//[package]
//-keeppackage facebook/
//-keeppackage com/squareup/
        String blockStr = TraceBuildConstants.DEFAULT_BLOCK_TRACE
                + FileUtil.readFileAsString(blockListFilePath);

        String[] blockArray = blockStr.trim().replace("/", ".").replace("\r", "").split("\n");

        if (blockArray != null) {
            for (String block : blockArray) {
                if (block.length() == 0) {
                    continue;
                }
                if (block.startsWith("#")) {
                    continue;
                }
                if (block.startsWith("[")) {
                    continue;
                }

                if (block.startsWith("-keepclass ")) {
                    block = block.replace("-keepclass ", "");
                    blockSet.add(processor.proguardClassName(block, block));
                } else if (block.startsWith("-keeppackage ")) {
                    block = block.replace("-keeppackage ", "");
                    blockSet.add(processor.proguardPackageName(block, block));
                }
            }
        }
        return blockSet.size();
    }

    @Override
    public String toString() {
        return "\n# Configuration" + "\n"
                + "|* packageName:\t" + packageName + "\n"
                + "|* mappingDir:\t" + mappingDir + "\n"
                + "|* baseMethodMapPath:\t" + baseMethodMapPath + "\n"
                + "|* methodMapFilePath:\t" + methodMapFilePath + "\n"
                + "|* ignoreMethodMapFilePath:\t" + ignoreMethodMapFilePath + "\n"
                + "|* blockListFilePath:\t" + blockListFilePath + "\n"
                + "|* traceClassOut:\t" + traceClassOut + "\n";
    }

    public static class Builder {

        public String packageName;//没设置呢? todo 老的有
        public String mappingPath;
        public String baseMethodMap;
        public String methodMapFile;
        public String ignoreMethodMapFile;
        public String blockListFile;
        public String traceClassOut;//老的有

        public Builder setPackageName(String packageName) {//老的有
            this.packageName = packageName;
            return this;
        }

        public Builder setMappingPath(String mappingPath) {//ok
            this.mappingPath = mappingPath;
            return this;
        }

        public Builder setBaseMethodMap(String baseMethodMap) {//ok
            this.baseMethodMap = baseMethodMap;
            return this;
        }

        public Builder setTraceClassOut(String traceClassOut) {//老的有
            this.traceClassOut = traceClassOut;
            return this;
        }

        public Builder setMethodMapFilePath(String methodMapDir) {//ok
            methodMapFile = methodMapDir;
            return this;
        }

        public Builder setIgnoreMethodMapFilePath(String methodMapDir) {//ok
            ignoreMethodMapFile = methodMapDir;
            return this;
        }

        public Builder setBlockListFile(String blockListFile) {//ok
            this.blockListFile = blockListFile;
            return this;
        }

        public Configuration build() {
            return new Configuration(packageName, mappingPath, baseMethodMap, methodMapFile, ignoreMethodMapFile, blockListFile, traceClassOut);
        }

    }
}

12.MappingCollector

/**
 * Created by caichongyang on 2017/6/3.
 */
public interface MappingProcessor {
    /**
     * mapping the class name.
     *
     * @param className    the original class name.
     * @param newClassName the new class name.
     * @return whether the processor is interested in receiving mappings of the class members of
     * this class.
     */
    boolean processClassMapping(String className,
                                       String newClassName);

    /**
     * mapping the method name.
     *
     * @param className          the original class name.
     * @param methodReturnType   the original external method return type.
     * @param methodName         the original external method name.
     * @param methodArguments    the original external method arguments.
     * @param newClassName       the new class name.
     * @param newMethodName      the new method name.
     */
    void processMethodMapping(String className,
                                     String methodReturnType,
                                     String methodName,
                                     String methodArguments,
                                     String newClassName,
                                     String newMethodName);
}
/**
 * Created by caichongyang on 2017/8/3.
 */
public class MappingCollector implements MappingProcessor {
    private final static String TAG = "MappingCollector";
    private final static int DEFAULT_CAPACITY = 2000;
    public HashMap<String, String> mObfuscatedRawClassMap = new HashMap<>(DEFAULT_CAPACITY);
    public HashMap<String, String> mRawObfuscatedClassMap = new HashMap<>(DEFAULT_CAPACITY);
    public HashMap<String, String> mRawObfuscatedPackageMap = new HashMap<>(DEFAULT_CAPACITY);
    private final Map<String, Map<String, Set<MethodInfo>>> mObfuscatedClassMethodMap = new HashMap<>();
    private final Map<String, Map<String, Set<MethodInfo>>> mOriginalClassMethodMap = new HashMap<>();

    @Override
    public boolean processClassMapping(String className, String newClassName) {
        this.mObfuscatedRawClassMap.put(newClassName, className);
        this.mRawObfuscatedClassMap.put(className, newClassName);
        int classNameLen = className.lastIndexOf('.');
        int newClassNameLen = newClassName.lastIndexOf('.');
        if (classNameLen > 0 && newClassNameLen > 0) {
            this.mRawObfuscatedPackageMap.put(className.substring(0, classNameLen), newClassName.substring(0, newClassNameLen));
        } else {
            Log.e(TAG, "class without package name: %s -> %s, pls check input mapping", className, newClassName);
        }
        return true;
    }

    @Override
    public void processMethodMapping(String className, String methodReturnType, String methodName, String methodArguments, String newClassName, String newMethodName) {
        newClassName = mRawObfuscatedClassMap.get(className);
        Map<String, Set<MethodInfo>> methodMap = mObfuscatedClassMethodMap.get(newClassName);
        if (methodMap == null) {
            methodMap = new HashMap<>();
            mObfuscatedClassMethodMap.put(newClassName, methodMap);
        }
        Set<MethodInfo> methodSet = methodMap.get(newMethodName);
        if (methodSet == null) {
            methodSet = new LinkedHashSet<>();
            methodMap.put(newMethodName, methodSet);
        }
        methodSet.add(new MethodInfo(className, methodReturnType, methodName, methodArguments));


        Map<String, Set<MethodInfo>> methodMap2 = mOriginalClassMethodMap.get(className);
        if (methodMap2 == null) {
            methodMap2 = new HashMap<>();
            mOriginalClassMethodMap.put(className, methodMap2);
        }
        Set<MethodInfo> methodSet2 = methodMap2.get(methodName);
        if (methodSet2 == null) {
            methodSet2 = new LinkedHashSet<>();
            methodMap2.put(methodName, methodSet2);
        }
        methodSet2.add(new MethodInfo(newClassName, methodReturnType, newMethodName, methodArguments));

    }

    public String originalClassName(String proguardClassName, String defaultClassName) {
        if (mObfuscatedRawClassMap.containsKey(proguardClassName)) {
            return mObfuscatedRawClassMap.get(proguardClassName);
        } else {
            return defaultClassName;
        }
    }

    public String proguardClassName(String originalClassName, String defaultClassName) {
        if (mRawObfuscatedClassMap.containsKey(originalClassName)) {
            return mRawObfuscatedClassMap.get(originalClassName);
        } else {
            return defaultClassName;
        }
    }

    public String proguardPackageName(String originalPackage, String defaultPackage) {
        if (mRawObfuscatedPackageMap.containsKey(originalPackage)) {
            return mRawObfuscatedPackageMap.get(originalPackage);
        } else {
            return defaultPackage;
        }
    }

    /**
     * get original method info
     *
     * @param obfuscatedClassName
     * @param obfuscatedMethodName
     * @param obfuscatedMethodDesc
     * @return
     */
    public MethodInfo originalMethodInfo(String obfuscatedClassName, String obfuscatedMethodName, String obfuscatedMethodDesc) {
        DescInfo descInfo = parseMethodDesc(obfuscatedMethodDesc, false);

        // obfuscated name -> original method names.
        Map<String, Set<MethodInfo>> methodMap = mObfuscatedClassMethodMap.get(obfuscatedClassName);
        if (methodMap != null) {
            Set<MethodInfo> methodSet = methodMap.get(obfuscatedMethodName);
            if (methodSet != null) {
                // Find all matching methods.
                Iterator<MethodInfo> methodInfoIterator = methodSet.iterator();
                while (methodInfoIterator.hasNext()) {
                    MethodInfo methodInfo = methodInfoIterator.next();
                    if (methodInfo.matches(descInfo.returnType, descInfo.arguments)) {
                        MethodInfo newMethodInfo = new MethodInfo(methodInfo);
                        newMethodInfo.setDesc(descInfo.desc);
                        return newMethodInfo;
                    }
                }
            }
        }

        MethodInfo defaultMethodInfo = MethodInfo.deFault();
        defaultMethodInfo.setDesc(descInfo.desc);
        defaultMethodInfo.setOriginalName(obfuscatedMethodName);
        return defaultMethodInfo;
    }

    /**
     * get obfuscated method info
     *
     * @param originalClassName
     * @param originalMethodName
     * @param originalMethodDesc
     * @return
     */
    public MethodInfo obfuscatedMethodInfo(String originalClassName, String originalMethodName, String originalMethodDesc) {
        DescInfo descInfo = parseMethodDesc(originalMethodDesc, true);

        // Class name -> obfuscated method names.
        Map<String, Set<MethodInfo>> methodMap = mOriginalClassMethodMap.get(originalClassName);
        if (methodMap != null) {
            Set<MethodInfo> methodSet = methodMap.get(originalMethodName);
            if (null != methodSet) {
                // Find all matching methods.
                Iterator<MethodInfo> methodInfoIterator = methodSet.iterator();
                while (methodInfoIterator.hasNext()) {
                    MethodInfo methodInfo = methodInfoIterator.next();
                    MethodInfo newMethodInfo = new MethodInfo(methodInfo);
                    obfuscatedMethodInfo(newMethodInfo);
                    if (newMethodInfo.matches(descInfo.returnType, descInfo.arguments)) {
                        newMethodInfo.setDesc(descInfo.desc);
                        return newMethodInfo;
                    }
                }
            }
        }
        MethodInfo defaultMethodInfo = MethodInfo.deFault();
        defaultMethodInfo.setDesc(descInfo.desc);
        defaultMethodInfo.setOriginalName(originalMethodName);
        return defaultMethodInfo;
    }

    private void obfuscatedMethodInfo(MethodInfo methodInfo) {
        String methodArguments = methodInfo.getOriginalArguments();
        String[] args = methodArguments.split(",");
        StringBuffer stringBuffer = new StringBuffer();
        for (String str : args) {
            String key = str.replace("[", "").replace("]", "");
            if (mRawObfuscatedClassMap.containsKey(key)) {
                stringBuffer.append(str.replace(key, mRawObfuscatedClassMap.get(key)));
            } else {
                stringBuffer.append(str);
            }
            stringBuffer.append(',');
        }
        if (stringBuffer.length() > 0) {
            stringBuffer.deleteCharAt(stringBuffer.length() - 1);
        }
        String methodReturnType = methodInfo.getOriginalType();
        String key = methodReturnType.replace("[", "").replace("]", "");
        if (mRawObfuscatedClassMap.containsKey(key)) {
            methodReturnType = methodReturnType.replace(key, mRawObfuscatedClassMap.get(key));
        }
        methodInfo.setOriginalArguments(stringBuffer.toString());
        methodInfo.setOriginalType(methodReturnType);
    }

    /**
     * parse method desc
     *
     * @param desc
     * @param isRawToObfuscated
     * @return
     */
    private DescInfo parseMethodDesc(String desc, boolean isRawToObfuscated) {
        DescInfo descInfo = new DescInfo();
        Type[] argsObj = Type.getArgumentTypes(desc);
        StringBuffer argumentsBuffer = new StringBuffer();
        StringBuffer descBuffer = new StringBuffer();
        descBuffer.append('(');
        for (Type type : argsObj) {
            String key = type.getClassName().replace("[", "").replace("]", "");
            if (isRawToObfuscated) {
                if (mRawObfuscatedClassMap.containsKey(key)) {
                    argumentsBuffer.append(type.getClassName().replace(key, mRawObfuscatedClassMap.get(key)));
                    descBuffer.append(type.toString().replace(key, mRawObfuscatedClassMap.get(key)));
                } else {
                    argumentsBuffer.append(type.getClassName());
                    descBuffer.append(type.toString());
                }
            } else {
                if (mObfuscatedRawClassMap.containsKey(key)) {
                    argumentsBuffer.append(type.getClassName().replace(key, mObfuscatedRawClassMap.get(key)));
                    descBuffer.append(type.toString().replace(key, mObfuscatedRawClassMap.get(key)));
                } else {
                    argumentsBuffer.append(type.getClassName());
                    descBuffer.append(type.toString());
                }
            }
            argumentsBuffer.append(',');
        }
        descBuffer.append(')');

        Type returnObj;
        try {
            returnObj = Type.getReturnType(desc);
        } catch (ArrayIndexOutOfBoundsException e) {
            returnObj = Type.getReturnType(desc + ";");
        }
        if (isRawToObfuscated) {
            String key = returnObj.getClassName().replace("[", "").replace("]", "");
            if (mRawObfuscatedClassMap.containsKey(key)) {
                descInfo.setReturnType(returnObj.getClassName().replace(key, mRawObfuscatedClassMap.get(key)));
                descBuffer.append(returnObj.toString().replace(key, mRawObfuscatedClassMap.get(key)));
            } else {
                descInfo.setReturnType(returnObj.getClassName());
                descBuffer.append(returnObj.toString());
            }
        } else {
            String key = returnObj.getClassName().replace("[", "").replace("]", "");
            if (mObfuscatedRawClassMap.containsKey(key)) {
                descInfo.setReturnType(returnObj.getClassName().replace(key, mObfuscatedRawClassMap.get(key)));
                descBuffer.append(returnObj.toString().replace(key, mObfuscatedRawClassMap.get(key)));
            } else {
                descInfo.setReturnType(returnObj.getClassName());
                descBuffer.append(returnObj.toString());
            }
        }

        // delete last ,
        if (argumentsBuffer.length() > 0) {
            argumentsBuffer.deleteCharAt(argumentsBuffer.length() - 1);
        }
        descInfo.setArguments(argumentsBuffer.toString());

        descInfo.setDesc(descBuffer.toString());
        return descInfo;
    }

    /**
     * about method desc info
     */
    private static class DescInfo {
        private String desc;
        private String arguments;
        private String returnType;

        public void setArguments(String arguments) {
            this.arguments = arguments;
        }

        public void setReturnType(String returnType) {
            this.returnType = returnType;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }
    }

}

13.MatrixTrace插桩的地方


class MatrixTrace(
        private val ignoreMethodMapFilePath: String,
        private val methodMapFilePath: String,
        private val baseMethodMapPath: String?,
        private val blockListFilePath: String?,
        private val mappingDir: String
) {
    companion object {
        private const val TAG: String = "Matrix.Trace"

        @Suppress("DEPRECATION")
        fun getUniqueJarName(jarFile: File): String {
            val origJarName = jarFile.name
            val hashing = Hashing.sha1().hashString(jarFile.path, Charsets.UTF_16LE).toString()
            val dotPos = origJarName.lastIndexOf('.')
            return if (dotPos < 0) {
                String.format("%s_%s", origJarName, hashing)
            } else {
                val nameWithoutDotExt = origJarName.substring(0, dotPos)
                val dotExt = origJarName.substring(dotPos)
                String.format("%s_%s%s", nameWithoutDotExt, hashing, dotExt)
            }
        }

    }

    fun doTransform(classInputs: Collection<File>,
                    changedFiles: Map<File, Status>,
                    inputToOutput: Map<File, File>,
                    isIncremental: Boolean,
                    traceClassDirectoryOutput: File,
                    legacyReplaceChangedFile: ((File, Map<File, Status>) -> Object)?,
                    legacyReplaceFile: ((File, File) -> (Object))?
    ) {

        val executor: ExecutorService = Executors.newFixedThreadPool(16)

        val config = Configuration.Builder()
                .setIgnoreMethodMapFilePath(ignoreMethodMapFilePath)
                .setMethodMapFilePath(methodMapFilePath)
                .setBaseMethodMap(baseMethodMapPath)
                .setBlockListFile(blockListFilePath)
                .setMappingPath(mappingDir)
                .build()

        /**
         * step 1
         */
        var start = System.currentTimeMillis()

        val futures = LinkedList<Future<*>>()

        val mappingCollector = MappingCollector()
        val methodId = AtomicInteger(0)
        val collectedMethodMap = ConcurrentHashMap<String, TraceMethod>()

        futures.add(executor.submit(ParseMappingTask(
                mappingCollector, collectedMethodMap, methodId, config)))

        val dirInputOutMap = ConcurrentHashMap<File, File>()
        val jarInputOutMap = ConcurrentHashMap<File, File>()

        for (file in classInputs) {
            if (file.isDirectory) {
                futures.add(executor.submit(CollectDirectoryInputTask(
                        directoryInput = file,
                        mapOfChangedFiles = changedFiles,
                        mapOfInputToOutput = inputToOutput,
                        isIncremental = isIncremental,
                        traceClassDirectoryOutput = traceClassDirectoryOutput,
                        legacyReplaceChangedFile = legacyReplaceChangedFile,
                        legacyReplaceFile = legacyReplaceFile,

                        // result
                        resultOfDirInputToOut = dirInputOutMap
                )))
            } else {
                val status = Status.CHANGED
                futures.add(executor.submit(CollectJarInputTask(
                        inputJar = file,
                        inputJarStatus = status,
                        inputToOutput = inputToOutput,
                        isIncremental = isIncremental,
                        traceClassFileOutput = traceClassDirectoryOutput,
                        legacyReplaceFile = legacyReplaceFile,

                        // result
                        resultOfDirInputToOut = dirInputOutMap,
                        resultOfJarInputToOut = jarInputOutMap
                )))
            }
        }

        for (future in futures) {
            future.get()
        }
        futures.clear()

        Log.i(TAG, "[doTransform] Step(1)[Parse]... cost:%sms", System.currentTimeMillis() - start)

        /**
         * step 2
         */
        start = System.currentTimeMillis()
        val methodCollector = MethodCollector(executor, mappingCollector, methodId, config, collectedMethodMap)

        methodCollector.collect(dirInputOutMap.keys, jarInputOutMap.keys)
        Log.i(TAG, "[doTransform] Step(2)[Collection]... cost:%sms", System.currentTimeMillis() - start)

        /**
         * step 3
         */
        start = System.currentTimeMillis()
        val methodTracer = MethodTracer(executor, mappingCollector, config, methodCollector.collectedMethodMap, methodCollector.collectedClassExtendMap)
        methodTracer.trace(dirInputOutMap, jarInputOutMap)
        Log.i(TAG, "[doTransform] Step(3)[Trace]... cost:%sms", System.currentTimeMillis() - start)

    }

    class ParseMappingTask
    constructor(
            private val mappingCollector: MappingCollector,
            private val collectedMethodMap: ConcurrentHashMap<String, TraceMethod>,
            private val methodId: AtomicInteger,
            private val config: Configuration
    ) : Runnable {

        override fun run() {
            val start = System.currentTimeMillis()

            val mappingFile = File(config.mappingDir, "mapping.txt")
            if (mappingFile.isFile) {
                val mappingReader = MappingReader(mappingFile)
                mappingReader.read(mappingCollector)
            }
            val size = config.parseBlockFile(mappingCollector)

            val baseMethodMapFile = File(config.baseMethodMapPath)
            getMethodFromBaseMethod(baseMethodMapFile, collectedMethodMap)
            retraceMethodMap(mappingCollector, collectedMethodMap)

            Log.i(TAG, "[ParseMappingTask#run] cost:%sms, black size:%s, collect %s method from %s",
                    System.currentTimeMillis() - start, size, collectedMethodMap.size, config.baseMethodMapPath)
        }

        private fun retraceMethodMap(
                processor: MappingCollector,
                methodMap: ConcurrentHashMap<String, TraceMethod>) {
            val retraceMethodMap = HashMap<String, TraceMethod>(methodMap.size)
            for (traceMethod in methodMap.values) {
                traceMethod.proguard(processor)
                retraceMethodMap[traceMethod.getMethodName()] = traceMethod
            }
            methodMap.clear()
            methodMap.putAll(retraceMethodMap)
            retraceMethodMap.clear()
        }

        private fun getMethodFromBaseMethod(
                baseMethodFile: File,
                collectedMethodMap: ConcurrentHashMap<String, TraceMethod>) {
            if (!baseMethodFile.exists()) {
                Log.w(TAG, "[getMethodFromBaseMethod] not exist!%s", baseMethodFile.absolutePath)
                return
            }

            try {
                Scanner(baseMethodFile, "UTF-8").use { fileReader ->
                    while (fileReader.hasNext()) {
                        var nextLine = fileReader.nextLine()
                        if (!Util.isNullOrNil(nextLine)) {
                            nextLine = nextLine.trim()
                            if (nextLine.startsWith("#")) {
                                Log.i("[getMethodFromBaseMethod] comment %s", nextLine)
                                continue
                            }
                            val fields = nextLine.split(",")
                            val traceMethod = TraceMethod()
                            traceMethod.id = Integer.parseInt(fields[0])
                            traceMethod.accessFlag = Integer.parseInt(fields[1])
                            val methodField = fields[2].split(" ")
                            traceMethod.className = methodField[0].replace("/", ".")
                            traceMethod.methodName = methodField[1]
                            if (methodField.size > 2) {
                                traceMethod.desc = methodField[2].replace("/", ".")
                            }
                            collectedMethodMap[traceMethod.getMethodName()] = traceMethod
                            if (methodId.get() < traceMethod.id && traceMethod.id != TraceBuildConstants.METHOD_ID_DISPATCH) {
                                methodId.set(traceMethod.id)
                            }

                        }
                    }
                }
            } catch (e: Throwable) {
                Log.printErrStackTrace(TAG, e, "")
            }
        }
    }


    class CollectDirectoryInputTask(
            private val directoryInput: File,
            private val mapOfChangedFiles: Map<File, Status>,
            private val mapOfInputToOutput: Map<File, File>,
            private val isIncremental: Boolean,
            private val traceClassDirectoryOutput: File,
            private val legacyReplaceChangedFile: ((File, Map<File, Status>) -> (Object))?,     // Will be removed in the future
            private val legacyReplaceFile: ((File, File) -> (Object))?,                         // Will be removed in the future

            private val resultOfDirInputToOut: MutableMap<File, File>
    ) : Runnable {

        override fun run() {
            try {
                handle()
            } catch (e: Exception) {
                e.printStackTrace()
                Log.e(TAG, "%s", e.toString())
            }
        }

        private fun handle() {
            val dirInput = directoryInput
            val dirOutput = if (mapOfInputToOutput.containsKey(dirInput)) {
                mapOfInputToOutput[dirInput]!!
            } else {
                File(traceClassDirectoryOutput, dirInput.name)
            }
            val inputFullPath = dirInput.absolutePath
            val outputFullPath = dirOutput.absolutePath

            if (!dirOutput.exists()) {
                dirOutput.mkdirs()
            }

            if (!dirInput.exists() && dirOutput.exists()) {
                if (dirOutput.isDirectory) {
                    FileUtils.deletePath(dirOutput)
                } else {
                    FileUtils.delete(dirOutput)
                }
            }

            if (isIncremental) {
                val outChangedFiles = HashMap<File, Status>()
                for ((changedFileInput, status) in mapOfChangedFiles) {
                    val changedFileInputFullPath = changedFileInput.absolutePath
                    val changedFileOutput = File(changedFileInputFullPath.replace(inputFullPath, outputFullPath))
                    if (status == Status.ADDED || status == Status.CHANGED) {
                        resultOfDirInputToOut[changedFileInput] = changedFileOutput
                    } else if (status == Status.REMOVED) {
                        changedFileOutput.delete()
                    }
                    outChangedFiles[changedFileOutput] = status
                }

                legacyReplaceChangedFile?.invoke(dirInput, outChangedFiles)
            } else {
                resultOfDirInputToOut[dirInput] = dirOutput
            }

            legacyReplaceFile?.invoke(dirInput, dirOutput)
        }
    }

    class CollectJarInputTask(
            private val inputJar: File,
            private val inputJarStatus: Status,
            private val inputToOutput: Map<File, File>,
            private val isIncremental: Boolean,
            private val traceClassFileOutput: File,
            private val legacyReplaceFile: ((File, File) -> (Object))?,             // Will be removed in the future
            private val resultOfDirInputToOut: MutableMap<File, File>,
            private val resultOfJarInputToOut: MutableMap<File, File>
    ) : Runnable {

        override fun run() {
            try {
                handle()
            } catch (e: Exception) {
                e.printStackTrace()
                Log.e(TAG, "%s", e.toString())
            }
        }

        private fun handle() {

            val jarInput = inputJar
            val jarOutput = if (inputToOutput.containsKey(jarInput)) {
                inputToOutput[jarInput]!!
            } else {
                File(traceClassFileOutput, getUniqueJarName(jarInput))
            }

            Log.d(TAG, "CollectJarInputTask input %s -> output %s", jarInput, jarOutput)

            if (!isIncremental && jarOutput.exists()) {
                jarOutput.delete()
            }
            if (!jarOutput.parentFile.exists()) {
                jarOutput.parentFile.mkdirs()
            }

            if (IOUtil.isRealZipOrJar(jarInput)) {
                if (isIncremental) {
                    if (inputJarStatus == Status.ADDED || inputJarStatus == Status.CHANGED) {
                        resultOfJarInputToOut[jarInput] = jarOutput
                    } else if (inputJarStatus == Status.REMOVED) {
                        jarOutput.delete()
                    }

                } else {
                    resultOfJarInputToOut[jarInput] = jarOutput
                }

            } else {

                // TODO for wechat
                Log.i(TAG, "Special case for WeChat AutoDex. Its rootInput jar file is actually a txt file contains path list.")
                // Special case for WeChat AutoDex. Its rootInput jar file is actually
                // a txt file contains path list.
                jarInput.inputStream().bufferedReader().useLines { lines ->
                    lines.forEach { realJarInputFullPath ->
                        val realJarInput = File(realJarInputFullPath)
                        // dest jar, moved to extra guard intermediate output dir.
                        val realJarOutput = File(traceClassFileOutput, getUniqueJarName(realJarInput))

                        if (realJarInput.exists() && IOUtil.isRealZipOrJar(realJarInput)) {
                            resultOfJarInputToOut[realJarInput] = realJarOutput
                        } else {
                            realJarOutput.delete()
                            if (realJarInput.exists() && realJarInput.isDirectory) {
                                val realJarOutputDir = File(traceClassFileOutput, realJarInput.name)
                                if (!realJarOutput.exists()) {
                                    realJarOutput.mkdirs()
                                }
                                resultOfDirInputToOut[realJarInput] = realJarOutputDir
                            }

                        }
                        // write real output full path to the fake jar at rootOutput.
                        jarOutput.outputStream().bufferedWriter().use { bw ->
                            bw.write(realJarOutput.absolutePath)
                            bw.newLine()
                        }
                    }
                }

                jarInput.delete() // delete raw inputList
            }

            legacyReplaceFile?.invoke(jarInput, jarOutput)
        }
    }


}

14.

15.

16.

17.

18.

19.

 类似资料: