ANR系列(二)——ANR监听方案之IdleHandler

前言

关于IdleHandler,比较多同学错误地认为,这个Handler的作用是主线程空闲状态时才执行它,那么用它做一些耗时操作也没所谓。可是IdleHandler在主线程的MessageQueue中,执行queueIdle()默认当然也是执行在主线程中的,这里的耗时操作其实很容易引起卡顿和ANR。

IdleHandler的介绍

IdleHandler是一种在只有当消息队列没有消息时或者是队列中的消息还没有到执行时间时才会执行的IdleHandler。从源码上看,IdleHandler是一个回调接口,当线程中的消息队列将要阻塞等待消息的时候,就会回调该接口,也就是说消息队列中的消息都处理完毕了,没有新的消息了,处于空闲状态时就会回调该接口。

public static interface IdleHandler {
    boolean queueIdle();
}

IdleHandler的使用

IdleHandler是MessageQueue的静态内部接口,通过静态方法就能拿得到,不过要注意的事,当前Looper是主线程的Looper的话,取到的也是主线程的MessageQueue

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
      	//空闲时处理逻辑
      
        return false;
    }
});

IdleHandler的问题

IdleHandler如果是主线程的执行超过5s同样也是会报ANR,我们通过主线程模拟休眠,会发现App直接ANR

Looper.myQueue().addIdleHandler(object : MessageQueue.IdleHandler {
    override fun queueIdle(): Boolean {
        Log.e("TAG", "[queueIdle] sleep(5000) start")
        Thread.sleep(5000)
        Log.e("TAG", "[queueIdle] sleep(5000) end")
        return false
    }
})

IdleHandler的监控分析

为了防止IdleHandler滥用,监控起来也是很有必要,特别是第三方产商经常通过这个接口做一些耗时操作。通过查看源码,找到IdleHandler的Hook点

  1. 通过Looper.myQueue().addIdleHandler()开始,可以看到是每次通过mIdleHandlers加入到队列中
public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}
  1. mIdleHandlers是一个列表,会保存每一个添加进来的IdleHandler
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
  1. Message#next()中,执行完Handler消息空闲后,会将当前的IdleHandler列表循环遍历执行queueIdle()
