Epic 是一个在虚拟机层面、以 Java Method 为粒度的 运行时 AOP Hook 框架。简单来说,Epic 就是 ART 上的 Dexposed(支持 Android 5.0 ~ 11)。它可以拦截本进程内部几乎任意的 Java 方法调用,可用于实现 AOP 编程、运行时插桩、性能分析、安全审计等。

Epic:GitHub - tiann/epic: Dynamic java method AOP hook for Android(continution of Dexposed on ART), Supporting 5.0~11

目前可以监控的场景如:Handler消息处理的耗时;Thread线程的执行时长;Activity的各生命周期耗时;ImageView中设置的图片大小检查;Binder进程间通信耗时;dex文件的加载等场景。

优点:无侵入性,代码编写简单、易懂。

实战:针对***可以使用epic的0.3.6版本,项目引入 api ("me.weishu:epic:0.3.6")

1、Handler消息处理的耗时:

hook两个方法:一个是sendMessageAtTime,这样我们就可以知道发生message的一个堆栈

另外一个是dispatchMessage方法。

因为对于发送message,不管调用哪个方法最终都会调用到一个是sendMessageAtTime这个方法,而处理message呢,它最终会调用dispatchMessage方法。

@Override

protected void beforeHookedMethod(MethodHookParam param) throws Throwable {

    String trace = Log.getStackTraceString(new Throwable()).replace("java.lang.Throwable""");

    super.beforeHookedMethod(param);

    if (param.method.getName().equals("sendMessageAtTime")) {

        Handler handler = (Handler) param.thisObject;

        msgTraceCache.put(handler, trace);

    }

    if (param.method.getName().equals("dispatchMessage")) {

        Handler handler = (Handler) param.thisObject;

        startTimeCache.put(handler, System.currentTimeMillis());

    }

}

@Override

protected void afterHookedMethod(MethodHookParam param) throws Throwable {

    super.afterHookedMethod(param);

    Handler handler = (Handler) param.thisObject;

    if (param.method.getName().equals("dispatchMessage") && !startTimeCache.isEmpty() && startTimeCache.get(handler) != null) {

        long cost = System.currentTimeMillis() - startTimeCache.remove(handler);

        String trace = msgTraceCache.remove(handler);

        if (!StringUtils.isEmpty(trace) && cost > 35 && Looper.myLooper() == Looper.getMainLooper()) {//vsync为20-35之间

            LogUtils.d(TAG, "msg_cost ", cost, " ,msg_trace ", trace);

        }

    }

}

注入Hook:

HandlerHook handlerHook = new HandlerHook();

DexposedBridge.findAndHookMethod(Handler.class"sendMessageAtTime", Message.classlong.class, handlerHook);

DexposedBridge.findAndHookMethod(Handler.class"dispatchMessage", Message.class, handlerHook);

如图为一些检测到的耗时操作:

2、Thread线程的执行时长:

Thread有2个AOP点:我为Dexposed续一秒——论ART上运行时 Method AOP实现 | Weishu's Notes

其一是 Thread.class 的run方法,拦截这个方法,我们可以知道所有通过Thread类本身创建的线程。hook线程的run方法:DexposedBridge.findAndHookMethod(Thread.class, "run", new ThreadMethodHook());打印前后的执行时差。

其二是Thread的构造函数,这个Hook点我们可以知道运行时具体有哪些类继承了Thread.class类,在找到这样的子类之后,直接hook掉这个类的run方法,从而达到了拦截所有线程创建的目的。

如图为检测到的一些线程创建、销毁、耗时等:

3、ImageView中设置的图片大小检查:

针对普通ImageView:

通过查看ImageView的源码我们可以看到setImageResource、setImageURI、setImageDrawable这些设置图片的方法都会调用到updateDrawable方法,在该方法内我们可以获取到转化好的Drawable对象,即可拿到被设置的图片的宽高信息,和imageView控件的宽高做对比即可,超过阈值则打印相关log。

针对项目中的ImageTile:

思路同上,hook点为updateDrawable。

DexposedBridge.findAndHookMethod(ImageView.class"updateDrawable", Drawable.classnew ImageHook());

DexposedBridge.findAndHookMethod(ImageTile.class"updateDrawable", Drawable.classnew ImageHook());

在ImageHook中,进行图片和view的宽高检查即可。

目前设置的为1.2倍,项目中有较多的不合理尺寸的图片,

如图为检测到的不合理的图片:

  

 4、Activity的各生命周期耗时

思路一:拿到项目中的所有自己的 XXX_Activity 进行hook的注册,然后针对onCreate、onStart、onResume方法的执行时间监控。

我们的项目是多dex,目前只拿到了主dex中的activity,未解决:需拿到所有dex,并找出所有XXX_activity。

思路二:可以hook住Android的 Activity ,根据activity两个生命周期的差值计算各个生命周期的耗时。

例如onCreate的耗时计算为:onStart开始时间 - onCreate开始时间,

存在问题:1、onResume的执行时间无法计算;2、可能混杂了系统的其他时间,因为onStart开始时间 - onCreate开始时间,之间会混有系统其他函数的执行时间,非纯onCreate时间。

如下图为思路二获取到的activity的onCreate+onStart的耗时时长:

思路三:模仿ARouter,添加注解,在编译期扫描所有标注过的Activity,然后监听耗时

思路四:在application和activity的所有生命周期调用中,都会先调用instrumentation的相应方法。Instrumentation的理解 - 简书

因此只需要hook其中的callActivityOnCreate,callApplicationOnCreate,newActivity,callActivityOnNewIntent 等方法,即可获取到精确的各个生命周期的耗时。

此种方式精确并且简单易行,项目中建议使用这个方式。如下图为打印出来的各个生命周期的耗时:

5、Binder进程间的通信耗时:

对android.os.BinderProxy类的transact方法进行Hook。

如下一些打印出来的binder耗时:

 

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