Android 源码系列之<十八>自定义Gradle Plugin,优雅的解决第三方Jar包中的bug<中>
来源:互联网 发布:软件就业前景 编辑:程序博客网 时间:2024/05/16 15:34
转载请注明出处:http://blog.csdn.net/llew2011/article/details/78548660
在上篇文章Android 源码系列之<十七>自定义Gradle Plugin,优雅的解决第三方Jar包中的bug<上>中由于篇幅原因我们主要讲解了如何创建自定义Gradle Plugin以及修复第三方Jar包中的bug的思路,如果你还没看过上篇文章,强烈建议阅读一下。这篇文章就带领小伙伴们借助Javassist开源库实现对class文件的修改。
上篇文章中我们讲到了修改第三方Jar包的时机是在BytecodeFixTransform的transform()方法中,也就是在待修改Jar包在被拷贝目标文件夹之前先做修改,修改完成之后我们直接把修改过的Jar包拷贝进目标文件夹而不是原来的Jar包。既然要修改Jar包里的class文件,我们就要知道是哪一个class需要修复,然后还要是class里边的哪一个方法需要修复,还要清楚要修复的内容是什么等等,因此我定义一个BytecodeFixExtension类来表示修复配置,如下所示:
package com.llew.bytecode.fix.extensionpublic class BytecodeFixExtension { /** * 字节码修复插件是否可用,默认可用 */ boolean enable = true /** * 是否开启日志功能,默认开启 */ boolean logEnable = true /** * 是否保留修复过的jar文件,默认保留 */ boolean keepFixedJarFile = true /** * 时候保留修复过的class文件,默认保留 */ boolean keepFixedClassFile = true /** * 构建字节码所依赖的第三方包绝对路径,默认包含了Android.jar文件 */ ArrayList<String> dependencies = new ArrayList<String>() /** * 配置文件集合,配置格式:className##methodName(param1,param2...paramN)##injectValue##injectLine */ ArrayList<String> fixConfig = new ArrayList<>(); // 省略了setters and getters 方法}
在BytecodeFixExtension中需要注意dependencies和fixConfig的配置。dependencies表示在利用Javassist修复class文件时所依赖的Jar包,例如修复上篇文章中提到的getMobileAPInfo()方法就需要引入ContextCompat类,因此需要添加ContextCompat所在Jar包的绝对路径。fixConfig表示修复信息集合,它的格式是固定的,必须以##做分隔符,格式如下:className##methodName(param1,param2...paramN)##injectValue##injectLine,具体字段说明如下所示:
- className:表示全类名
例如:com.tencent.av.sdk.NetworkHelp - methodName(param1,param2...paramN):表示方法名及相关参数,参数只写类型且必须以逗号(,)分隔,非基础数据类型要写全路径
例如:getAPInfo(android.content.Context)
例如:getAPInfo(android.content.Context, int) - injectValue:表示待插入代码块,注意代码块要有分号(;),其中$0表示this;$1表示第一个参数;$2表示第二个参数;以此类推
例如:$1 = null;System.out.println("I have hooked this method by BytecodeFixer Plugin !!!");
$1 = null;就是表示把第一个参数置空;接着是打印一句日志
【注意:】如果injectValue为{}表示给原有方法添加try-catch操作 - injectLine:表示插在方法中的哪一行,该参数可选,如果省略该参数则默认把injectValue插在方法的最开始处
injectLine > 0 插入具体行数
injectLine = 0 插入方法最开始处
injectLine < 0 替换方法体
package com.llew.bytecode.fix.injectorimport com.llew.bytecode.fix.extension.BytecodeFixExtensionimport com.llew.bytecode.fix.task.BuildJarTaskimport com.llew.bytecode.fix.utils.FileUtilsimport com.llew.bytecode.fix.utils.Loggerimport com.llew.bytecode.fix.utils.TextUtilimport javassist.ClassPoolimport javassist.CtClassimport javassist.CtMethodimport org.gradle.api.Projectimport java.util.jar.JarFileimport java.util.zip.ZipFilepublic class BytecodeFixInjector { private static final String INJECTOR = "injector" private static final String JAVA = ".java" private static final String CLASS = ".class" private static final String JAR = ".jar" private static ClassPool sClassPool private static BytecodeFixInjector sInjector private Project mProject private String mVersionName private BytecodeFixExtension mExtension private BytecodeFixInjector(Project project, String versionName, BytecodeFixExtension extension) { this.mProject = project this.mVersionName = versionName this.mExtension = extension appendClassPath() } public static void init(Project project, String versionName, BytecodeFixExtension extension) { sClassPool = ClassPool.default sInjector = new BytecodeFixInjector(project, versionName, extension) } public static BytecodeFixInjector getInjector() { if (null == sInjector) { throw new IllegalAccessException("init() hasn't bean called !!!") } return sInjector } public synchronized File inject(File jar) { File destFile = null if (null == mExtension) { Logger.e("can't find bytecodeFixConfig in your app build.gradle !!!") return destFile } if (null == jar) { Logger.e("jar File is null before injecting !!!") return destFile } if (!jar.exists()) { Logger.e(jar.name + " not exits !!!") return destFile } try { ZipFile zipFile = new ZipFile(jar) zipFile.close() zipFile = null } catch (Exception e) { Logger.e(jar.name + " not a valid jar file !!!") return destFile } def jarName = jar.name.substring(0, jar.name.length() - JAR.length()) def baseDir = new StringBuilder().append(mProject.projectDir.absolutePath) .append(File.separator).append(INJECTOR) .append(File.separator).append(mVersionName) .append(File.separator).append(jarName).toString() File rootFile = new File(baseDir) FileUtils.clearFile(rootFile) rootFile.mkdirs() File unzipDir = new File(rootFile, "classes") File jarDir = new File(rootFile, "jar") JarFile jarFile = new JarFile(jar) mExtension.fixConfig.each { config -> if (!TextUtil.isEmpty(config.trim())) { // com.tencent.av.sdk.NetworkHelp##getAPInfo(android.content.Context)##if(Boolean.TRUE.booleanValue()){$1 = null;System.out.println("i have hooked this method !!!");}##0 def configs = config.trim().split("##") if (null != configs && configs.length > 0) { if (configs.length < 3) { throw new IllegalArgumentException("参数配置有问题") } def className = configs[0].trim() def methodName = configs[1].trim() def injectValue = configs[2].trim() def injectLine = 0 if (4 == configs.length) { try { injectLine = Integer.parseInt(configs[3]) } catch (Exception e) { throw new IllegalArgumentException("行数配置有问题") } } if (TextUtil.isEmpty(className)) { Logger.e("className invalid !!!") return } if (TextUtil.isEmpty(methodName)) { Logger.e("methodName invalid !!!") return } if (TextUtil.isEmpty(injectValue)) { Logger.e("inject value invalid !!!") return } def methodParams = new ArrayList<String>() if (methodName.contains("(") && methodName.contains(")")) { def tempMethodName = methodName methodName = tempMethodName.substring(0, tempMethodName.indexOf("(")).trim() def params = tempMethodName.substring(tempMethodName.indexOf("(") + 1, tempMethodName.indexOf(")")).trim() if (!TextUtil.isEmpty(params)) { if (params.contains(",")) { params = params.split(",") if (null != params && params.length > 0) { params.each { p -> methodParams.add(p.trim()) } } } else { methodParams.add(params) } } } if (className.endsWith(JAVA)) { className = className.substring(0, className.length() - JAVA.length()) + CLASS } if (!className.endsWith(CLASS)) { className += CLASS } def contain = FileUtils.containsClass(jarFile, className) if (contain) { // 1、判断是否进行过解压缩操作 if (!FileUtils.hasFiles(unzipDir)) { FileUtils.unzipJarFile(jarFile, unzipDir) } // 2、开始注入文件,需要注意的是,appendClassPath后边跟的根目录,没有后缀,className后完整类路径,也没有后缀 sClassPool.appendClassPath(unzipDir.absolutePath) // 3、开始注入,去除.class后缀 if (className.endsWith(CLASS)) { className = className.substring(0, className.length() - CLASS.length()) } CtClass ctClass = sClassPool.getCtClass(className) if (!ctClass.isInterface()) { CtMethod ctMethod if (methodParams.isEmpty()) { ctMethod = ctClass.getDeclaredMethod(methodName) } else { CtClass[] params = new CtClass[methodParams.size()] for (int i = 0; i < methodParams.size(); i++) { String param = methodParams.get(i) params[i] = sClassPool.getCtClass(param) } ctMethod = ctClass.getDeclaredMethod(methodName, params) } if (injectLine > 0) { ctMethod.insertAt(injectLine, injectValue) } else if (injectLine == 0) { ctMethod.insertBefore(injectValue) } else { if (!injectValue.startsWith("{")) { injectValue = "{" + injectValue } if (!injectValue.endsWith("}")) { injectValue = injectValue + "}" } ctMethod.setBody(injectValue) } ctClass.writeFile(unzipDir.absolutePath) ctClass.detach() } else { Logger.e(className + " is interface and can't inject code !!!") } } } } } // 4、循环体结束,判断classes文件夹下是否有文件 if (FileUtils.hasFiles(unzipDir)) { BuildJarTask buildJarTask = mProject.tasks.create("BytecodeFixBuildJarTask", BuildJarTask) buildJarTask.baseName = jarName buildJarTask.from(unzipDir.absolutePath) buildJarTask.doLast { // 进行文件的拷贝 def stringBuilder = new StringBuilder().append(mProject.projectDir.absolutePath) .append(File.separator).append("build") .append(File.separator).append("libs") .append(File.separator).append(jar.name).toString() if (!jarDir.exists()) { jarDir.mkdirs() } destFile = new File(jarDir, jar.name) FileUtils.clearFile(destFile) destFile.createNewFile() File srcFile = new File(stringBuilder) com.android.utils.FileUtils.copyFile(srcFile, destFile) FileUtils.clearFile(srcFile) if (null != mExtension && !mExtension.keepFixedClassFile) { FileUtils.clearFile(unzipDir) } } // FIXME buildJarTask sometimes has bug // buildJarTask.execute() destFile = new File(jarDir, jar.name) FileUtils.clearFile(destFile) FileUtils.zipJarFile(unzipDir, destFile) if (null != mExtension && !mExtension.keepFixedClassFile) { FileUtils.clearFile(unzipDir) } } else { FileUtils.clearFile(rootFile) } jarFile.close() return destFile } private void appendClassPath() { if (null == mProject) return def androidJar = new StringBuffer().append(mProject.android.getSdkDirectory()) .append(File.separator).append("platforms") .append(File.separator).append(mProject.android.compileSdkVersion) .append(File.separator).append("android.jar").toString() File file = new File(androidJar); if (!file.exists()) { androidJar = new StringBuffer().append(mProject.rootDir.absolutePath) .append(File.separator).append("local.properties").toString() Properties properties = new Properties() properties.load(new File(androidJar).newDataInputStream()) def sdkDir = properties.getProperty("sdk.dir") androidJar = new StringBuffer().append(sdkDir) .append(File.separator).append("platforms") .append(File.separator).append(mProject.android.compileSdkVersion) .append(File.separator).append("android.jar").toString() file = new File(androidJar) } if (file.exists()) { sClassPool.appendClassPath(androidJar); } else { Logger.e("couldn't find android.jar file !!!") } if (null != mExtension && null != mExtension.dependencies) { mExtension.dependencies.each { dependence -> sClassPool.appendClassPath(dependence) } } }}
public class BytecodeFixTransform extends Transform { private static final String DEFAULT_NAME = "BytecodeFixTransform" private BytecodeFixExtension mExtension; BytecodeFixTransform(Project project, String versionName, BytecodeFixExtension extension) { this.mExtension = extension Logger.enable = extension.logEnable BytecodeFixInjector.init(project, versionName, mExtension) } @Override public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { // 省略相关代码... for (TransformInput input : inputs) { if (null == input) continue; for (DirectoryInput directoryInput : input.directoryInputs) { if (directoryInput) { if (null != directoryInput.file && directoryInput.file.exists()) { // ClassInjector.injector.inject(directoryInput.file.absolutePath, mPackageName.replaceAll("\\.", File.separator)); File dest = outputProvider.getContentLocation(directoryInput.getName(), directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY); FileUtils.copyDirectory(directoryInput.file, dest); } } } for (JarInput jarInput : input.jarInputs) { if (jarInput) { if (jarInput.file && jarInput.file.exists()) { String jarName = jarInput.name; String md5Name = DigestUtils.md5Hex(jarInput.file.absolutePath); if (jarName.endsWith(".jar")) { jarName = jarName.substring(0, jarName.length() - 4); } // 在这里jar文件进行动态修复,这里是重点 File injectedJarFile = null if (null != mExtension && mExtension.enable) { injectedJarFile = BytecodeFixInjector.injector.inject(jarInput.file) } File dest = outputProvider.getContentLocation(DigestUtils.md5Hex(jarName + md5Name), jarInput.contentTypes, jarInput.scopes, Format.JAR); if (dest) { if (dest.parentFile) { if (!dest.parentFile.exists()) { dest.parentFile.mkdirs(); } } if (!dest.exists()) { dest.createNewFile(); } // 校验injectedJarFile是否做过修改,如果做过修改则直接把injectedJarFile拷贝到目的文件夹中 // 然后根据mExtension的配置是否保留修复过的injectedJarFile文件 if (null != injectedJarFile && injectedJarFile.exists()) { FileUtils.copyFile(injectedJarFile, dest) Logger.e(jarInput.file.name + " has successful hooked !!!") if (null != mExtension && !mExtension.keepFixedJarFile) { injectedJarFile.delete() } } else { FileUtils.copyFile(jarInput.file, dest) } } } } } } }}
dependencies { classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.jakewharton:butterknife-gradle-plugin:8.6.0' // 添加如下配置 classpath 'com.llew.bytecode.fix.gradle:BytecodeFixer:1.0.2'}2、在主工程的build.gradle文件末尾添加如下配置:
apply plugin: 'com.llew.bytecode.fix'bytecodeFixConfig { enable true logEnable = true keepFixedJarFile = true keepFixedClassFile = true dependencies = [] fixConfig = [ 'com.tencent.av.sdk.NetworkHelp##getAPInfo(android.content.Context)##$1 = null;System.out.println("I have hooked this method by BytecodeFixer !!!");##0', 'com.tencent.av.sdk.NetworkHelp##getMobileAPInfo(android.content.Context,int)##$1 = null;System.out.println("I have hooked this method by BytecodeFixer !!!");return new com.tencent.av.sdk.NetworkHelp.APInfo();##-1', ]}配置完成之后,运行项目,这时候会在主工程的目录下生成修复过的class文件,如下所示:
好了,运行项目后,通过bytecodeFixConfig的配置,目标Jar文件就会被自动修复,是不是很方便,顿时这个世界是那么的美好,从此以后不管用户授予没有授予相关权限,都可以让用户愉快的玩耍我们APP了,并且做到了一切第三方的Jar文件都在我们的掌控中,看哪一个方法不顺眼就可以使用BytecodeFixer插件做修复,是不是很爽?因此在Java的世界里,如果你精通反射,代理,再加上Javassist利器,你可以做很多事情……
- Android 源码系列之<十八>自定义Gradle Plugin,优雅的解决第三方Jar包中的bug<中>
- Android 源码系列之<十七>自定义Gradle Plugin,优雅的解决第三方Jar包中的bug<上>
- Android 源码系列之<十九>自定义Gradle Plugin,优雅的解决第三方Jar包中的bug<下>
- 在android源码编译中导入第三方jar包
- android系统源码中引用第三方jar包
- android源码应用中导入第三方jar包
- 《android 导入第三方源码jar包遇到的坑》
- eclipse中导入第三方jar包的源码
- Maven优雅的添加第三方Jar包
- Maven优雅的添加第三方Jar包
- gradle:编译过程中修改第三方jar包
- Android开发 之 Android项目中如何正确的引入第三方jar包(工程)
- Android中使用第三方jar包
- android 中导入第三方jar包
- Android 工程中引入第三方jar包的问题
- android中正确导入第三方jar包的方法
- android framework中调用第三方的jar包
- android framework中调用第三方的jar包
- 云计算与大数据 金融行业
- Python-部署Django到Apache
- Linux中环境变量文件及配置
- Kickstart/Anaconda实现自动化安装原理探究
- 第一篇: mina框架初了解
- Android 源码系列之<十八>自定义Gradle Plugin,优雅的解决第三方Jar包中的bug<中>
- 【第十一周项目2】操作用邻接表存储的图
- 论文阅读理解
- 一窥Twitter产品链,数据科学家怎样在各类公司发挥作用?
- 从零开始打造一个Android 3D立体旋转容器
- dilated conv带孔卷积、pooling层提高感受野 反卷积 的理解
- 独家 | 关于数据管理标准化工作的思考
- 【新工具】Java条形码组件Spire.Barcode for JAVA 发布 | 附下载
- Golang---TCP