Message next() {
    .....
    
    for (;;) {
        .....
        
        if (mPendingIdleHandlers == null) {
            mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
        }
        // 1、取出所有IdleHandler
        mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
            
            boolean keep = false;
            try {
                // 2、执行IdleHandler的queueIdle()
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
    }
}

IdleHandler的监控实现

  1. IdleHandler的执行流程可以看出,Hook点就在mIdleHandlers列表中,将当前的MessageQueue中的mIdleHandlers列表替换成自己的列表
class IdleHandlerMonitor {

    private HandlerThread idleHandlerThread;
    private Handler idleHandlerHandler;
    private static String mIdleHandler = null;

    IdleHandlerMonitor() {
        // 1、创建子线程的handler(idleHandlerHandler),方便后续在子线程发送消息不被主线程卡住影响
        if (Build.VERSION.SDK_INT >= 23) {
            this.idleHandlerThread = new HandlerThread("IdleHandlerThread");
            this.idleHandlerThread.start();
            this.idleHandlerHandler = new Handler(this.idleHandlerThread.getLooper());
            this.detectIdleHandler();
        }
    }

    @RequiresApi(api = 23)
    private void detectIdleHandler() {
        // 2、修改MessageQueue中的mIdleHandlers变量,传入自定义的List
        try {
            MessageQueue mainQueue = Looper.getMainLooper().getQueue();
            Field field = MessageQueue.class.getDeclaredField("mIdleHandlers");
            field.setAccessible(true);
            CustomArrayList<MessageQueue.IdleHandler> myIdleHandlerArrayList = new CustomArrayList();
            field.set(mainQueue, myIdleHandlerArrayList);
        } catch (Throwable var4) {
            var4.printStackTrace();
        }
    }

    private class CustomArrayList<T> extends ArrayList {

        public boolean add(Object o) {
            if (o instanceof MessageQueue.IdleHandler) {
                // 3、将原来的IdleHandler包装进自己的CustomIdleHandler
                CustomIdleHandler customIdleHandler = new CustomIdleHandler((MessageQueue.IdleHandler) o);
                return super.add(customIdleHandler);
            }
            return super.add(o);
        }

        public boolean remove(@Nullable Object o) {
            if (o instanceof CustomIdleHandler) {
                return super.remove(((CustomIdleHandler) o));
            }
            return super.remove(o);
        }
    }

    private class CustomIdleHandler implements MessageQueue.IdleHandler {
        private MessageQueue.IdleHandler idleHandler;

        CustomIdleHandler(MessageQueue.IdleHandler idleHandler) {
            this.idleHandler = idleHandler;
        }

        @Override
        public boolean queueIdle() {
            mIdleHandler = this.idleHandler.toString();
            idleHandlerHandler.removeCallbacks(idleHanlderRunnable);
            idleHandlerHandler.postDelayed(idleHanlderRunnable, 3000L);
            // 4、将包装起来的IdleHandler取出来,执行queueIdle,包装前设置多一项3s的延时任务。
            // 只要queueIdle在3s内没执行完,将执行当前的idleHanlderRunnable
            boolean ret = this.idleHandler.queueIdle();
            idleHandlerHandler.removeCallbacks(idleHanlderRunnable);
            return ret;
        }
    }

    // 5、报告输出当前Idle信息超时通知
    private static Runnable idleHanlderRunnable = () -> {
        Log.e("TAG", "[queueIdle] more then 3000L \n message=" + mIdleHandler);
    };
}

hook的巧妙点在于将当前的List换成自己的List,然后在List的添加和删除中,偷梁换柱成自己的IdleHandler进行加工处理

IdleHandler的验证

Hook解决完之后,我们来通过Demo验证下是否是我们想要的监控,通过初始化IdleHandlerMonitor()启动监控,然后模拟3次IdleHandler发送不同时间的消息,最后看日志输出,是否被捕获到超时的Idle任务

private fun initIdelHandler() {
    IdleHandlerMonitor()
    Looper.myQueue().addIdleHandler(object : MessageQueue.IdleHandler {
        override fun queueIdle(): Boolean {
            Log.e("TAG", "[queueIdle] sleep(2000) start")
            Thread.sleep(2000)
            Log.e("TAG", "[queueIdle] sleep(2000) end")
            return false
        }
    })
    Looper.myQueue().addIdleHandler(object : MessageQueue.IdleHandler {
        override fun queueIdle(): Boolean {
            Log.e("TAG", "[queueIdle] sleep(5000) start")
            Thread.sleep(5000)
            Log.e("TAG", "[queueIdle] sleep(5000) end")
            return false
        }
    })
    Looper.myQueue().addIdleHandler(object : MessageQueue.IdleHandler {
        override fun queueIdle(): Boolean {
            Log.e("TAG", "[queueIdle] sleep(10000) start")
            Thread.sleep(10000)
            Log.e("TAG", "[queueIdle] sleep(10000) end")
            return false
        }
    })
}

通过日志输出结果,可以看到当前的Idle阻塞3s时候的代码类位置

E/TAG: [queueIdle] sleep(2000) start
E/TAG: [queueIdle] sleep(2000) end
E/TAG: [queueIdle] sleep(5000) start
E/TAG: [queueIdle] more then 3000L 
     message=com.example.syncbarriermonitor.MainActivity$initIdelHandler$2@4d9ab66
E/TAG: [queueIdle] sleep(5000) end
E/TAG: [queueIdle] sleep(10000) start
E/TAG: [queueIdle] more then 3000L 
     message=com.example.syncbarriermonitor.MainActivity$initIdelHandler$3@be7cc0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

许英俊潇洒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值