Android性能优化系列-腾讯matrix卡顿优化之IdleHandlerLagTracer源码分析

这是性能优化系列之matrix框架的第13篇文章,我将在性能优化专栏中对matrix apm框架做一个全面的代码分析,性能优化是Android高级工程师必知必会的点,也是面试过程中的高频题目,对性能优化感兴趣的小伙伴可以去我主页查看所有关于matrix的分享。

前言

IdleHandler是Android系统为开发者提供的一种在消息队列空闲时运行任务的机制,通过IdleHandler执行的任务优先级低于主线程优先级,会在主线程任务执行完成后再执行,所以适用于一些实时性要求不高的任务,通常用于Android启动优化中,将一些优先级较低的任务延后执行,以提高应用启动速度。看下消息队列中的源码。

ini 复制代码
@UnsupportedAppUsage
Message next() {
    //前边省略了很多代码,只有消息队列当前没有需要执行的任务时,才会执行到下边的代码
    for (int i = 0; i < pendingIdleHandlerCount; i++) {
        final IdleHandler idler = mPendingIdleHandlers[i];
        keep = idler.queueIdle();
    }
}

那么既然IdleHandler是用于进行性能优化的,为什么matrix还要对其进行监控呢?从上边的分析我们可以知道,IdleHandler也是在主线程消息队列中运行的,所以假如IdleHandler中出现了耗时任务执行,那么很明显就会导致主线程卡顿,IdleHandler也是属于主线程卡顿监控的关键一环。

了解了IdleHandler监控的必要性,我们现在开始今天的源码分析。和其他类型的tracer一致,IdleHandler也是在TracePlugin中进行初始化和调用的,那么我们就从这几个关键方法入手:

  • 构造方法
  • onStartTrace
  • onStopTrace

构造方法

构造方法仅仅是拿到了传入的配置,配置中包含的是IdleHandler监控是否启用的开关,isIdleHandlerTraceEnable。

arduino 复制代码
public IdleHandlerLagTracer(TraceConfig config) {
    traceConfig = config;
}

onStartTrace

onStartTrace会调用到onAlive方法,我们看onAlive的源码,首先初始化了一个HandlerThread,然后创建了一个IdleHandlerLagRunnable,最后调用了detectIdleHandler开启监控。

scss 复制代码
@Override
public void onAlive() {
    super.onAlive();
    if (traceConfig.isIdleHandlerTraceEnable()) {
        //异步线程
        idleHandlerLagHandlerThread = new HandlerThread("IdleHandlerLagThread");
        //上报信息用的runnable
        idleHandlerLagRunnable = new IdleHandlerLagRunable();
        detectIdleHandler();
    }
}

IdleHandlerLagRunnable是用于上报信息的,我们先看detectIdleHandler方法。首先拿到主线程消息队列对象,然后通过反射从MessageQueue对象上获取到mIdleHandlers的Field对象,mIdleHandlers是一个List集合,内部存储了所有当前消息队列添加的IdleHandler对象。拿到之后构造了一个自定义的List-MyArrayList,反射将其设置到消息队列上,这里的目的是将mIdleHandlers作为一个hook点,完成替换之后,主线程添加和移除IdleHandler的操作都在我们的监控范围之内了。

ini 复制代码
private static void detectIdleHandler() {
    MessageQueue mainQueue = Looper.getMainLooper().getQueue();
    Field field = MessageQueue.class.getDeclaredField("mIdleHandlers");
    field.setAccessible(true);
    MyArrayList<MessageQueue.IdleHandler> myIdleHandlerArrayList = new MyArrayList<>();
    //反射替换消息队列中的List
    field.set(mainQueue, myIdleHandlerArrayList);
    idleHandlerLagHandlerThread.start();
    idleHandlerLagHandler = new Handler(idleHandlerLagHandlerThread.getLooper());
}

MyArrayList

看下MyArrayList的实现。它继承自ArrayList,重写了add和remove方法,也就是拦截了IdleHandler的添加和移除。当通过调用MessageQueue的addIdleHandler方法向list中添加时,就会走到MyArrayList的add方法中,此时会将IdleHandler再包装一层MyIdleHandler存入,达到拦截IdleHandler的queueIdle方法调用的目的。

typescript 复制代码
static class MyArrayList<T> extends ArrayList {
    Map<MessageQueue.IdleHandler, MyIdleHandler> map = new HashMap<>();
    @Override
    public boolean add(Object o) {
        if (o instanceof MessageQueue.IdleHandler) {
            //包装一层,作为代理。拦截queueIdle方法的执行
            MyIdleHandler myIdleHandler = new MyIdleHandler((MessageQueue.IdleHandler) o);
            //记录映射关系
            map.put((MessageQueue.IdleHandler) o, myIdleHandler);
            return super.add(myIdleHandler);
        }
        return super.add(o);
    }
    @Override
    public boolean remove(@Nullable Object o) {
       if (o instanceof MyIdleHandler) {
           MessageQueue.IdleHandler idleHandler = ((MyIdleHandler) o).idleHandler;
           map.remove(idleHandler);
           return super.remove(o);
       } else {
           MyIdleHandler myIdleHandler = map.remove(o);
           if (myIdleHandler != null) {
              return super.remove(myIdleHandler);
           }
           return super.remove(o);
       }
   }
}

