自定义gradle插件并实现ASM字节码插桩

插件开发:

如在build.gradle(App)中,声明使用自定义插件

apply plugin: 'com.hyu.asmpatch'
​
patch{
    debugOn true
    applicationName "com.example.mytest.App"
}

a.在项目中新建一个java-library项目(buildSrc)去实现此插件,在src/main/文件夹中新建resources/META-INF/gradle-plugins/com.hyu.asmpatch.properties文件,并添加以下代码声明;

//声明此插件名"com.hyu.asmpatch"的实现代码入口
implementation-class=com.example.patch.PatchPlugin

b.添加此插件的依赖

//代码中要计算md5值,在libs文件夹下添加commons-codec.jar
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.tools.build:gradle:4.2.1'
}

c.新建插件的扩展,即面向用户可自定义的入参

public class PatchExtension {
    //是否在debug模式下处理
    private boolean debugOn;
    //输出路径
    private String output;
    //application名称
    private String applicationName;
​
    public PatchExtension() {
        this.debugOn = false;
    }
​
    public boolean isDebugOn() {
        return debugOn;
    }
​
    public void setDebugOn(boolean debugOn) {
        this.debugOn = debugOn;
    }
​
    public String getOutput() {
        return output;
    }
​
    public void setOutput(String output) {
        this.output = output;
    }
​
    public String getApplicationName() {
        return applicationName;
    }
​
    public void setApplicationName(String applicationName) {
        this.applicationName = applicationName;
    }
}

d.gradle打包的流程,gradle打包时即执行多个任务,如预编译,打包资源文件、编译java文件为class文件,将多个class文件打包成dex文件等。正是由于这么多的任务执行后才打包成最后的apk文件;而我们自定义的gradle插件,就是在android插件执行打包的各个任务执行的前后过程中进行监听,插入自已的代码,如这次我们实现的:对生成的class文件进行字节码插桩,然后再将插桩完成的class文件重新放入原位置,这样最后生成的apk文件里的dex文件就是经过我们插桩过的代码。

