基于面试问题005和006,我们已经基本了解了Handler内部的运行原理,主线程子线程使用Handler的注意事项,消息及消息池等,接下来我们来看下Handler关联的其他内容。
知道IdleHandler吗?有用过吗?
IdleHandler是声明在MessageQueue中的接口类,通过MessageQueue.addIdleHandler可以向消息队列中的mIdleHandlers成员添加一个IdleHandler对象,当线程空闲,消息队列出于阻塞状态时,会主动调用IdleHandler接口的queueIdle方法,当该方法返回false时,方法执行完成后,会自动移除该IdleHandler对象,IdleHandler相关代码如下:
IdleHandler对象入队
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
执行IdleHandle.queueIdle方法
Message next() {
...
for (;;) {
...
synchronized (this) {
...
// 获取当前需要处理的IdleHandler个数
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);
}
// 依此处理IdleHandler,调用IdleHandler.queueIdle
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);
}
// 如果queueIdle返回false
// 则自动移除该IdleHandler
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
...
}
}
主动移除IdleHandler
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
综上,我们可以看出,在线程空间,队列阻塞时,都会处理IdleHandler,当我们在IdleHandler.queueIdle返回true时,该IdleHandler常驻,每当线程空闲时,就会执行该IdleHandler。
IdleHandler用处
基于以上理论,我们可以看出IdleHandler适用于一些需要利用线程空闲执行任务的场景下,比如在Activity启动时,在不影响功能的前提下,我们可以将部分有可能影响启动速度的耗时操作放在IdleHandler中执行,IdleHandler通常用于优化启动速度,但应注意避免因使用IdleHandler导致队列阻塞(占用资源导致阻塞),监控IdleHandler执行时间。
系统中的IdleHandler
在ActivityThread.handleResumeActivity中,在成功将待启动Activity设为可见状态并回调onResume后,调用Idler类执行ActivityManagerService.activityIdle方法,销毁一些在后台的Activity以便释放系统资源,相关代码如下:
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
...
Looper.myQueue().addIdleHandler(new Idler());
}
private class Idler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
ActivityClientRecord a = mNewActivities;
boolean stopProfiling = false;
if (mBoundApplication != null && mProfiler.profileFd != null
&& mProfiler.autoStopProfiler) {
stopProfiling = true;
}
if (a != null) {
mNewActivities = null;
final ActivityClient ac = ActivityClient.getInstance();
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) {
ac.activityIdle(a.token, a.createdConfig, stopProfiling);
a.createdConfig = null;
}
prev = a;
a = a.nextIdle;
prev.nextIdle = null;
} while (a != null);
}
if (stopProfiling) {
mProfiler.stopProfiling();
}
applyPendingProcessState();
return false;
}
}
当然在ActivityThread中还有GcIdler,PurgeIdler等IdlerHandler的实现,大家可以按需查看源码学习下。
Handler线程切换原理
基于上图可以看出,Handler线程切换本质上是基于共享变量(通过Handler开放了MessageQueue的操作入口)实现的,两个或多个线程共享同一个线程的Handler对象,从而达到向该线程发送消息的目的,当消息添加到消息队列时,Looper循环被唤醒,处理该消息。
对于子线程往主线程发消息而言,可以直接通过
Handler handler= new Handler(Looper.getMainLooper())
来获取Handler对象。
Handler内存泄漏的原因及解决方案
假设我们发送了一个延时60s的消息,在60秒到来前执行了MainActivity的destroyed方法,此时我们有可能会发现GC后,MainActivity实例仍然存在在内存中,此时就说明发生了内存泄漏。
内存泄漏指的是本应被销毁的对象被比它生命周期更长的对象持有,而导致内存空间无法被释放的现象。
通常情况下开发者会使用如下方式创建Handler对象:
private Handler mHandler = new Handler() {
@Override
public void dispatchMessage(@NonNull Message msg) {
super.dispatchMessage(msg);
}
};
假设这段代码在MainActivity中,我们查看MainActivity的字节码,可以发现mHandler这个类对象在构造时持有了外部的MainActivity的引用,代码如下:
也就是非静态内部类持有外部类引用。
那么我们结合前文来分析引用关系,在这种情况下,自底向上的关联关系如下:
MainActivity->Handler->Message->MessageQueue->Looper->ThreadLocal->sThreadLocalMap->Thread
逆序即为MainActivity被持有的关系,由于主线程的生命周期远远大于MainActivity的生命周期,在MainActivity销毁时,延时60s的消息仍然在MessageQueue中,导致其无法被GC,进而形成内存泄漏,内存泄漏一般是因为对象直接或间接被GC Roots持有而导致的。
解决方案也很简单,主要有将非静态匿名内部类修改为静态内部类和在Activity onDestroyed时调用removeCallbacksAndMessages两种方案解决。
MainActivity的完整引用链如下: