高空利剑-Handler通信源码分析-Handler面试之问

本文源码取自Android API Level 30

本文目标

彻底搞懂Handler源码套路以及经典面试8问

套路

我们就从最简单的场景入手,使用Handler来更新UI,子线程通过网络请求从服务器拿到数据,然后把数据用发消息的方式发送到主线程然后更新UI,这个时候就会调用到Handler的sendMessage() 或者 sendMessageDelayed() 这样的方法,然后我们追踪进sendMessage()方法:

Handler类

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        //currentTimeMillis()系统当前时间,即日期时间,可以被系统设置修改
        //uptimeMillis()自开机后,经过的时间,不包括深度休眠时间
        //SystemClock.uptimeMillis() + delayMillis   当前时间 + 延迟时间
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

以上方法从sendMessage()一路调用到最后的 MessageQueue 的enqueueMessage()方法

MessageQueue类

boolean enqueueMessage(Message msg, long when) {
        // 判断有没有 target 
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        // 对当前消息队列加锁
        synchronized (this) {
            // 有没有在使用 
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
            // 判断消息队列是否弃用(通常因为线程已死)
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
            // 标记消息正在使用中
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            // 第一次添加数据到队列中,或者当前 msg 的时间小于 mMessages 的时间
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                // 把当前 msg 添加到链表的第一个
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                // 不是第一次添加数据,并且 msg 的时间 大于 mMessages(头指针) 的时间
                //要不要唤醒Looper= 当前Looper处于休眠状态 & 队头消息是同步屏障消息 & 新消息是异步消息
                //目的是为了让异步消息尽早执行
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {   // 不断的遍历找到合适的位置
                    prev = p;
                    p = p.next;
                    //按时间顺序,找到该消息,合适的位置
                    //>>msg1.wneh=2--->msg.when=4-->msg.wneh-->6
                    //>>msg1.wneh=2--->msg.when=4-->【newMsg.when=5】-->msg.wneh-->6
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 调整链表中节点的指向关系
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            
            // 唤醒looper
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

仔细观察以上代码发现,Message只是添加到MessageQueue队列中了,其实这个队列用的是单向链表,就是说第1个Message对象里面有个next字段,然后这个next字段指向第2个Message对象,然后第2个Message对象的next指向第3个Message对象,如此往复,然后一直指到next为null,链表结束.而且这个链表的顺序也是根据when的时间进行动态排列先后的,简单来说就是when时间越短越往前排,这个时间是从Handler类中的sendMessageDelayed方法(SystemClock.uptimeMillis() + delayMillis 当前时间 + 延迟时间 )计算得到的,而且这个SystemClock.uptimeMillis()方法是系统开机时间,只有手机深度休眠后这个值不会更新,用以下代码发送消息,在队列中如图展示

// 发送 Message1
Message message1 = new Message();
mHandler.sendMessage(message1, 0);

// 发送 Message2
Message message2 = new Message();
mHandler.sendMessageDelayed(message2,1000);

// 发送 Message3
Message message3 = new Message();
mHandler.sendMessageDelayed(message3, 500);

上面的一顿分析,我们只是看到handler发送了消息进到了MessageQueue消息队列中,然后就没了,并没有看到handler的handleMessage()方法被调用,这个时候先别捉急,先看一种现象,有时候这么写会报错

  new Thread(){
      @Override
      public void run() {
          Handler handler = new Handler();
      }
  }.start();

必须要这么写才可以:

    new Thread(){
       @Override
       public void run() {
          Looper.prepare();
          Handler handler = new Handler();
          Looper.loop();
        }
     }.start();

我们在Activivity中从来没有调用用Looper.prepare();为什么从来不报错呢?那是因为在应用的启动流程中,在ActivityThread的main方法中,系统帮我们做了

ActivityThread类

public static void main(String[] args) {
    // ... 省略部分代码

    //准备循环
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    //开始循环
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

重点看 Looper.prepareMainLooper();和Looper.loop();这两行代码,先看prepareMainLooper()

Looper类

   @UnsupportedAppUsage
   static final ThreadLocal sThreadLocal = new ThreadLocal();

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

    /**
    *该方法就是把looper对象存到ThreadLocal中
   * 一个线程如何保证只有1个Looper
    *1.首先利用ThreadLocal把Looper对象存到Thread对象的map中
    * 2.然后就这个prepare方法里面会做判断,判断ThradLocal是否已经有存    
     *Looper对象,如果有则直接报错
    */
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

可以看出,就是创建了Looper对象然后存到了ThreadLocal中,用来保证1个线程只有1个Looper对象,这样就保证了线程的安全.
然后看Looper.loop() 这行:

    public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        // 一个死循环
        for (;;) {
            // 不断的从消息队列里面取消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                // 通过 target 去 dispatchMessage 而 target 就是绑定的 Handler
                msg.target.dispatchMessage(msg);
            } catch (Exception exception) {
            } finally {
            }
            // 消息回收循环利用
            msg.recycleUnchecked();
        }
    }

看到这里流程就清晰了,Looper.prepareMainLooper()创建了一个Looper对象,而且保证一个线程只有一个Looper;
Looper.loop() 这里面是个死循环,不断的从MessageQueue消息队列中取消息,然后通过Handler执行.Handler源码至此分析完毕

总结

handler 持有 Looper对象
Looper对象持有MessageQueue
handler用于线程间通信,可以把子线程的数据发送到主线程,展开来说就是handler通过sendMessage()或者sendDelayMessage()方法发送信息到MessageQueue消息队列中,会根据when的时间动态的更改消息队列中的位置,简单来说时间越短越位置越靠前.然后因为应用启动的时候在ActivityThreadmain方法中会调用Looper.prepareMainLooper()方法,这个方法的作用就是创建一个Looper对象然后存到ThreadLocal中(保证线程的安全性,一个线程对应一个looper),然后还会调用Looper.loop()方法,该方法主要是开启消息的轮询,如果有消息就调用handlerdispatchMessage()方法,然后调用handlerhandleMessage()方法.

面试8问

1.为什么主线程不会因为 Looper.loop()里的死循环卡死?

主线程确实是通过Looper.loop()进入了循环状态,因为这样主线程才不会向我们一般创建的线程一样,当可执行代码执行完后,线程生命周期就终止了.
在主线程的MessageQueue没有消息时候,便阻塞在MessageQueue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到新消息达到.所以主线程大多数时候都是出于休眠状态,并不会消耗大量CPU资源
这里采用linux的epoll机制,是一种IO多路复用机制,可以同时监控多个文件描述符,当某个文件描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作拿到最新的消息,进而唤醒等待的线程.

2.post 和 sendMessage 两类发送消息的方法有什么区别?

post 一类的方法发送的是Runnable对象,但是最后还是会被封装成Message对象,将Runnable对象赋值给Message对象中的callBack字段,然后交由sendMessageAtTime()方法发送出去.
在处理消息时,会在dispatchMessage()方法里首先被handleCallback(msg)方法执行,实际上就是执行Message对象里面的Runnable对象的run方法

sendMessage 一类方法发送的直接是Message对象,处理消息时,在dispatchMessage里优先级会低于handleCallback(msg)方法,是通过自己重写的handleMessage(msg)方法执行

3.为什么要通过Message.obtain()方法获取Message对象?

obtain方法可以从全局消息池中得到一个空的Message对象,这样可以有效节省系统资源.同时,通过各种obtain重载方法还可以得到一些message的拷贝,或对Message对象进行一些初始化.

4.Handler实现发送延迟消息的原理是什么?

我们常用postDelayed()与sendMessageDelayed()来发送延迟消息,其实最终都是将延迟时间转为确定时间,然后通过 sendMessageAtTime() -> enqueueMessage() -> queue.enqueueMessage() 这一系列方法将消息插入到MessageQueue中.所以并不是先延迟在发送消息,而是直接发送消息,在借助MessageQueue的设计来实现消息的延迟处理
消息延迟处理的原理设计 MessageQueue的两个静态方法 MessageQueue.next() 和 MessageQueue.enqueueMessage().通过Native方法阻塞线程一定时间,等到消息的执行时间到后再取出消息执行.

5.同步屏障SyncBarrier是什么?有什么作用?

在一般情况下,同步和异步消息处理起来并没有什么不同.只有在设置了同步屏障后再会有差异.同步屏障从代码层面上看是一个Message对象,但是其target属性为null,用以区分普通消息.在MessageQueue.next() 中如果当前消息是提个同步屏障,则跳过后面的所有同步消息,找到第一个异步消息来处理.但是开发者调用不了.在ViewRootImpl的UI测绘流程有体现

6.IdleHandler是什么?有什么作用?

当消息队列没有消息时调用或者如果队列中仍有待处理的消息,但都未到执行时间,也会调用此方法,用以监听主线程空闲状态.

7.为什么非静态类的Handler导致内存泄漏?如何解决?

首先,非静态的内部类,匿名内部类,局部内部类都会隐式的持有其外部类的引用.也就是说在Activity中创建的Handler会因此持有Activity的引用
当我们在主线程使用Handler的时候,Handler会默认绑定这个线程的Looper对象,并关联其MessageQueue,Handler发出的所有消息都会加入到这个MessageQueue中.Looper对象的生命周期贯穿整个主线程的生命周期,所以当Looper对象中的MessageQueue里还有未处理完的Message时,因为每个Message都持有Handler的引用,所以Handler无法被回收,自然其持有引用的外部类Activity也无法回收,造成泄漏
使用静态内部类 + 弱引用的方式

8.如何在子线程中弹出toast?

调用Looper.prepara以及Looper.loop(),但是切记线程任务执行完,需要手动调用Looper.quitSafely(),否则线程不会结束.

9.一个线程如何保证只有1个Looper

主要在于Looper类prepare方法,该方法的作用就是把looper对象存到ThreadLocal中
1.首先利用ThreadLocalLooper对象存到Thread对象的map
2.然后这个prepare方法里面会做判断,判断ThradLocal是否已经存有
Looper对象,如果有则直接报错Only one Looper may be created per thread

你可能感兴趣的:(高空利剑-Handler通信源码分析-Handler面试之问)