IdleHandler的使用及剖析

一、IdleHandler是什么?怎么使用?

IdleHandler就是Handler机制提供的一种可以在Looper事件循环的过程中,当出现空闲的时候,允许我们去执行任务的一种机制。

IdleHandler被定义在MessageQueue中,它是一个接口:

// MessageQueue.java
public static interface IdleHandler {
  boolean queueIdle();
}

可以看到,定义时需要实现其queueIdle()方法,同时返回值为true表示是一个持久的IdleHandler,会重复使用;返回false表示是一个一次性的IdleHandler。

在MessageQueue中还定义了对应的add和remove方法:

// MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) {
	// ...
  synchronized (this) {
    mIdleHandlers.add(handler);
  }
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
  synchronized (this) {
    mIdleHandlers.remove(handler);
  }
}

可以看到 add 或 remove其实操作的都是mIdleHandlers,它的类型是一个ArrayList:

private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

既然IdleHandler主要是在MessageQueue出现空闲的时候被执行的,那么何时会出现空闲?

  • MessageQueue维护的消息队列为空,没有消息。
  • MessageQueue中最近要处理的消息,是一个延迟消息(when>currentTime),需要滞后执行。

这两个场景都会尝试执行IdleHandler。
处理IdleHandler的场景,就在Message.next()这个获取消息队列下一个待执行消息的方法中:

Message next() {
	// ...
  int pendingIdleHandlerCount = -1; 
  int nextPollTimeoutMillis = 0;
  for (;;) {
    nativePollOnce(ptr, nextPollTimeoutMillis);
 
    synchronized (this) {
      // ...
      if (msg != null) {
        if (now < msg.when) {
          // 计算休眠的时间
          nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
        } else {
          // Other code
          // 找到消息处理后返回
          return msg;
        }
      } else {
        // 没有更多的消息
        nextPollTimeoutMillis = -1;
      }
      
      //执行到这里说明,要不消息队列为空,要么待处理的消息是延迟消息(因为如果有需要执行的非延迟消息的话,在上面 return msg 处就已经找到该消息做返回了,就不会执行到这里)
      if (pendingIdleHandlerCount < 0
          && (mMessages == null || now < mMessages.when)) {
        pendingIdleHandlerCount = mIdleHandlers.size();
      }
      //这里判断如果有待处理的IdleHandler的数量为空,就不会往下执行了,会跳过本次循环。
      if (pendingIdleHandlerCount <= 0) {
        mBlocked = true;
        continue;
      }
 
      if (mPendingIdleHandlers == null) {
        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
      }
      mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
    }
 
    for (int i = 0; i < pendingIdleHandlerCount; i++) {
      final IdleHandler idler = mPendingIdleHandlers[i];
      mPendingIdleHandlers[i] = null; 
 
      boolean keep = false;
      try {
        keep = idler.queueIdle();
      } catch (Throwable t) {
        Log.wtf(TAG, "IdleHandler threw exception", t);
      }
 
 		//这里keep的值取值于idler.queueIdle(),即回调方法中的返回值,如果为false,则在从集合里移除掉这个IdleHandler,如果为true,则忽略。(这里正说明了,queueIdle的返回值如果是false,那么就是一次性的,如果为true,那么在每次空闲时都会回调queueIdle)
      if (!keep) {
        synchronized (this) {
          mIdleHandlers.remove(idler);
        }
      }
    }
 
    pendingIdleHandlerCount = 0;
    nextPollTimeoutMillis = 0;
  }
}

我们先解释一下next()中关于 IdleHandler执行的主逻辑:
(1)准备执行IdleHandler时,说明当前待执行的消息为null,或者这条消息的执行时间未到;
(2)当pendingIdleHandlerCout<0时,根据mIdleHandlers.size()赋值给pendingIdleHandlerCount,它是后期循环的基础;
(3)将mIdleHandlers中的IdleHandler拷贝到mPendingIdleHandlers数组中,这个数组是临时的,之后进入for循环。
(4)循环从数组取出IdleHandler,并调用其queueIdle()记录返回值存到keep中;
(5)当keep为false时,从mIdleHandler中移除当前循环的IdleHandler,反之则保留;

需要注意的是,对mIdleHandler的操作,都通过synchronized来保证线程安全,这一点无需担心。

二、IdleHandler是如何保证不进入死循环的?

