IdleHandler 的原理分析和妙用

// which is not supported.

···

int nextPollTimeoutMillis = 0;

for (;😉 {

if (nextPollTimeoutMillis != 0) {

Binder.flushPendingCommands();

}

···

nativePollOnce(ptr, nextPollTimeoutMillis); //没有消息,阻塞等待

···

}

}

该方法在 nextPollTimeoutMillis = -1的时候就阻塞等待,直到下一条消息可用为止。否则就继续向下执行。那我们再看看是在哪里唤醒的呢?是在消息入队最终执行的方法 enqueueMessage 中:

boolean enqueueMessage(Message msg, long when) {

···

// We can assume mPtr != 0 because mQuitting is false.

if (needWake) {

nativeWake(mPtr);

}

}

return true;

}

在 nativeWake 方法中进行唤醒,就是唤醒上面的那个地方,没有消息的时候,这里就处于阻塞状态。

这样我们把消息处理机制的整个逻辑大概梳理了一下,为什么需要理清呢,因为 IdleHandler 是在消息队列没有消息或者是在有暂时不需要处理的消息(延迟消息),就是说这个时候是空闲的,进行 IdleHandler 进行处理的。所以我们可以猜测 IdleHandler 应该也在 next 方法中进行触发它的方法。事实也确实如此:

Message next() {

for (;😉 {

synchronized (this) {

// 此处为正常消息队列的处理

if (mQuitting) {

dispose();

return null;

}

if (pendingIdleHandlerCount < 0

&& (mMessages == null || now < mMessages.when)) {

pendingIdleHandlerCount = mIdleHandlers.size();

}

if (pendingIdleHandlerCount <= 0) {

// No idle handlers to run. Loop and wait some more.

mBlocked = true;

continue;

}

if (mPendingIdleHandlers == null) {

mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];

}

//mIdleHandlers 数组,赋值给 mPendingIdleHandlers

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 {

keep = idler.queueIdle();

} catch (Throwable t) {

Log.wtf(TAG, “IdleHandler threw exception”, t);

}

if (!keep) {

synchronized (this) {

mIdleHandlers.remove(idler);

}

}

}

pendingIdleHandlerCount = 0;

nextPollTimeoutMillis = 0;

}

}

看 MessageQueue 的源码可以发现有两处关于 IdleHandler 的声明,分别是:

  • 存放 IdleHandler 的 ArrayList(mIdleHandlers),

  • 还有一个 IdleHandler 数组 (mPendingIdleHandlers)。

后面的数组,它里面放的 IdleHandler 实例都是临时的,也就是每次使用完(调用了queueIdle 方法)之后,都会置空(mPendingIdleHandlers[i] = null),在 MessageQueue 的 next() 方法中

大致的流程是这样的:

  1. 如果本次循环拿到的 Message 为空,或者这个 Message 是一个延时的消息而且还没到指定的触发时间,那么,就认定当前的队列为空闲状态。

  2. 接着就会遍历 mPendingIdleHandlers 数组(这个数组里面的元素每次都会到 mIdleHandlers 中去拿)来调用每一个IdleHandler 实例的 queueIdle 方法。

  3. 如果这个方法返回false的话,那么这个实例就会从 mIdleHandlers 中移除,也就是当下次队列空闲的时候,不会继续回调它的 queueIdle 方法了。

处理完 IdleHandler 后会将 nextPollTimeoutMillis 设置为0,也就是不阻塞消息队列,当然要注意这里执行的代码同样不能太耗时,因为它是同步执行的,如果太耗时肯定会影响后面的 message 执行。

系统源码中的使用

知道了这个 IdleHandler 是如何被触发的,我们再来看看系统源码时如何使用它的:比如 ActivityThread.Idle 在 ActivityThread.handleResumeActivity()中调用。

@Override

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,

String reason) {

// If we are getting ready to gc after going to the background, well

// we are back active so skip it.

unscheduleGcIdler();

mSomeActivitiesChanged = true;

···

//该方法最终会执行 onResume方法

final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);

if (r == null) {

// We didn’t actually resume the activity, so skipping any follow-up actions.

return;

}

···

···

r.nextIdle = mNewActivities;

mNewActivities = r;

if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);

Looper.myQueue().addIdleHandler(new Idler());

}

可以看到在 handleResumeActivity() 方法中末尾会执行 Looper.myQueue().addIdleHandler(new Idler()),也就是说在 onResume 等方法都执行完,界面已经显示出来了,那么这个 Idler 是用来干嘛的呢?先来看看这个内部类的定义:

