ASM技术的介绍:

java文件会先转化为class文件,然后在转化为dex文件。而通过Gradle插件提供的Transform API,可以在编译成dex文件之前得到class文件。得到class文件之后,便可以通过ASM对字节码进行修改,即可完成字节码插桩。

 

全局方法执行时长:

首先我们需要创建一个gradle插件:

第一步:修改build.gradle

apply plugin: 'groovy'
apply plugin: 'maven'

dependencies {
    //gradle sdk
    implementation gradleApi()
    //groovy sdk
    implementation localGroovy()

    implementation 'com.android.tools.build:gradle:3.1.3'
    implementation group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'
}

repositories {
    mavenCentral()
    jcenter()
}


//本地的Maven地址设置为D:/repos
group='com.hc.plugin'
version='1.0.0'
uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: uri('/Users/malei/Desktop/repos'))
        }
    }
}

第二步:将lib库里的东西都删除掉,只保留.gradle文件和src目录,然后在main包下添加groovy和resources包:

在groovy包下添加MethodLogPlugin.groovy文件:

package com.hc.plugin

import com.android.build.gradle.AppExtension
import org.gradle.api.Plugin
import org.gradle.api.Project

class MethodLogPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        def android = project.extensions.getByType(AppExtension)
        android.registerTransform(new MethodLogTransform(project))
    }
}

在创建 MethodLogTransform.groovy

package com.hc.plugin

import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import org.apache.commons.io.FileUtils
import org.gradle.api.Project

class MethodLogTransform extends Transform {

    private Project mProject

    MethodLogTransform(Project project) {
        this.mProject = project
    }