当队列空闲时,会循环执行一遍mIdleHandlers数组并执行IdleHandler.queueIdle()方法。而如果数组中有一些IdleHandler的queueIdle()方法返回了true,则会保留在mIdleHandlers数组中,下次依然会再执行一遍。

注意现在代码逻辑还在MessageQueue.next()的循环中,在这个场景下IdleHandler机制是如何保证不会进入死循环的?

实际不会死循环的关键是在于pendingIdleHandlerCount,我们看看下面的代码:

Message next() {
	// ...
  // Step 1
  int pendingIdleHandlerCount = -1; 
  int nextPollTimeoutMillis = 0;
  for (;;) {
    nativePollOnce(ptr, nextPollTimeoutMillis);
 
    synchronized (this) {
      // ...
      // Step 2
      if (pendingIdleHandlerCount < 0
          && (mMessages == null || now < mMessages.when)) {
        pendingIdleHandlerCount = mIdleHandlers.size();
      }
     	// Step 3
      if (pendingIdleHandlerCount <= 0) {
        mBlocked = true;
        continue;
      }
      // ...
    }
		// Step 4
    pendingIdleHandlerCount = 0;
    nextPollTimeoutMillis = 0;
  }
}

我们梳理一下:

  • Step1,循环开始前,pendingIdleHandlerCount 的初始值为-1;
  • Step2,在pendingIdleHandlerCount<0时,才会通过mIdleHandlers.size()赋值。也就是说只有第一次循环才会改变pendingIdleHandlerCount的值;
  • Step3,如果pendingIdleHandlerCount<=0时,则循环continue;
  • Step4,重置pendingIdleHandlerCount 为0;

在第二次循环时,pendingIdleHandlerCount等于0,在Step2不会改变它的值,那么在Step3中会直接continue继续下一次循环,此时没有机会修改nextPollTimeoutMillis。

那么nextPollTimeoutMillis的值有两种可能:-1 或者 下次唤醒的等待时间间隔,在执行到nativePollOnce()时就会进入休眠,等待再次被唤醒。

下次唤醒时,mMessage必然会有一个待执行的Message,则MessageQueue.next() 返回到Looper.loop()的循环中,分发处理这个Message,之后又是新一轮的next()中去循环。

三、系统中有哪些地方会用到IdleHandler机制?

在AS中搜一下IdleHandler:
请添加图片描述

  • ActivityThread.Idler 在ActivityThread.handleResumeActivity()中调用
  • ActivityThread.GcIdler 是在内存不足时,强行GC
  • Instrumentation.ActivityGoing 在Activity onCreate()执行前添加;
  • Instrumentation.Idler 调用的时机就比较多了,是键盘相关的调用
  • TextToSpeechService.SynthThread 是在TTS合成完成之后发送广播

四、一些面试问题

  1. Q:IdleHandler有什么作用?
    (1)IdleHandler 是Handler提供的一种在消息队列空闲时,执行任务的时机;
    (2)当MessageQueue 没有立即需要处理的消息时,会执行IdleHandler
  2. Q:MessageQueue 提供了 add/remove IdleHandler的方法,是否需要成对使用?
    (1)不是必须
    (2)IdleHandler.queueIdle()的返回值,可以移除加入MessageQueue的IdleHandler
    3、Q:当mIdleHandlers一直不为空时,为什么不会进入死循环?
    (1)只有在pendingIdleHandlerCount 为-1时,才会尝试执行mIdleHandler;
    (2)pedingIdleHandlerCount在next() 中初始时为-1,执行一遍后会被置为0,所以不会重复执行。
    4、Q:是否可以将一些不重要的启动服务,搬移到IdleHandler中去处理?
    (1)不建议;
    (2)IdleHandler的处理时机不可控,如果MessageQueue中一直有待处理的消息,那么IdleHandler的执行时机会很靠后。
    5、IdleHandler的queueIdle()运行在哪个线程?
    (1)queueIdle() 运行的线程,只和当前MessageQueue的Looper所在的线程有关;
    (2)子线程一样可以构造Looper,并添加IdleHandler;

五、小结

IdleHandler是Handler提供的一种在消息队列空闲时,执行任务的时机。但它执行的时机依赖消息队列的情况,那么如果MessageQueue 一直有待执行的消息时,IdleHandler就一直得不到执行,也就是它的执行时机是不可控的,不适合执行一些对时机要求比较高的任务。

IdleHandler 在API上面的解释如下:

public final void addIdleHandler (MessageQueue.IdleHandler handler)