private class Idler implements MessageQueue.IdleHandler {

@Override

public final boolean queueIdle() {

ActivityClientRecord a = mNewActivities;

···

if (a != null) {

mNewActivities = null;

IActivityManager am = ActivityManager.getService();

ActivityClientRecord prev;

do {

//打印一些日志

if (localLOGV) Slog.v(

TAG, "Reporting idle of " + a +

" finished=" +

(a.activity != null && a.activity.mFinished));

if (a.activity != null && !a.activity.mFinished) {

try {

//AMS 进行一些资源的回收

am.activityIdle(a.token, a.createdConfig, stopProfiling);

a.createdConfig = null;

} catch (RemoteException ex) {

throw ex.rethrowFromSystemServer();

}

}

prev = a;

a = a.nextIdle;

prev.nextIdle = null;

} while (a != null);

}

if (stopProfiling) {

mProfiler.stopProfiling();

}

//确认Jit 可以使用,否则抛出异常

ensureJitEnabled();

return false;

}

}

可以看到在 queueIdle 方法中会进行回收等操作,下面会详细讲解,但这一些都是等 onResume 方法执行完,界面已经显示这些更重要的事情已经处理完了,空闲的时候开始处理这些事情。也就是说系统的设计逻辑是保障最重要的逻辑先执行完,再去处理其他次要的事情。

但是如果 MessageQueue 队列中一直有消息,那么 IdleHandler 就一直没有机会被执行,那么原本该销毁的界面的 onStop,onDestory 就得不到执行吗?不是这样的,在 resumeTopActivityInnerLocked() -> completeResumeLocked() -> scheduleIdleTimeoutLocked() 方法中会发送一个会发送一个延迟消息(10s),如果界面很久没有关闭(如果界面需要关闭),那么 10s 后该消息被触发就会关闭界面,执行 onStop 等方法。

常见使用方式

  1. 在应用启动时我们可能希望把一些优先级没那么高的操作延迟一点处理,一般会使用 Handler.postDelayed(Runnable r, long delayMillis)来实现,但是又不知道该延迟多少时间比较合适,因为手机性能不同,有的性能较差可能需要延迟较多,有的性能较好可以允许较少的延迟时间。所以在做项目性能优化的时候可以使用 IdleHandler,它在主线程空闲时执行任务,而不影响其他任务的执行。

  2. 想要在一个 View 绘制完成之后添加其他依赖于这个 View 的 View,当然这个用View.post()也能实现,区别就是前者会在消息队列空闲时执行

  3. 发送一个返回 true 的 IdleHandler,在里面让某个 View 不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作

第三方库的使用

LeakCanary(1.5源码)

我们来看看 LeakCanary 的使用,是在AndroidWatchExecutor 这个类中

public final class AndroidWatchExecutor implements WatchExecutor {

static final String LEAK_CANARY_THREAD_NAME = “LeakCanary-Heap-Dump”;

private final Handler mainHandler;

private final Handler backgroundHandler;

private final long initialDelayMillis;

private final long maxBackoffFactor;

public AndroidWatchExecutor(long initialDelayMillis) {

mainHandler = new Handler(Looper.getMainLooper());

HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);

handlerThread.start();

backgroundHandler = new Handler(handlerThread.getLooper());

this.initialDelayMillis = initialDelayMillis;

maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;

}

//初始调用

@Override public void execute(Retryable retryable) {

// 最终都会切换到主线程,调用waitForIdle()方法

if (Looper.getMainLooper().getThread() == Thread.currentThread()) {

waitForIdle(retryable, 0);

} else {

postWaitForIdle(retryable, 0);

}

}

private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {

mainHandler.post(new Runnable() {

@Override public void run() {

waitForIdle(retryable, failedAttempts);

}

});

}

//IdleHandler 使用

void waitForIdle(final Retryable retryable, final int failedAttempts) {

// This needs to be called from the main thread.

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {

@Override public boolean queueIdle() {

postToBackgroundWithDelay(retryable, failedAttempts);

return false;

}

});

}

//最终的调用方法

private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {

long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);

long delayMillis = initialDelayMillis * exponentialBackoffFactor;

backgroundHandler.postDelayed(new Runnable() {

@Override public void run() {

Retryable.Result result = retryable.run();

if (result == RETRY) {

postWaitForIdle(retryable, failedAttempts + 1);

}

}

}, delayMillis);

}

}

再来看看 execute() 这个方法在何处被调用,我们知道 LeakCancary 是在界面销毁 onDestroy 方法中进行 refWatch.watch() 的,而watch() -> ensureGoneAsync() -> execute()

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {

watchExecutor.execute(new Retryable() {

@Override public Retryable.Result run() {

return ensureGone(reference, watchStartNanoTime);

}

});

}

而 ensureGone() 中会进行 GC 回收和一些分析等操作,所以通过这些分析后,我们可以知道 LeakCanary 进行内存泄漏检测并不是 onDestry 方法执行完成后就进行垃圾回收和一些分析的,而是利用 IdleHandler 在空闲的时候进行这些操作的,尽量不去影响主线程的操作。

尾声

你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?

改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
er 在空闲的时候进行这些操作的,尽量不去影响主线程的操作。

尾声

你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?

改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-bVOAGwgv-1715837714582)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值