MatrixGradlePlugin

主流程

graph LR
注册plugin-->CollectDirectoryInputTask-->MethodCollector
注册plugin-->CollectJarInputTask-->MethodCollector-->MethodTracer

解决了很多Matrix自身的问题,参考同目录下«Matrix接入后遇到的问题.md»

MatrixPlugin.apply

@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
         }
       }
     }
   }
 }

MatrixTraceTransform.inject

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);

 }

ParseMappingTask

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();
   }
 }

MethodCollector.collect

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();
CollectJarTask
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);
TraceClassAdapter
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();
saveCollectedMethod
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());
 }