package com.example.patch;
​
import com.android.build.gradle.AppExtension;
import com.android.build.gradle.AppPlugin;
import com.android.build.gradle.api.ApplicationVariant;
import com.android.tools.r8.utils.F;
import com.android.utils.FileUtils;
import com.sun.jna.platform.unix.solaris.LibKstat;
​
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.file.FileCollection;
import org.gradle.api.provider.Property;
​
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
​
public class PatchPlugin implements Plugin<Project> {
    private Project project;
    private boolean isDebugOn;
    private String applicationName;
    private String output;
    private String pkgName;
    private PatchGenerator patchGenerator;
​
​
    @Override
    public void apply(Project project) {
        //判断如果没有apply com.android.application就不能使用我们的插件
        if(!project.getPlugins().hasPlugin(AppPlugin.class)){
            throw new GradleException("can not use com.hyu.asmpatch in unused application plugin ");
        }
​
​
        this.project = project;
        //创建一个patch{}配置,供gradle文件中配置PatchExtension对象参数,定义插件名为patch,
        //类似Android项目使用的插件为android
        //就和引入了 apply plugin: 'com.android.application' 一样,可以配置android{}
        project.getExtensions().create("patch",PatchExtension.class);
​
        //gradle执行会解析build.gradle文件,afterEvaluate表示在解析完成之后才能拿到参数,
        // 再执行我们的代码
        project.afterEvaluate(new Action<Project>() {
            @Override
            public void execute(Project project) {
                //获取扩展参数
                final PatchExtension patchExtension = project.getExtensions().getByType(PatchExtension.class);
                applicationName = patchExtension.getApplicationName();
                isDebugOn = patchExtension.isDebugOn();
                output = patchExtension.getOutput();
​
                //得到android插件扩展参数
                final AppExtension appExtension = project.getExtensions().getByType(AppExtension.class);
                // android项目默认会有 debug和release,
                // 那么getApplicationVariants就是包含了debug和release的集合,all表示对集合进行遍历
                appExtension.getApplicationVariants().all(new Action<ApplicationVariant>() {
                    @Override
                    public void execute(ApplicationVariant applicationVariant) {
                        
                        //获取包名:
                        pkgName = applicationVariant.getGenerateBuildConfigProvider().get().getAppPackageName().get();
                        //String a = applicationVariant.getProductFlavors().get(0).getApplicationIdSuffix();
                        String b= applicationVariant.getGenerateBuildConfig().getAppPackageName().get();
                        System.out.println("a========"+pkgName);
                        System.out.println("b========"+b);
                        //variantName==debug/release
                        String variantName = applicationVariant.getName();
                        System.out.println("variantName="+variantName);
                        //配置热修复插件生成补丁的一系列任务
                        configTasks(project, applicationVariant, patchExtension);
​
                    }
                });
            }
        });
    }
​
    private void configTasks(Project project, ApplicationVariant applicationVariant, PatchExtension patchExtension) {
        String variantName = applicationVariant.getName();
        //首字母大写
        String VariantName = Utils.capitalize(variantName);
​
        File outputDir;
        //如果没有指名输出目录,默认输出到 build/patch/debug(release) 下
        if(!Utils.isEmpty(output)){
            outputDir = new File(output,variantName);
        }else{
            outputDir = new File(project.getBuildDir(),"patch/"+variantName);
        }
        outputDir.mkdirs();
​
        Task proguardTask = null;
        File mappingBak = null;
​
        if(variantName!=null && variantName.equals("release")){
            //获得android的混淆任务
            //proguardTask = project.getTasks().getByName("transformClassesAndResourcesWithProguardFor"+VariantName);
            proguardTask = project.getTasks().getByName("merge"+VariantName+"GeneratedProguardFiles");
            //备份本次生成的mapping文件
            mappingBak = new File(outputDir,"mapping.txt");
            //如果没开启混淆则为Null,不需要备份
            if(proguardTask != null){
                // dolast:在这个任务之后再干一些事情
                // 在混淆后备份mapping
                File finalMappingBak = mappingBak;
                proguardTask.doLast(new Action<Task>() {
                    @Override
                    public void execute(Task task) {
                        Set<File> files = task.getOutputs().getFiles().getFiles();
                        for(File file : files){
                            if(file.getName().endsWith("mapping.txt")){
                                try {
                                    //复制编译的mapping.txt文件到mappingBak文件
                                    FileUtils.copyFile(file, finalMappingBak);
                                    project.getLogger().info("复制mapping.txt文件到"+ finalMappingBak.getCanonicalPath());
                                }catch (Exception e){
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                });
            }
        }
​
​
​
​
        //将上次混淆的mapping应用到本次,如果没有上次的混淆文件就没操作
        Utils.applyMapping(proguardTask, mappingBak);
​
        //记录类的hash值,并生成补丁包
        final File hexFile = new File(outputDir,"hex.txt");
​
        //需要打包成补丁的类
        final File patchClassFile  = new File(outputDir,"patchClass.jar");
​
        //用dx打包后的jar包:补丁包
        final File patchFile = new File(outputDir,"patch.jar");
​
        final Task dexTask = project.getTasks().getByName("dexBuilder"+VariantName);
        //final Task dexTask = project.getTasks().getByName("mergeProjectDexDebug");
​
        //在打包之前执行,即可以拿到经过编译的类的class文件
        dexTask.doFirst(new Action<Task>() {
            @Override
            public void execute(Task task) {
​
                patchGenerator = new PatchGenerator(project, patchFile,
                        patchClassFile, hexFile);
                /**
                 * 插桩记录md5值
                 */
                Map<String,String> newHexMap = new HashMap<>();
                Set<File> inputFiles = dexTask.getInputs().getFiles().getFiles();
                for(File file : inputFiles){
                    String filePath = file.getAbsolutePath();
                    //会得到各种资源Jar包及我们代码打包的class文件夹,我们仅需对我们自已代码生成的class文件进行处理
                    if(filePath.endsWith("\\classes")){
                        try {
                            processClassFile(file,applicationVariant.getDirName(), newHexMap);
                        }catch (Exception e){
                            e.printStackTrace();
                        }
​
                    }
​
                    //类的md5集合 写入到文件
                    Utils.writeHex(newHexMap, hexFile);
                    try {
                        //生成补丁
                        patchGenerator.generate();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
​
            }
        });
​
    }
​
    private void processClassFile(File file, String dirName, Map hexFile) throws IOException {
        String fileDirPath = file.getAbsolutePath();
        String convertPkg = pkgName.replace(".","\\");
        File classFileDir = new File(fileDirPath+"\\"+convertPkg);
        File[] files = classFileDir.listFiles();
        //得到class文件夹下的class文件
        for(int i= 0;i<files.length;i++){
            File classFile = files[i];
            String classFilePath = classFile.getAbsolutePath();
            System.out.println("classFilePath="+classFilePath);
            System.out.println("fileDirPath="+String.valueOf(fileDirPath));
​
            String classFilePathDot = classFilePath.replace("\\",".");
            String fileDirPathDot = fileDirPath.replace("\\",".");
            //得到class文件的文件名:com.example.mytest.MainActivity2.class
            String  className = classFilePathDot.split(fileDirPathDot+".")[1];
​
​
            if(className != null && className.equals("com.example.mytest.MainActivity2.class")){
                try {
                    System.out.println("exec className="+className);
                    FileInputStream is = new FileInputStream(classFilePath);
                    //执行插桩
                    byte[] byteCode = ClassUtils.referHackWhenInit(is);
​
                    String hex = Utils.hex(byteCode);
​
                    is.close();
​
                    FileOutputStream os = new FileOutputStream(classFilePath);
                    os.write(byteCode);
                    os.close();
​
                    hexFile.put(className, hex);
                    //对比缓存的md5,不一致则放入补丁
                    patchGenerator.checkClass(className, hex, byteCode);
​
​
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            
        }
​
    }
}

e.具体执行插桩的代码:

public class ClassUtils {
​
​
    public static byte[] referHackWhenInit(InputStream inputStream) throws IOException {
        ClassReader cr = new ClassReader(inputStream);
        ClassWriter cw = new ClassWriter(cr, 0);
        ClassVisitor cv = new ClassVisitor(Opcodes.ASM5, cw) {
            @Override
            public MethodVisitor visitMethod(int access, final String name, String desc,
                                             String signature, String[] exceptions) {
​
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                mv = new MethodVisitor(api, mv) {
                    @Override
                    public void visitInsn(int opcode) {
                        //在构造方法中插入AntilazyLoad引用
                        if ("<init>".equals(name) && opcode == Opcodes.RETURN) {
                            super.visitLdcInsn(Type.getType("Lcom/example/hack/AntilazyLoad;"));
                        }
                        super.visitInsn(opcode);
                    }
                };
                return mv;
            }
​
        };
        cr.accept(cv, 0);
        return cw.toByteArray();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android 开发中,Gradle 是一个非常重要的构建工具,可以用来构建和打包 Android 应用程序。Gradle 插件是一种工具,可以扩展 Gradle 的功能,使其能够支持更多的功能。而多渠道打包是 Android 应用程序开发中非常重要的一个方面,它可以让我们将应用程序打包成不同的版本,并发布到不同的应用商店或市场上。 在 Android Studio 中,我们可以通过自定义 Gradle 插件实现多渠道打包,具体步骤如下: 1. 创建 Gradle 插件项目 在 Android Studio 中创建一个新项目,选择 Gradle 插件项目模板。这将创建一个 Gradle 插件项目,并生成一些默认的代码和文件。 2. 实现多渠道打包 在插件项目中,我们需要实现多渠道打包的功能。这可以通过 Gradle 的 productFlavors 和 buildTypes 配置来实现。我们可以定义多个 productFlavors,并为每个 productFlavor 配置不同的参数,例如应用程序的包名、应用程序名称等。在 buildTypes 中,我们可以为每个 buildType 配置不同的参数,例如应用程序的版本号、是否开启混淆等。 3. 打包应用程序 在插件项目中,我们可以编写一个 Gradle 任务来实现应用程序的打包。这个任务可以使用 Gradle 提供的 assemble 任务来实现。我们可以为每个 productFlavor 和 buildType 配置不同的打包参数,并使用 Gradle 的 assemble 任务来生成应用程序的 APK 文件。 4. 发布应用程序 在插件项目中,我们可以编写一个 Gradle 任务来实现应用程序的发布。这个任务可以使用 Gradle 提供的 uploadArchives 任务来实现。我们可以为每个 productFlavor 和 buildType 配置不同的发布参数,并使用 Gradle 的 uploadArchives 任务将应用程序发布到不同的应用商店或市场上。 总的来说,自定义 Gradle 插件多渠道打包是 Android 应用程序开发中非常重要的一个方面。通过自定义 Gradle 插件,我们可以实现更加灵活和高效的应用程序打包和发布。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值