ANR系列——ANR监听方案之IdleHandler

Posted 许英俊潇洒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于ANR系列——ANR监听方案之IdleHandler的主要内容,如果未能解决你的问题,请参考以下文章

ANR系列——ANR监听方案之SyncBarrier

ANR系列之二:Input类型ANR产生原理讲解

ANR系列:如何分析ANR和避免ANR?

ANR系列:Service触发ANR的源码分析

ANR系列:不同组件多长时间后触发ANR?

ANR系列:广播触发ANR的原理