Skip to content

用于分析函数耗时,能打印出函数参数值和返回值以及函数调用栈,还能修改aar和jar,自定义lint

Notifications You must be signed in to change notification settings

zjw-swun/AppMethodTime

Repository files navigation

AppMethodTime

一个测量函数耗时的库(更新readme)

背景:这次没什么背景就是知识总结,Gradle自定义插件+Transform+javassist 做了个 JakeWharton大神hugo库类似的东西

特别说明:本文已经稍后送上的代码参考以及抄袭了(http://blog.csdn.net/eclipsexys/article/details/50973205) (https://github.com/HalfStackDeveloper/Savior), 还有巴掌大神的库(https://github.com/JeasonWong/CostTime) ,十分鸣谢巴掌大神手把手教我改代码 (有用到代码的同学,联系我,有侵必删)

源码下载地址:(https://github.com/zjw-swun/AppMethodTime) 觉得有帮助可以给个star

简单介绍一下:Transform 参与class打包的Android gradle plugin的api,javassist 是生成或者修改class字节码的库。借由这2个特性,我们就可以完成类似于JakeWharton大神hugo库类似的东西。

#1. 2种生成自定义插件的方式 1.新建一个BuildSrc module (lib module ,BuildSrc 名字固定),从src(包括src)开始以下文件或者文件夹都是自己建的(具体步骤就不讲了,CSDN 简书上都有讲) QQ图片20170502230318.png

2.比第1种方式名字可以不固定,但是需要带上META那个文件夹,

QQ图片20170502231259.png 该build.gradle 需要加一个task

//设置maven deployer
uploadArchives {
    repositories {
        mavenDeployer {
            //设置插件的GAV参数
            pom.groupId = 'com.branch.plugin'
            pom.artifactId = 'dellog'
            pom.version = '1.0.0'
            //文件发布到下面目录
            repository(url: uri('../repo'))
        }
    }
}

然后执行uploadArchives 上传 具体不细说看代码吧

核心就是MyTransform类,以及MyInject类 MyInject类里面的代码如下

package com.zjw.plugin

import javassist.*

public class MyInject {

    private static ClassPool pool = ClassPool.getDefault()
    //注意这里需要替换你的anroid.jar路径
    static String androidJar = "D:\\Application\\Android\\sdkUpDate\\platforms\\android-24\\android.jar"
    static String myPackageName = "com.zjw.appmethodtime";
    static String myCostTimeAnnotation = "@com.zjw.appmethodtime.CostTime";
    static String CostTime = "CostTime";

    public static void injectDir(String path, String packageName, boolean enabeCostTime) {
        pool.appendClassPath(path)
        // 以下为windows环境下你的相应android.jar路径
        pool.insertClassPath(androidJar)

        println("enabeCostTime is " + enabeCostTime)

        File dir = new File(path)
        if (dir.isDirectory()) {
            dir.eachFileRecurse { File file ->
                String filePath = file.absolutePath
                //确保当前文件是class文件,并且不是系统自动生成的class文件以及注解文件
                if (filePath.endsWith(".class")
                        && !filePath.contains('R$')
                        && !filePath.contains('R.class')
                        && !filePath.contains("BuildConfig.class")
                        && !filePath.contains("CostTime")
                ) {
                    // 判断当前目录是否是在我们的应用包里面
                    int index = filePath.indexOf(packageName);
                    boolean isMyPackage = index != -1;
                    if (isMyPackage) {
                        int end = filePath.length() - 6 // .class = 6
                        String className = filePath.substring(index, end).replace('\\', '.').replace('/', '.')
                        //开始修改class文件
                        CtClass c = pool.getCtClass(className)

                        if (c.isFrozen()) {
                            c.defrost()
                        }
                        pool.importPackage(myPackageName)

                        //给类添加计时变量
                        CtField startTime = new CtField(CtClass.longType, "sStart", c);
                        startTime.setModifiers(Modifier.STATIC);
                        c.addField(startTime);

                        //给类添加计时变量
                        CtField endTime = new CtField(CtClass.longType, "sEnd", c);
                        endTime.setModifiers(Modifier.STATIC);
                        c.addField(endTime);

                        //遍历类的所有方法
                        CtMethod[] methods = c.getDeclaredMethods();
                        for (CtMethod method : methods) {
                            println("method ====" + method.longName)
                            if (enabeCostTime
                                    && method.getAvailableAnnotations() != null
                                    && method.getAvailableAnnotations().length >= 1
                                    && "${method.getAvailableAnnotations()[0]}".contains(CostTime)
                            ) {
                                insertCostTimeCode(method)
                            } else if (!enabeCostTime) {
                                insertCostTimeCode(method)
                            }
                        }//END   for (CtMethod method : methods)
                        c.writeFile(path)
                        c.detach()
                    }//END if(isMyPackage)
                }
            }
        }
    }

    private static void insertCostTimeCode(CtMethod method) {
        //插入到函数第一句
        StringBuilder startInjectStr = new StringBuilder();
        startInjectStr.append("sStart = System.nanoTime();");
        print("方法第一句插入了:" + startInjectStr.toString() + "语句\n")
        method.insertBefore(startInjectStr.toString())

        //插入到函数最后一句
        StringBuilder endInjectStr = new StringBuilder();
        endInjectStr.append("sEnd = System.nanoTime();\n");
        endInjectStr.append("android.util.Log.e(\"AppMethodTime\",");
        endInjectStr.append("\"" + method.longName + "\"");
        endInjectStr.append("+(sEnd - sStart)/1000000+\" (毫秒)\");");
        print("方法最后一句插入了:" + endInjectStr.toString() + "语句\n")
        method.insertAfter(endInjectStr.toString(), true)
    }
}

#2. 插件自定义的配置字段

pluginsrc{
    message = 'hello gradle plugin'
    cost = true
}

在MyTransform类中

 MyInject.injectDir(directoryInput.file.absolutePath,
                        "com" + File.separator + "zjw" + File.separator + "appmethodtime",
                        project.pluginsrc.cost)

使用了。如果你想在自定义插件中自己创建的task中在你的app编译生成apk前读取

pluginsrc{
    message = 'hello gradle plugin'
    cost = true
}

cost 等字段值的话,你得加上 preBuild.dependsOn appPlugin

不说了自定义插件的初级知识应该都在这里了 效果图

QQ图片20170512022507.png 说明:如果函数返回类型是void log中对应return is null

2019/7/6 新增支持修改aar和jar 中class插入字节码

添加aarOrJarPath 配置字段,填入目标jar或者aar路径 执行gradle面板对应项目中other目录appMethodJarOrAar 任务即可在aarOrJarPath 配置的同目录下生成带 Target_前缀的目标jar或者aar文件

2019/7/13 增加自定义lint

感谢 https://github.com/Omooo/CustomLint 的作者

调试Gradle 插件的方法

  1. 运行按钮 -> edit config -> 命名 + 指定要调试的源码位置 ->apply
  2. studio 命令行中 : gradle assembleDebug -Dorg.gradle.debug=true --no-daemon
  3. studio 面板debug启动 按钮

About

用于分析函数耗时,能打印出函数参数值和返回值以及函数调用栈,还能修改aar和jar,自定义lint

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published