向消息队列中添加一个新的MessageQueue.IdleHandler。当调用IdleHandler.queueIdle()返回false时,此MessageQueue.IdleHandler会自动的从消息队列中移除。或者调用removeIdleHandler(MessageQueue.IdleHandler)也可以从消息队列中移除MessageQueue.IdleHandler。

此方法是线程安全的。

注意:在主线程中使用时queueIdle中不能执行太耗时的任务。

六、使用场景

1、用在Android初始化Activity界面时。比如,想用Android做一个播放器,如果下面包括进度条,暂停、停止等按钮的空间用PopWindow实现的话。就是程序一运行起来就需要将下面的PopupWindow显示在Activity上。用这个是比较好的,当然你也可以用Handler的sendEmptyMessage()去做你想要的操作。

把IdleHandler用在onCreate()里面,用法很简单:

Looper.myQueue().addIdleHandler(new IdleHandler()
{
 
@Override
public boolean queueIdle()
{
 
// TODO Auto-generated method stub
//你想做的任何事情
//........
//........
return false;
}
});

2、在Android项目中都会遇到希望一些操作延迟一点的处理,一般会使用Handler.postDelayed(Runnable r,long delayMillis)来实现,但是又不知道该延迟多少时间比较合适,因为手机性能不同,有的性能差可能需要延迟较多,有的性能好可以允许较少的延迟时间。

对于多个任务的延迟加载,如果addIdleHandler()调用多次明显不太优雅,而且也不要把所有要延迟的任务都一起放到queueIdle()方法内。根据queueIdle()返回true时可以执行多次的特点,也可以实现一个任务列表,然后从这个任务列表中取任务执行。

下面给出具体实现方案:

import android.os.Looper;
import android.os.MessageQueue;
 
import java.util.LinkedList;
import java.util.Queue;
 
public class DelayTaskDispatcher {
    private Queue<Task> delayTasks = new LinkedList<>();
 
    private MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            if (delayTasks.size() > 0) {
                Task task = delayTasks.poll();
                if (task != null) {
                    task.run();
                }
            }
            return !delayTasks.isEmpty(); //delayTasks非空时返回ture表示下次继续执行,为空时返回false系统会移除该IdleHandler不再执行
        }
    };
 
    public DelayTaskDispatcher addTask(Task task) {
        delayTasks.add(task);
        return this;
    }
 
    public void start() {
        Looper.myQueue().addIdleHandler(idleHandler);
    }
}
//使用系统Runnable接口自定义Task接口
public interface Task extends Runnable {
 
}

使用方法:

    new DelayTaskDispatcher().addTask(new Task() {
            @Override
            public void run() {
                Log.d(TAG, "DelayTaskDispatcher one task");
            }
        }).addTask(new Task() {
            @Override
            public void run() {
                Log.d(TAG, "DelayTaskDispatcher two task");
            }
        }).start();

使用上述方式可以添加多个任务,在线程空闲时分别执行。

3、IdleHandler它在源码中的使用场景
比如在ActivityThread中,就有一个名叫GcIdler的内部类,实现了IdleHandler接口。

它在queueIdle方法被回调时,会做强行的GC操作(即调用BinderInternal的forceGc方法),但强行GC的前提是,与上一次强行GC至少相隔5秒以上。

在ActivityThread中的H收到GC_WHEN_IDLE消息后,会执行scheduleGcIdler,将GcIdler添加到MessageQueue中的空闲任务集合中。具体如下:

 void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            //添加GC任务
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

ActivityThread中GcIdler的详细声明:

	//GC任务
   final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            doGcIfNeeded();
            //执行后,就直接删除
            return false;
        }
    }
    // 判断是否需要执行垃圾回收。
    void doGcIfNeeded() {
        mGcIdlerScheduled = false;
        final long now = SystemClock.uptimeMillis();
        //获取上次GC的时间
        if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
            //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
            BinderInternal.forceGc("bg");
        }
    }

其他使用场景:

  • Activity启动优化(加快App启动速度):onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间
  • 想要在一个View绘制完成之后添加其他依赖于这个View的View,当然这个用View#post()也能实现,区别就是前者会在消息队列空闲时执行
  • 发送一个返回true的IdleHandler,在里面让某个View不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作
  • 一些第三方库中有使用,比如LeakCanary,Glide中有使用到,具体可以自行去查看

转载自:IdleHandler 是什么?怎么使用,能解决什么问题?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值