Matrix卡顿优化之IdleHandlerLagTracer源码分析

前言

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

复制代码
@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。

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

onStartTrace

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

复制代码
@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的操作都在我们的监控范围之内了。

复制代码
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方法调用的目的。

复制代码
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方法中,也就都在我们的监控之内了。

复制代码
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

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

复制代码
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方法,这里就是当任务停止时,移除所有消息。

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

总结

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

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
liangshanbo12151 天前
React 性能优化实战
性能优化·react
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas