diff --git a/README.md b/README.md index f89bcb7..1e930cb 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ #HiBeaver +By applying the regular expression and wildcard feature, HiBeaver now has been upgraded to an Android lightweight AOP design tool. + ![cute animals always busy in building their river dam](https://github.com/BryanSharp/hibeaver/blob/master/beaver.jpeg?raw=true) Beaver means 河狸 in Chinese, cute animals always busy in building their cute river dam. -HiBeaver is an Android plugin for modifying your library jars byte code. +Basically, HiBeaver is an Android plugin for modifying your java byte code during the building of your package. Dress them as if they are naked! Yeah~ @@ -16,7 +18,6 @@ This plugin has been uploaded to jcenter. You can use this by adding the followi and then add this to you app build scripts: - apply plugin: 'hiBeaver' import com.bryansharp.gradle.hibeaver.utils.MethodLogAdapter import org.objectweb.asm.ClassVisitor import org.objectweb.asm.MethodVisitor @@ -32,17 +33,70 @@ and then add this to you app build scripts: keepQuiet = false //this is a kit feature of the plugin, set it true to see the time consume of this build watchTimeConsume = false - + //this is the most important part + //structure is like ['class':[[:],[:]],'class':[[:],[:]]], type is Map>> modifyMatchMaps = [ - 'the classname of which you want to modify': [ + 'classname of which to be modified': [ // you can use javap -s command to get the description of one method - ['methodName': 'name of the method', 'methodDesc': 'method description', 'adapter': { + // the adapter is a closure + ['methodName': 'the name of the method', 'methodDesc': 'javap -s to get the description', 'adapter': { + //the below args cannot be changed, to copy them entirely with nothing changed is recommended + ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions -> + //return null to modify nothing + return null; + }] + , + ['methodName': 'the name of the method2', 'methodDesc': 'javap -s to get the description', 'adapter': { ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions -> - //return the following part to check the method byte code - return new MethodLogAdapter(cv.visitMethod(access, name, desc, signature, exceptions)); + return null; }] - ], + ] + , + //the latter ones are advanced cases + '*Activity' : [ + //the value of classMatchType can either be one of the three: all,regEx,wildcard + //default value is all + 'classMatchType': 'wildcard', + 'modifyMethods' : [ + //methodMatchType会同时对methodName和methodDesc的匹配生效 + //methodDesc设置为空代表对methodDesc不进行限制 + ['methodName': 'on**', 'methodMatchType': 'wildcard', 'methodDesc': null, 'adapter': { + ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions -> + MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions); + MethodVisitor adapter = new MethodLogAdapter(methodVisitor) { + @Override + void visitCode() { + super.visitCode(); + methodVisitor.visitLdcInsn(desc); + methodVisitor.visitLdcInsn(name); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "bruce/com/testhibeaver/MainActivity", "hookXM", "(Ljava/lang/Object;Ljava/lang/Object;)V"); + } + } + return adapter; + }] + ] + ] + , + '.*D[a-zA-Z]*Receiver' : [ + 'classMatchType': 'regEx', + 'modifyMethods' : [ + ['methodName': 'on**', 'methodMatchType': 'wildcard', 'methodDesc': null, 'adapter': { + ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions -> + MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions); + MethodVisitor adapter = new MethodLogAdapter(methodVisitor) { + @Override + void visitCode() { + super.visitCode(); + methodVisitor.visitLdcInsn(desc); + methodVisitor.visitLdcInsn(name); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "bruce/com/testhibeaver/MainActivity", "hookXM", "(Ljava/lang/Object;Ljava/lang/Object;)V"); + } + } + return adapter; + }] + ] + ] ] } @@ -50,12 +104,4 @@ You can also see the content above in the build log outputs. There is also a demo showing how to use it. You can either get it through git submodule and add a settings.gradle file to include the module, or get it by checking out [hiBeaverDemo](https://github.com/BryanSharp/hiBeaverDemo). -This plugin is also uploaded to jitpack. You can use jitpack version by adding the following code to your buildScripts: - - classpath 'com.github.BryanSharp:hibeaver:1.2.1' - -using jitpack version, its Maven Repo is needed, add this as well: - - maven { url 'https://jitpack.io' } - Hope you can enjoy it! Any comment and suggestion is welcomed. diff --git a/src/main/groovy/com/bryansharp/gradle/hibeaver/HiBeaverParams.groovy b/src/main/groovy/com/bryansharp/gradle/hibeaver/HiBeaverParams.groovy index 89cb98a..892af00 100644 --- a/src/main/groovy/com/bryansharp/gradle/hibeaver/HiBeaverParams.groovy +++ b/src/main/groovy/com/bryansharp/gradle/hibeaver/HiBeaverParams.groovy @@ -6,5 +6,5 @@ public class HiBeaverParams { boolean watchTimeConsume = false boolean keepQuiet = false boolean showHelp = true - Map>> modifyMatchMaps = [:] + Map modifyMatchMaps = [:] } \ No newline at end of file diff --git a/src/main/groovy/com/bryansharp/gradle/hibeaver/InjectTransform.groovy b/src/main/groovy/com/bryansharp/gradle/hibeaver/InjectTransform.groovy index e4e2368..07a1d23 100644 --- a/src/main/groovy/com/bryansharp/gradle/hibeaver/InjectTransform.groovy +++ b/src/main/groovy/com/bryansharp/gradle/hibeaver/InjectTransform.groovy @@ -5,9 +5,11 @@ import com.android.annotations.Nullable import com.android.build.api.transform.* import com.android.build.gradle.AppExtension import com.android.build.gradle.internal.pipeline.TransformManager +import com.bryansharp.gradle.hibeaver.utils.Const import com.bryansharp.gradle.hibeaver.utils.DataHelper import com.bryansharp.gradle.hibeaver.utils.Log import com.bryansharp.gradle.hibeaver.utils.ModifyClassUtil +import com.bryansharp.gradle.hibeaver.utils.Util import groovy.io.FileType import org.apache.commons.codec.digest.DigestUtils import org.apache.commons.io.FileUtils @@ -27,8 +29,9 @@ import java.util.zip.ZipEntry * introduction: */ public class InjectTransform extends Transform { + static AppExtension android - static HashSet targetClasses = []; + static Map targetClasses = [:]; private static Project project; public InjectTransform(Project project) { @@ -66,13 +69,25 @@ public class InjectTransform extends Transform { android = project.extensions.getByType(AppExtension) // String flavorAndBuildType = context.name.split("For")[1] // Log.info("flavorAndBuildType ${flavorAndBuildType}") - targetClasses = []; - Map>> modifyMatchMaps = project.hiBeaver.modifyMatchMaps; + targetClasses = [:]; + Map modifyMatchMaps = project.hiBeaver.modifyMatchMaps; if (modifyMatchMaps != null) { - targetClasses.addAll(modifyMatchMaps.keySet()); + def set = modifyMatchMaps.entrySet(); + for (Map.Entry> entry : set) { + def value = entry.getValue() + if (value) { + int type; + if (value instanceof Map) { + type = Util.typeString2Int(value.get(Const.KEY_CLASSMATCHTYPE)); + } else { + type = Const.MT_FULL; + } + targetClasses.put(entry.getKey(), type) + } + } } /** - * 获取所有依赖的classPaths + * 获取所有依赖的classPaths,仅做备用 */ def classPaths = [] String buildTypes @@ -155,6 +170,7 @@ public class InjectTransform extends Transform { } } + private static void saveModifiedJarForCheck(File optJar) { File dir = DataHelper.ext.hiBeaverDir; File checkJarFile = new File(dir, optJar.getName()); @@ -164,11 +180,33 @@ public class InjectTransform extends Transform { FileUtils.copyFile(optJar, checkJarFile); } - static boolean shouldModifyClass(String className) { + static String shouldModifyClass(String className) { if (project.hiBeaver.enableModify) { - return targetClasses.contains(className) + def set = targetClasses.entrySet(); + for (Map.Entry entry : set) { + def mt = entry.getValue(); + String key = entry.getKey() + switch (mt) { + case Const.MT_FULL: + if (className.equals(key)) { + return key; + } + break; + case Const.MT_REGEX: + if (Util.regMatch(key, className)) { + return key; + } + break; + case Const.MT_WILDCARD: + if (Util.wildcardMatch(key, className)) { + return key; + } + break; + } + } + return null; } else { - return false; + return null; } } @@ -187,7 +225,7 @@ public class InjectTransform extends Transform { * 读取原jar */ def file = new JarFile(jarFile); - def modifyMatchMaps = project.hiBeaver.modifyMatchMaps + Map modifyMatchMaps = project.hiBeaver.modifyMatchMaps Enumeration enumeration = file.entries(); while (enumeration.hasMoreElements()) { JarEntry jarEntry = (JarEntry) enumeration.nextElement(); @@ -204,8 +242,9 @@ public class InjectTransform extends Transform { byte[] sourceClassBytes = IOUtils.toByteArray(inputStream); if (entryName.endsWith(".class")) { className = path2Classname(entryName) - if (modifyMatchMaps != null && shouldModifyClass(className)) { - modifiedClassBytes = ModifyClassUtil.modifyClasses(className, sourceClassBytes, modifyMatchMaps.get(className)); + String key = shouldModifyClass(className) + if (modifyMatchMaps != null && key != null) { + modifiedClassBytes = ModifyClassUtil.modifyClasses(className, sourceClassBytes, modifyMatchMaps.get(key)); } } if (modifiedClassBytes == null) { @@ -231,10 +270,11 @@ public class InjectTransform extends Transform { File modified; try { String className = path2Classname(classFile.absolutePath.replace(dir.absolutePath + File.separator, "")); - def modifyMatchMaps = project.hiBeaver.modifyMatchMaps + Map modifyMatchMaps = project.hiBeaver.modifyMatchMaps byte[] sourceClassBytes = IOUtils.toByteArray(new FileInputStream(classFile)); - if (shouldModifyClass(className)) { - byte[] modifiedClassBytes = ModifyClassUtil.modifyClasses(className, sourceClassBytes, modifyMatchMaps.get(className)); + String key = shouldModifyClass(className) + if (key != null) { + byte[] modifiedClassBytes = ModifyClassUtil.modifyClasses(className, sourceClassBytes, modifyMatchMaps.get(key)); if (modifiedClassBytes) { modified = new File(tempDir, className.replace('.', '') + '.class') if (modified.exists()) { @@ -269,7 +309,7 @@ public class InjectTransform extends Transform { String className if (entryName.endsWith(".class")) { className = entryName.replace("/", ".").replace(".class", "") - if (shouldModifyClass(className)) { + if (shouldModifyClass(className) != null) { modified = true; } } diff --git a/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/Const.java b/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/Const.java new file mode 100644 index 0000000..cbbd08d --- /dev/null +++ b/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/Const.java @@ -0,0 +1,20 @@ +package com.bryansharp.gradle.hibeaver.utils; + +/** + * Created by bsp on 17/3/6. + */ + +public interface Const { + int MT_FULL = 0; + int MT_WILDCARD = 1; + int MT_REGEX = 2; + String KEY_CLASSMATCHTYPE="classMatchType"; + String KEY_MODIFYMETHODS="modifyMethods"; + String KEY_METHODNAME="methodName"; + String KEY_METHODMATCHTYPE="methodMatchType"; + String KEY_METHODDESC="methodDesc"; + String KEY_ADAPTER="adapter"; + String VALUE_WILDCARD="wildcard"; + String VALUE_REGEX="regEx"; + String VALUE_ALL="all"; +} diff --git a/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/Log.groovy b/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/Log.groovy index 70c7a7c..cdccb97 100644 --- a/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/Log.groovy +++ b/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/Log.groovy @@ -121,7 +121,8 @@ public class Log { map.entrySet().each { entry -> if ((entry.getKey().intValue() & access) > 0) { - builder.append('|' + entry.getValue() + '|'); + //此处如果使用|作为分隔符会导致编译报错 因此改用斜杠 + builder.append('\\' + entry.getValue() + '/ '); } } return builder.toString(); diff --git a/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/ModifyClassUtil.groovy b/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/ModifyClassUtil.groovy index 3f28cd0..144fed9 100644 --- a/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/ModifyClassUtil.groovy +++ b/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/ModifyClassUtil.groovy @@ -12,17 +12,20 @@ import org.objectweb.asm.* public class ModifyClassUtil { public - static byte[] modifyClasses(String className, byte[] srcByteCode, List> methodMatchMaps) { + static byte[] modifyClasses(String className, byte[] srcByteCode, Object container) { + List> methodMatchMaps = getList(container); byte[] classBytesCode = null; - try { - Log.info("====start modifying ${className}===="); - classBytesCode = modifyClass(srcByteCode, methodMatchMaps); - Log.info("====revisit modified ${className}===="); - onlyVisitClassMethod(classBytesCode, methodMatchMaps); - Log.info("====finish modifying ${className}===="); - return classBytesCode; - } catch (Exception e) { - e.printStackTrace(); + if (methodMatchMaps) { + try { + Log.info("====start modifying ${className}===="); + classBytesCode = modifyClass(srcByteCode, methodMatchMaps); + Log.info("====revisit modified ${className}===="); + onlyVisitClassMethod(classBytesCode, methodMatchMaps); + Log.info("====finish modifying ${className}===="); + return classBytesCode; + } catch (Exception e) { + e.printStackTrace(); + } } if (classBytesCode == null) { classBytesCode = srcByteCode; @@ -30,6 +33,14 @@ public class ModifyClassUtil { return classBytesCode; } + static List> getList(Object container) { + if (container instanceof List) { + return container; + } else if (container instanceof Map) { + return (List>) container.get(Const.KEY_MODIFYMETHODS); + } + return null; + } private static byte[] modifyClass(byte[] srcClass, List> modifyMatchMaps) throws IOException { @@ -120,13 +131,15 @@ public class ModifyClassUtil { } methodMatchMaps.each { Map map -> - String metName = map.get('methodName'); - String methodDesc = map.get('methodDesc'); - if (name.equals(metName)) { - Closure visit = map.get('adapter'); + String metName = map.get(Const.KEY_METHODNAME); + String metMatchType = map.get(Const.KEY_METHODMATCHTYPE); + String methodDesc = map.get(Const.KEY_METHODDESC); + if (Util.isPatternMatch(metName, metMatchType, name)) { + Closure visit = map.get(Const.KEY_ADAPTER); if (visit != null) { + //methodDesc 不设置,为空,即代表对methodDesc不限制 if (methodDesc != null) { - if (methodDesc.equals(desc)) { + if (Util.isPatternMatch(methodDesc, metMatchType, desc)) { if (onlyVisit) { myMv = new MethodLogAdapter(cv.visitMethod(access, name, desc, signature, exceptions)); } else { diff --git a/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/Util.groovy b/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/Util.groovy new file mode 100644 index 0000000..79d0cbc --- /dev/null +++ b/src/main/groovy/com/bryansharp/gradle/hibeaver/utils/Util.groovy @@ -0,0 +1,130 @@ +package com.bryansharp.gradle.hibeaver.utils + +import java.util.regex.Pattern + +/** + * Created by bryansharp(bsp0911932@163.com) on 2016/5/10. + * + * @author bryansharp + * Project: FirstGradle + * introduction: + */ +public class Util { + public static boolean regMatch(String pattern, String target) { + if (isEmpty(pattern) || isEmpty(target)) { + return false; + } + return Pattern.matches(pattern, target); + } + + public static int typeString2Int(String type) { + if (type == null || Const.VALUE_ALL.equals(type)) { + return Const.MT_FULL; + } else if (Const.VALUE_REGEX.equals(type)) { + return Const.MT_REGEX; + } else if (Const.VALUE_WILDCARD.equals(type)) { + return Const.MT_WILDCARD; + } else { + return Const.MT_FULL; + } + } + + public static boolean isPatternMatch(String pattern, String type, String target) { + if (isEmpty(pattern) || isEmpty(target)) { + return false; + } + int intType = typeString2Int(type); + switch (intType) { + case Const.MT_FULL: + if (target.equals(pattern)) { + return true; + } + break; + case Const.MT_REGEX: + if (regMatch(pattern, target)) { + return true; + } + break; + case Const.MT_WILDCARD: + if (wildcardMatch(pattern, target)) { + return true; + } + break; + } + return false; + } + + public static boolean isEmpty(String text) { + return text == null || text.trim().length() < 1; + } + + public static boolean isNotEmpty(String text) { + return !isEmpty(text); + } + /** + * 星号匹配 + * @param pattern + * @param target + * @return + * new StringBuilder() + .append(wildcardMatch("com.**.act.*.github.*Activity", "com.jj.act.jj.github.MainActivity")).append(",") //true + .append(wildcardMatch("*Activity", "com.jj.act.jj.github.MainActivity")).append(",")//true + .append(wildcardMatch("*Activity", "com.jj.act.jjActivity")).append(",")//true + .append(wildcardMatch("*Activity*", "com.jj.act.jjActivity")).append(",")//false + .append(wildcardMatch(".*Activity", "com.Activity")).append(",")//false + .append(wildcardMatch("com.**.a*t.*.github.*Activity", "com.jj.act.jj.github.MainActivity")).append(",")//true + .append(wildcardMatch("com.**.act.*.gi*ub.*Act*vity", "com.jj.MainActivity.act")).append(",")//false + .append(wildcardMatch("com.**.act.*.gi*ub.*Act*vity", "com.jj.act.jj.github.Mactivity")).append(",")//false + .toString() + */ + public static boolean wildcardMatch(String pattern, String target) { + if (isEmpty(pattern) || isEmpty(target)) { + return false; + } + try { + String[] split = pattern.split("\\*{1,3}"); + //如果以分隔符开头和结尾,第一位会为空字符串,最后一位不会为空字符,所以*Activity和*Activity*的分割结果一样 + if (pattern.endsWith("*")) {//因此需要在结尾拼接一个空字符 + List strings = new LinkedList<>(Arrays.asList(split)); + strings.add(""); + split = new String[strings.size()]; + strings.toArray(split); + } + for (int i = 0; i < split.length; i++) { + String part = split[i]; + if (isEmpty(target)) { + return false; + } + if (i == 0 && isNotEmpty(part)) { + if (!target.startsWith(part)) { + return false; + } + } + if (i == split.length - 1 && isNotEmpty(part)) { + if (!target.endsWith(part)) { + return false; + } else { + return true; + } + } + if (part == null || part.trim().length() < 1) { + continue; + } + int index = target.indexOf(part); + if (index < 0) { + return false; + } + int newStart = index + part.length() + 1; + if (newStart < target.length()) { + target = target.substring(newStart); + } else { + target = ""; + } + } + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } +} \ No newline at end of file diff --git a/src/main/resources/helpContent.groovy b/src/main/resources/helpContent.groovy index 6b56d12..3a3ae13 100644 --- a/src/main/resources/helpContent.groovy +++ b/src/main/resources/helpContent.groovy @@ -4,6 +4,7 @@ // You can turn Help Content output off by setting the showHelp flag false // hi, 这里是HiBeaver帮助内容,你可以直接把这整个内容复制到build.gradle中,然后解除注释作为初始设置 // 如果嫌烦可以在下面配置showHelp = false 来关闭这个帮助内容的输出 +// hiBeaver现在支持轻量级AOP配置,欢迎尝鲜 // //import com.bryansharp.gradle.hibeaver.utils.MethodLogAdapter //import org.objectweb.asm.ClassVisitor @@ -40,23 +41,49 @@ // }] // ] // , -// //the next part is an real case -// 'okhttp3.internal.http.HttpEngine': [ -// ['methodName': '', 'methodDesc': null, 'adapter': { -// ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions -> -// MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions); -// MethodVisitor adapter = new MethodLogAdapter(methodVisitor) { -// @Override -// void visitCode() { -// super.visitCode(); -// methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); -// methodVisitor.visitVarInsn(Opcodes.ALOAD, 2); -// //there is an convenient util MethodLogAdapter.className2Path("bruce.com.testhibeaver.MainActivity") -// methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "bruce/com/testhibeaver/MainActivity", "hookXM", "(Ljava/lang/Object;Ljava/lang/Object;)V"); -// } -// } -// return adapter; -// }] +// //the latter ones are advanced cases +// '*Activity' : [ +// //the value of classMatchType can either be one of the three: all,regEx,wildcard +// //default value is all +// 'classMatchType': 'wildcard', +// 'modifyMethods' : [ +// //methodMatchType会同时对methodName和methodDesc的匹配生效 +// //methodDesc设置为空代表对methodDesc不进行限制 +// ['methodName': 'on**', 'methodMatchType': 'wildcard', 'methodDesc': null, 'adapter': { +// ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions -> +// MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions); +// MethodVisitor adapter = new MethodLogAdapter(methodVisitor) { +// @Override +// void visitCode() { +// super.visitCode(); +// methodVisitor.visitLdcInsn(desc); +// methodVisitor.visitLdcInsn(name); +// methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "bruce/com/testhibeaver/MainActivity", "hookXM", "(Ljava/lang/Object;Ljava/lang/Object;)V"); +// } +// } +// return adapter; +// }] +// ] +// ] +// , +// '.*D[a-zA-Z]*Receiver' : [ +// 'classMatchType': 'regEx', +// 'modifyMethods' : [ +// ['methodName': 'on**', 'methodMatchType': 'wildcard', 'methodDesc': null, 'adapter': { +// ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions -> +// MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions); +// MethodVisitor adapter = new MethodLogAdapter(methodVisitor) { +// @Override +// void visitCode() { +// super.visitCode(); +// methodVisitor.visitLdcInsn(desc); +// methodVisitor.visitLdcInsn(name); +// methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "bruce/com/testhibeaver/MainActivity", "hookXM", "(Ljava/lang/Object;Ljava/lang/Object;)V"); +// } +// } +// return adapter; +// }] +// ] // ] // ] //} diff --git a/submodule b/submodule index 2a8693c..fe1682a 160000 --- a/submodule +++ b/submodule @@ -1 +1 @@ -Subproject commit 2a8693c258cd94a6f0774d5a244c73590b862198 +Subproject commit fe1682aaae1e5f8818e5ff230c03e6f042546f20