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
    评论
以下是几款常用的 Android 卡顿分析工具及使用介绍: 1. Systrace Systrace 是 Android SDK 提供的一款系统级分析工具,可以用于分析系统运行时的性能问题,包括 CPU 使用率、内存使用情况、线程运行情况、应用程序启动时间等等。使用 Systrace 可以帮助开发者快速定位应用程序卡顿的原因。 使用方法: 1)确保 Android SDK 中已经安装了 Systrace 工具。 2)在终端中输入以下命令,启动 Systrace 工具: ```python python systrace.py --time=10 -o mytrace.html gfx wm view sched freq idle ``` 其中,--time 参数指定采样时间,-o 参数指定输出文件名,gfx、wm、view、sched、freq、idle 参数指定要监控的系统事件。 3)在终端中输入以下命令,停止 Systrace 工具: ```python Ctrl + C ``` 4)使用浏览器打开输出的 HTML 文件,即可查看 Systrace 分析结果。 2. Traceview Traceview 是 Android SDK 提供的一款应用程序级分析工具,可以用于分析应用程序在运行时的性能问题,包括方法调用时间、内存使用情况、线程运行情况等等。使用 Traceview 可以帮助开发者定位应用程序卡顿的原因。 使用方法: 1)在应用程序代码中插入 Trace 开始和结束语句,例如: ```java Debug.startMethodTracing("mytrace"); // 要分析的代码逻辑 Debug.stopMethodTracing(); ``` 2)在 Android Studio 中打开应用程序项目,选择菜单栏中的 Run -> Profile 'app',打开 Profiler 工具。 3)在 Profiler 工具中选择 CPU 标签页,点击 Record 按钮开始录制 Trace 数据。 4)在应用程序中操作,触发卡顿问题。 5)点击 Stop 按钮停止录制 Trace 数据。 6)在 Profiler 工具中选择 CPU 标签页,点击 Open in Traceview 按钮,打开 Traceview 工具。 7)在 Traceview 工具中查看 Trace 数据,定位应用程序卡顿的原因。 3. Profiler Profiler 是 Android Studio 提供的一款性能分析工具,可以用于分析应用程序的性能问题,包括 CPU 使用率、内存使用情况、线程运行情况等等。使用 Profiler 可以帮助开发者快速定位应用程序卡顿的原因。 使用方法: 1)在 Android Studio 中打开应用程序项目,选择菜单栏中的 Run -> Profile 'app',打开 Profiler 工具。 2)在 Profiler 工具中选择 CPU 标签页,点击 Record 按钮开始录制性能数据。 3)在应用程序中操作,触发卡顿问题。 4)点击 Stop 按钮停止录制性能数据。 5)在 Profiler 工具中查看性能数据,定位应用程序卡顿的原因。 4. CTS (Compatibility Test Suite) CTS 是 Android SDK 提供的一款兼容性测试工具,可以用于测试应用程序的兼容性和性能。CTS 可以帮助开发者发现应用程序中的性能问题,包括卡顿、崩溃等等。 使用方法: 1)在 Android SDK 中安装 CTS 工具。 2)在终端中输入以下命令,运行 CTS 测试: ```python cts-tradefed run cts --plan CTS --device <device_id> ``` 其中,--plan 参数指定测试计划,--device 参数指定测试设备。 3)查看测试结果,定位应用程序卡顿的原因。 总之,Android 卡顿分析工具种类繁多,开发者可以根据实际需要选择合适的工具进行分析和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值