graph LR
注册plugin-->CollectDirectoryInputTask-->MethodCollector
注册plugin-->CollectJarInputTask-->MethodCollector-->MethodTracer
解决了很多Matrix自身的问题,参考同目录下«Matrix接入后遇到的问题.md»
@Override
void apply(Project project) {
project.extensions.create("matrix", MatrixExtension)
project.matrix.extensions.create("trace", MatrixTraceExtension)
project.matrix.extensions.create("removeUnusedResources", MatrixDelUnusedResConfiguration)
if (!project.plugins.hasPlugin('com.android.application')) {
throw new GradleException('Matrix Plugin, Android Application plugin required')
}
project.afterEvaluate {
def android = project.extensions.android
def configuration = project.matrix
android.applicationVariants.all { variant ->
if (configuration.trace.enable) {
com.tencent.matrix.trace.transform.MatrixTraceTransform.inject(project, configuration.trace, variant.getVariantData().getScope())//main
}
if (configuration.removeUnusedResources.enable) {
if (Util.isNullOrNil(configuration.removeUnusedResources.variant) || variant.name.equalsIgnoreCase(configuration.removeUnusedResources.variant)) {
Log.i(TAG, "removeUnusedResources %s", configuration.removeUnusedResources)
RemoveUnusedResourcesTask removeUnusedResourcesTask = project.tasks.create("remove" + variant.name.capitalize() + "UnusedResources", RemoveUnusedResourcesTask)
removeUnusedResourcesTask.inputs.property(RemoveUnusedResourcesTask.BUILD_VARIANT, variant.name)
project.tasks.add(removeUnusedResourcesTask)
removeUnusedResourcesTask.dependsOn variant.packageApplication
variant.assemble.dependsOn removeUnusedResourcesTask
}
}
}
}
}
public static void inject(Project project, MatrixTraceExtension extension, VariantScope variantScope) {
GlobalScope globalScope = variantScope.getGlobalScope();
BaseVariantData variant = variantScope.getVariantData();
String mappingOut = Joiner.on(File.separatorChar).join(
String.valueOf(globalScope.getBuildDir()),
FD_OUTPUTS,
"mapping",
variantScope.getVariantConfiguration().getDirName());
String traceClassOut = Joiner.on(File.separatorChar).join(
String.valueOf(globalScope.getBuildDir()),
FD_OUTPUTS,
"traceClassOut",
variantScope.getVariantConfiguration().getDirName());
Configuration config = new Configuration.Builder()
.setPackageName(variant.getApplicationId())
.setBaseMethodMap(extension.getBaseMethodMapFile())
.setBlackListFile(extension.getBlackListFile())
.setMethodMapFilePath(mappingOut + "/methodMapping.txt")//main
.setIgnoreMethodMapFilePath(mappingOut + "/ignoreMethodMapping.txt")
.setMappingPath(mappingOut)
.setTraceClassOut(traceClassOut)
.build();
try {
String[] hardTask = getTransformTaskName(extension.getCustomDexTransformName(), variant.getName());
for (Task task : project.getTasks()) {
for (String str : hardTask) {
if (task.getName().equalsIgnoreCase(str) && task instanceof TransformTask) {
TransformTask transformTask = (TransformTask) task;
Log.i(TAG, "successfully inject task:" + transformTask.getName());
Field field = TransformTask.class.getDeclaredField("transform");
field.setAccessible(true);
field.set(task, new MatrixTraceTransform(config, transformTask.getTransform()));
break;
}
}
private static String[] getTransformTaskName(String customDexTransformName, String buildTypeSuffix) {
if (!Util.isNullOrNil(customDexTransformName)) {
return new String[]{customDexTransformName + "For" + buildTypeSuffix};
} else {
String[] names = new String[]{
"transformClassesWithDexBuilderFor" + buildTypeSuffix,
"transformClassesWithDexFor" + buildTypeSuffix,
};
return names;
}
}
MatrixTraceTransform.transform
@Override
public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation);
long start = System.currentTimeMillis();
try {
doTransform(transformInvocation); // hack,main
} catch (ExecutionException e) {
e.printStackTrace();
}
long cost = System.currentTimeMillis() - start;
long begin = System.currentTimeMillis();
origTransform.transform(transformInvocation);
long origTransformCost = System.currentTimeMillis() - begin;
Log.i("Matrix." + getName(), "[transform] cost time: %dms %s:%sms MatrixTraceTransform:%sms", System.currentTimeMillis() - start, origTransform.getClass().getSimpleName(), origTransformCost, cost);
}
private void doTransform(TransformInvocation transformInvocation) throws ExecutionException, InterruptedException {
final boolean isIncremental = transformInvocation.isIncremental() && this.isIncremental();
/**
\* step 1
*/
long start = System.currentTimeMillis();
List<Future> futures = new LinkedList<>();
final MappingCollector mappingCollector = new MappingCollector();
final AtomicInteger methodId = new AtomicInteger(0);
final ConcurrentHashMap<String, TraceMethod> collectedMethodMap = new ConcurrentHashMap<>();
futures.add(executor.submit(new ParseMappingTask(mappingCollector, collectedMethodMap, methodId)));//main
Map<File, File> dirInputOutMap = new ConcurrentHashMap<>();
Map<File, File> jarInputOutMap = new ConcurrentHashMap<>();
Collection<TransformInput> inputs = transformInvocation.getInputs();
for (TransformInput input : inputs) {
for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
futures.add(executor.submit(new CollectDirectoryInputTask(dirInputOutMap, directoryInput, isIncremental)));
}
for (JarInput inputJar : input.getJarInputs()) {
futures.add(executor.submit(new CollectJarInputTask(inputJar, isIncremental, jarInputOutMap, dirInputOutMap)));
}
}
for (Future future : futures) {
future.get();
}
futures.clear();
Log.i(TAG, "[doTransform] Step(1)[Parse]... cost:%sms", System.currentTimeMillis() - start);
/**
\* step 2
*/
start = System.currentTimeMillis();
//main
MethodCollector methodCollector = new MethodCollector(executor, mappingCollector, methodId, config, collectedMethodMap);
methodCollector.collect(dirInputOutMap.keySet(), jarInputOutMap.keySet());
Log.i(TAG, "[doTransform] Step(2)[Collection]... cost:%sms", System.currentTimeMillis() - start);
/**
\* step 3
*/
start = System.currentTimeMillis();
MethodTracer methodTracer = new MethodTracer(executor, mappingCollector, config, methodCollector.getCollectedMethodMap(), methodCollector.getCollectedClassExtendMap());
methodTracer.trace(dirInputOutMap, jarInputOutMap);
Log.i(TAG, "[doTransform] Step(3)[Trace]... cost:%sms", System.currentTimeMillis() - start);
}
private class ParseMappingTask implements Runnable {
@Override
public void run() {
try {
long start = System.currentTimeMillis();
File mappingFile = new File(config.mappingDir, "mapping.txt");
if (mappingFile.exists() && mappingFile.isFile()) {
MappingReader mappingReader = new MappingReader(mappingFile);
mappingReader.read(mappingCollector);
}
int size = config.parseBlackFile(mappingCollector);
File baseMethodMapFile = new 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);
} catch (IOException e) {
e.printStackTrace();
}
}
public void collect(Set<File> srcFolderList, Set<File> dependencyJarList) throws ExecutionException, InterruptedException {
List<Future> futures = new LinkedList<>();
for (File srcFile : srcFolderList) {
ArrayList<File> classFileList = new ArrayList<>();
if (srcFile.isDirectory()) {
listClassFiles(classFileList, srcFile);
} else {
classFileList.add(srcFile);
}
for (File classFile : classFileList) {
futures.add(executor.submit(new CollectSrcTask(classFile)));
}
}
for (File jarFile : dependencyJarList) {
futures.add(executor.submit(new CollectJarTask(jarFile)));
}
for (Future future : futures) {
future.get();
}
futures.clear();
futures.add(executor.submit(new Runnable() {
@Override
public void run() {
saveIgnoreCollectedMethod(mappingCollector);
}
}));
futures.add(executor.submit(new Runnable() {
@Override
public void run() {
saveCollectedMethod(mappingCollector);
}
}));
for (Future future : futures) {
future.get();
}
futures.clear();
class CollectJarTask implements Runnable {
@Override
public void run() {
ZipFile zipFile = null;
try {
zipFile = new ZipFile(fromJar);
Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
while (enumeration.hasMoreElements()) {
ZipEntry zipEntry = enumeration.nextElement();
String zipEntryName = zipEntry.getName();
if (isNeedTraceFile(zipEntryName)) {
InputStream inputStream = zipFile.getInputStream(zipEntry);
ClassReader classReader = new ClassReader(inputStream);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor visitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);//main
classReader.accept(visitor, 0);
private class TraceClassAdapter extends ClassVisitor {
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if (isABSClass) {
return super.visitMethod(access, name, desc, signature, exceptions);
} else {
if (!hasWindowFocusMethod) {
hasWindowFocusMethod = isWindowFocusChangeMethod(name, desc);
}
return new CollectMethodNode(className, access, name, desc, signature, exceptions);
}
}
private class CollectMethodNode extends MethodNode {
@Override
public void visitEnd() {
super.visitEnd();
if (isNeedTrace && !collectedMethodMap.containsKey(traceMethod.getMethodName())) {
traceMethod.id = methodId.incrementAndGet();
collectedMethodMap.put(traceMethod.getMethodName(), traceMethod);
incrementCount.incrementAndGet();
private void saveCollectedMethod(MappingCollector mappingCollector) {
File methodMapFile = new File(configuration.methodMapFilePath);
List<TraceMethod> methodList = new ArrayList<>();
TraceMethod extra = TraceMethod.create(TraceBuildConstants.METHOD_ID_DISPATCH, Opcodes.ACC_PUBLIC, "android.os.Handler",
"dispatchMessage", "(Landroid.os.Message;)V");
collectedMethodMap.put(extra.getMethodName(), extra);//main
methodList.addAll(collectedMethodMap.values());
Log.i(TAG, "[saveCollectedMethod] size:%s incrementCount:%s path:%s", collectedMethodMap.size(), incrementCount.get(), methodMapFile.getAbsolutePath());
Collections.sort(methodList, new Comparator<TraceMethod>() {
@Override
public int compare(TraceMethod o1, TraceMethod o2) {
return o1.id - o2.id;
}
});
...
FileOutputStream fileOutputStream = new FileOutputStream(methodMapFile, false);
Writer w = new OutputStreamWriter(fileOutputStream, "UTF-8");
pw = new PrintWriter(w);
for (TraceMethod traceMethod : methodList) {
traceMethod.revert(mappingCollector);
pw.println(traceMethod.toString());
}