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.