    @Override
    String getName() {
        return 'MethodLogTransform'
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    @Override
    boolean isIncremental() {
        return true
    }

    @Override
    void transform(Context context, Collection<TransformInput> inputs,
                   Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider,
                   boolean isIncremental) throws IOException, TransformException, InterruptedException {
        println '--------------------MethodLogTransform Begin-------------------'
        long beginTime = System.currentTimeMillis()

        if (outputProvider != null) {
            outputProvider.deleteAll();
        }

        inputs.each {
            TransformInput input ->
                input.directoryInputs.each {
                    DirectoryInput directoryInput ->
                        LogInsertHelper.loadClassPath(directoryInput.file.absolutePath, mProject)
                }

                input.jarInputs.each {
                    JarInput jarInput ->
                        LogInsertHelper.loadClassPath(jarInput.file.absolutePath, mProject)
                }

                //Class文件插桩
                input.directoryInputs.each {
                    DirectoryInput directoryInput ->
                        def dest = outputProvider.getContentLocation(directoryInput.name,
                                directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
                        LogInsertHelper.injectLog2Class(directoryInput.file.absolutePath)
                        FileUtils.copyDirectory(directoryInput.file, dest)
                }

                //Jar包插桩
                input.jarInputs.each {
                    JarInput jarInput ->
                        def dest = outputProvider.getContentLocation(jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR)
                        //添加jar包所在的Module名字 包括aar包
                        String ModuleName = "xxxxx"
                        if (jarInput.name.contains(ModuleName)) {
                            LogInsertHelper.injectLog2Jar(jarInput,dest)
                        } else {
                            FileUtils.copyFile(jarInput.file, dest)
                        }
                }
        }

        long duringTime = System.currentTimeMillis() - beginTime
        println("MethodLogTransform Using Time :" + duringTime + "ms")
        println '---------------------MethodLogTransform End------------------- '
    }
}

最后创建LogInsertHelper.groovy文件

package com.hc.plugin

import com.android.SdkConstants
import com.android.build.api.transform.JarInput
import javassist.ClassPool
import javassist.CtClass
import javassist.CtMethod
import javassist.Modifier
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.gradle.api.Project

import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry

class LogInsertHelper {

    private static final ClassPool sClassPool = ClassPool.getDefault()
    private static final String LOGTAG = "MALEI_LOG"

    static void loadClassPath(String path, Project project) {
        sClassPool.appendClassPath(path)
        sClassPool.appendClassPath(project.android.bootClasspath[0].toString())
        sClassPool.importPackage('android.os.Bundle')
    }

    static void injectLog2Class(String path) {
        File dir = new File(path)
        boolean isCodeChanged = false
        if (dir.isDirectory()) {
            dir.eachFileRecurse { File file ->
                isCodeChanged = false
                boolean flag = checkClassFile(file.name)
                String debugStr = "debug"
                if (flag && dir.absolutePath.toLowerCase().contains(debugStr)) {
                    String tempStr = file.getCanonicalPath()
                    String fullpath = tempStr.substring(dir.absolutePath.length() + 1, tempStr.length())
                    String className = fullpath.replace("/", ".")
                    if (className.endsWith(".class")) {
                        className = className.replace(".class", "")
                    }
                    CtClass ctClass = sClassPool.getCtClass(className)
                    System.println("Changed class name = " + ctClass.getName())
                    if (ctClass.isFrozen()) {
                        ctClass.defrost()
                    }
                    ctClass.getDeclaredMethods().each {
                        CtMethod ctMethod ->
                            //System.println("MethodName : " + ctMethod.getName())
                            if (!ctMethod.isEmpty() && !isNative(ctMethod) && filterMethodName(ctMethod)) {
                                ctMethod.addLocalVariable("startTime", CtClass.longType)
                                String name = ctMethod.getName()
                                String curClass = ctClass.getName()
                                String insertStr = "startTime = 0;startTime = System.currentTimeMillis();"
                                String lastStr = """if(System.currentTimeMillis() - startTime > 10){android.util.Log.e(\"$LOGTAG\","【ClassName: " + \"$curClass\" + "-> 执行方法:" + \"$name\" + " -> 执行时长:" + (System.currentTimeMillis() - startTime) + "ms -> IsMainThread:" + (android.os.Looper.getMainLooper() == android.os.Looper.myLooper()) + "】");}"""
                                ctMethod.insertBefore(insertStr)
                                ctMethod.insertAfter(lastStr)
                                isCodeChanged = true
                            }
                    }
                    if (isCodeChanged) {
                        ctClass.writeFile(path)
                    }
                    ctClass.detach()
                }
            }
        }
    }

    static void injectLog2Jar(JarInput jarInput, File dest) {
        if (jarInput.file.absolutePath.endsWith(".jar")) {
            JarFile jarFile = new JarFile(jarInput.file)
            Enumeration enumeration = jarFile.entries()
            File tmpFile = new File(jarInput.file.getParent() + File.separator + "classes_temp.jar")
            if (tmpFile.exists()) {
                tmpFile.delete()
            }
            JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(tmpFile))
            while (enumeration.hasMoreElements()) {
                JarEntry jarEntry = enumeration.nextElement()
                String entryName = jarEntry.getName()
                String[] classNames = entryName.split("/")
                ZipEntry zipEntry = new ZipEntry(entryName)
                InputStream inputStream = jarFile.getInputStream(jarEntry)
                if (classNames.length > 0 && checkJarFile(entryName) && checkClassFile(classNames[classNames.length - 1])) {
                    jarOutputStream.putNextEntry(zipEntry)
                    entryName = entryName.replace("/", ".").substring(0, entryName.length() - SdkConstants.DOT_CLASS.length())
                    System.out.println("Jar ClassName = " + entryName)
                    CtClass ctClass = sClassPool.getCtClass(entryName)
                    if (ctClass.isFrozen()) {
                        ctClass.defrost()
                    }
                    ctClass.getDeclaredMethods().each {
                        CtMethod ctMethod ->
                            //System.println("Jar MethodName : " + ctMethod.getName())
                            if (!ctMethod.isEmpty() && !isNative(ctMethod) && filterMethodName(ctMethod)) {
                                ctMethod.addLocalVariable("startTime", CtClass.longType)
                                String name = ctMethod.getName()
                                String curClass = ctClass.getName()
                                String insertStr = "startTime = 0;startTime = System.currentTimeMillis();"
                                String lastStr = """if(System.currentTimeMillis() - startTime >= 10){android.util.Log.i(\"$LOGTAG\","【ClassName: " + \"$curClass\" + "】***【 MethodName:" + \"$name\" + "】***【DuringTime:" + (System.currentTimeMillis() - startTime) + "ms】");}"""
                                ctMethod.insertBefore(insertStr)
                                ctMethod.insertAfter(lastStr)
                            }
                    }
                    jarOutputStream.write(ctClass.toBytecode())
                } else {
                    jarOutputStream.putNextEntry(zipEntry)
                    jarOutputStream.write(IOUtils.toByteArray(inputStream))
                }
                jarOutputStream.closeEntry()
            }
            jarOutputStream.close()
            jarFile.close()

            FileUtils.copyFile(tmpFile, dest)
            tmpFile.delete()
        }
    }

    static boolean checkClassFile(String name) {
        //只处理需要的class文件
        return (name.endsWith(".class") && !name.startsWith("R\$") && !name.startsWith("R2\$")
                && !"R.class".equals(name) && !"BuildConfig.class".equals(name)
                && !"android/support/v4/app/FragmentActivity.class".equals(name))
    }

    static boolean checkJarFile(String name) {
        return !name.startsWith("android") && !name.startsWith("javassist")
    }

    static boolean isNative(CtMethod method) {
        return Modifier.isNative(method.getModifiers())
    }

    static boolean filterMethodName(CtMethod ctMethod) {
        String name = ctMethod.getName()
        if (name.contains("\$") && name.contains("isLoggable")) {
            return false
        }
        return true
    }
}

最后在resources文件中添加配置文件

resources/META-INF.gradle-plugins/com.hc.plugin.properties文件

implementation-class=com.hc.plugin.MethodLogPlugin

这个“com.hc.plugin ” 文件的名字就是 之后我们使用的插件名称

完成以后,我们就可以直接包自己的gradle插件上传到本地的maven仓库了。

使用gradle插件

我们首先在项目gradle中添加maven仓库的依赖和包的依赖:

执行的时候我们只需要在app的gradle中添加如下:

apply plugin: 'com.hc.plugin'

打印结果:

E/MALEI_LOG: 【ClassName: com.example.myqiyi.MainActivity-> 执行方法:dog2 -> 执行时长:105ms -> IsMainThread:true】
E/MALEI_LOG: 【ClassName: com.example.myqiyi.MainActivity-> 执行方法:dog_aroundBody0 -> 执行时长:206ms -> IsMainThread:true】
E/MALEI_LOG: 【ClassName: com.example.myqiyi.MainActivity$AjcClosure1-> 执行方法:run -> 执行时长:206ms -> IsMainThread:true】
E/MALEI_LOG: 【ClassName: com.example.myqiyi.MainActivity-> 执行方法:dog -> 执行时长:208ms -> IsMainThread:true】
E/MALEI_LOG: 【ClassName: com.example.myqiyi.MainActivity-> 执行方法:onCreate -> 执行时长:291ms -> IsMainThread:true】

 

 

 

 

 

 

 

 

 

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