MyIdleHandler

接下来我们看看MyIdleHandler是怎么实现的。可以看到它继承自IdleHandler,并重写了它的queueIdle方法,这样一来每一个IdleHandler执行时都会走到MyIdleHandler的queueIdle方法中,也就都在我们的监控之内了。

typescript 复制代码
static class MyIdleHandler implements MessageQueue.IdleHandler {
    @Override
    public boolean queueIdle() {
        //发送延时消息,延时内未执行完成就上报
        idleHandlerLagHandler.postDelayed(idleHandlerLagRunnable, traceConfig.idleHandlerLagThreshold);
        boolean ret = this.idleHandler.queueIdle();
        //执行完成则移除延时消息
        idleHandlerLagHandler.removeCallbacks(idleHandlerLagRunnable);
        return ret;
    }
}

当queueIdle执行的时候,通过idleHandlerLagHandler发送一个延时2s(默认)的消息,idleHandlerLagHandler是一个和HandlerThread绑定的Handler,它会将消息发送到HandlerThread子线程执行,假如2s内queueIdle方法执行完成,那么这个消息就会被移除,也就是不会触发上报。

这个消息做了什么呢?我们接下来看看这个idleHandlerLagRunnable。

IdleHandlerLagRunnable

这里也就是在收集信息上报了。

ini 复制代码
static class IdleHandlerLagRunable implements Runnable {
    @Override
    public void run() {
        String stackTrace = Utils.getMainThreadJavaStackTrace();
        boolean currentForeground = AppForegroundUtil.isInterestingToUser();
        String scene = AppActiveMatrixDelegate.INSTANCE.getVisibleScene();
        JSONObject jsonObject = new JSONObject();
        jsonObject = DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication());
        jsonObject.put(SharePluginInfo.ISSUE_STACK_TYPE, Constants.Type.LAG_IDLE_HANDLER);             jsonObject.put(SharePluginInfo.ISSUE_SCENE, scene);
        jsonObject.put(SharePluginInfo.ISSUE_THREAD_STACK, stackTrace);
        jsonObject.put(SharePluginInfo.ISSUE_PROCESS_FOREGROUND, currentForeground);
        Issue issue = new Issue();
        issue.setTag(SharePluginInfo.TAG_PLUGIN_EVIL_METHOD);
        issue.setContent(jsonObject);
        plugin.onDetectIssue(issue);
    }
}

onStopTrace

onStopTrace会调用到onDead方法,这里就是当任务停止时,移除所有消息。

typescript 复制代码
@Override
public void onDead() {
    super.onDead();
    if (traceConfig.isIdleHandlerTraceEnable()) {
        idleHandlerLagHandler.removeCallbacksAndMessages(null);
    }
}

总结

IdleHandlerLagTracer的实现逻辑还是很简单的,它通过hook的方法替换了主线程消息队列的IdleHandlers集合,从而拦截到了IdleHandler的添加和移除逻辑,在拦截到添加IdleHandler的操作时,为原来的IdleHandler做一层代理,从来可以在queueIdle方法执行时做超时监听逻辑,超时未执行完成则收集信息上报,从而发现IdleHandler导致的卡顿问题。

相关推荐
Lzh编程小栈29 分钟前
【嵌入式底层】SPI超透彻详解:通信原理、四线结构、四种模式、寄存器底层 + 面试全集
c语言·stm32·单片机·面试·职场和发展
Rain的Java大神之路2 小时前
线上接口负载满了如何解决
java·数据库·redis·后端·缓存·面试·架构
Dr.kangder2 小时前
嵌入式面试总结(十八)——JTAG调试
嵌入式硬件·面试·职场和发展·架构·嵌入式
小孔龙4 小时前
GraphicBuffer 跨进程共享
android
行业研究员5 小时前
Android内置GPS与腾讯地图定位技术对比分析
android·android定位·腾讯地图定位
Dr.kangder5 小时前
嵌入式面试总结(十六)——AMBA总线
面试·职场和发展·架构·嵌入式·虚拟化·总线
Dr.kangder5 小时前
嵌入式面试总结(十九)——内存泄露
单片机·算法·面试·职场和发展·架构·硬件架构
Dr.kangder5 小时前
嵌入式面试总结(十七)——DMA总线
面试·职场和发展·架构·嵌入式·总线
Android-Flutter5 小时前
android WMS 详解(二)
android·kotlin