面试串讲007-Handler的其他问题(2)

72 阅读2分钟

基于面试问题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_thread_change

基于上图可以看出,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的引用,代码如下:

匿名Handler字节码

也就是非静态内部类持有外部类引用。

那么我们结合前文来分析引用关系,在这种情况下,自底向上的关联关系如下:

MainActivity->Handler->Message->MessageQueue->Looper->ThreadLocal->sThreadLocalMap->Thread

逆序即为MainActivity被持有的关系,由于主线程的生命周期远远大于MainActivity的生命周期,在MainActivity销毁时,延时60s的消息仍然在MessageQueue中,导致其无法被GC,进而形成内存泄漏,内存泄漏一般是因为对象直接或间接被GC Roots持有而导致的。

解决方案也很简单,主要有将非静态匿名内部类修改为静态内部类和在Activity onDestroyed时调用removeCallbacksAndMessages两种方案解决。

MainActivity的完整引用链如下:

mainactivity引用链