Android Handler异步消息

2023年 10月 14日 84.1k 0

前言

在Android中,经常会遇到线程间通信的场景,下面就说说Android中最重要的异步消息机制Handler

异步消息机制Handler

Handler是Android中最重要的异步消息机制,总共由四部分组成:Handler,Message,MessageQueue,Looper

1、主线程创建 Handler 对象(如果在子线程创建,必须保证调用了Looper.prepare()),并重写 handleMessage() 方法。

2、子线程创建 Message 对象,通过第一步创建的Handler 发送消息,handler.sendMessage(message),handler将消息发送到MessageQueue中。

3、Looper 通过loop()循环从 MessageQueue 中取出待处理消息。

4、looper将取出的message分发回 Handler 的 handleMessage() 方法中处理。

Looper

创建 Looper 的方法是调用 Looper.prepare() 方法。注意:在 ActivtyThread 中的 main 方法为我们 prepare 了

public static void main(String[] args) {
  Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,     "ActivityThreadMain");
  //...
  Looper.prepareMainLooper(); //初始化Looper以及MessageQueue
  ActivityThread thread = new ActivityThread();
  thread.attach(false);
  if (sMainThreadHandler == null) {
      sMainThreadHandler = thread.getHandler();
  }
  if (false) {
      Looper.myLooper().setMessageLogging(new
      LogPrinter(Log.DEBUG, "ActivityThread"));
  }
  // End of event ActivityThreadMain.
  Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  Looper.loop(); //开始轮循操作
  throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.prepareMainLopper()

public static void prepareMainLooper() {
prepare(false);//调用prepare(), 消息队列不可以quit
synchronized (Looper.class) {
    if (sMainLooper != null) {
        throw new IllegalStateException("The main Looper has already been
        prepared.");
    }
    sMainLooper = myLooper();
  }
}

Looper.prepare(boolean quitAllowed)

public static void prepare() { 
    prepare(true);//消息队列可以quit
}
// 私有的构造函数
  private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {//不为空表示当前线程已经创建了Looper
            throw new RuntimeException("Only one Looper may be created per thread");
            //每个线程只能创建一个Looper
        }
        // ThreadLocal 多线程中重要的知识点,线程上下文的存储变量
        sThreadLocal.set(new Looper(quitAllowed));//创建Looper并设置给sThreadLocal,这样get的时候就不会为null了
}

**注意:**一个线程只要一个Looper。如何保证只有一个?
这里用到了ThreadLocal。一句话理解ThreadLocal,threadlocl是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadlocl,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。所以利用ThreadLocal可以保证一个线程只有一个Looper。
为了保证 ThreadLocalMap.set(value) 时,value 不会被覆盖(即Looper不会改变),会先进行上面代码的 if 操作,if (sThreadLocal.get() != null) , 不为空表示当前线程已经创建了Looper,然后直接抛异常结束prepare。如果 if 不成立,则 new Looper()。

MessageQueue

MessageQueue的创建是在Looper构造函数中

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);//创建了MessageQueue
    mThread = Thread.currentThread(); //当前线程的绑定
}

MessageQueue(boolean quitAllowed) {
    //mQuitAllowed决定队列是否可以销毁 主线程的队列不可以被销毁需要传入false, 在MessageQueue的 quit()方法
    //省略...
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

Looper.loop()

在 ActivtyThread的main 方法中 Looper.prepareMainLooper() 后 Looper.loop() 开始轮询

public static void loop() {
    final Looper me = myLooper();//里面调用了sThreadLocal.get()获得刚才创建的Looper对象
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }//如果Looper为空则会抛出异常
    final MessageQueue queue = me.mQueue;
    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    for (;;) {
        //这是一个死循环,从消息队列不断的取消息
        Message msg = queue.next(); // might block
        if (msg == null) {
            //由于刚创建MessageQueue就开始轮询,队列里是没有消息的,等到Handler sendMessage enqueueMessage后
            //队列里才有消息
            // No message indicates that the message queue is quitting.
            return;
        }
    // This must be in a local variable, in case a UI event sets the logger
    Printer logging = me.mLogging;
    if (logging != null) {
        logging.println(">>>>> Dispatching to " + msg.target + " " +
        msg.callback + ": " + msg.what);
    }
    msg.target.dispatchMessage(msg);//msg.target就是绑定的Handler,详见后面Message的部分,Handler开始
    //后面代码省略.....
    msg.recycleUnchecked();
    }
}

loop方法开启后,不断地从MessageQueue中获取Message,对 Message 进行 Delivery 和 Dispatch,最终发给对应的 Handler 去处理(通过msg.target找到对应的handler)。
**说明:**MessageQueue队列中是 Message,在没有 Message 的时候,MessageQueue借助Linux的ePoll机制,阻塞休眠等待,直到有Message进入队列将其唤醒。

Handler

handler通过发送和处理Message和Runnable对象来关联相对应线程的MessageQueue。

最常见的创建 handler 的方式:

//第一种方式
Handler handler=new Handler(){
    @Override
    public void handleMessage(Message msg) {
    super.handleMessage(msg);
    }
};
//第二种方式(looper作为参数传入)
 Handler handler=new Handler(Looper looper)

第一种方式在内部调用 this(null, false);

public Handler(Callback callback, boolean async) {
    //前面省略
    mLooper = Looper.myLooper();//获取Looper,**注意不是创建Looper**!
    if (mLooper == null) {
        throw new RuntimeException(
       "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;//创建消息队列MessageQueue
    mCallback = callback; //初始化了回调接口
    mAsynchronous = async;
}

Looper.myLooper() ;

//这是Handler中定义的ThreadLocal ThreadLocal主要解多线程并发的问题
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal sThreadLocal = new ThreadLocal();
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

sThreadLocal.get() will return null unless you’ve called prepare(). 这句话告诉我们 get 可能返回 null 除非先调用 prepare()方法创建 Looper 。在前面已经介绍了

Message

message的创建可以直接 new Message() ,但是有更好的方式 Message.obtain()。因为可以检测是否有可以复用的 Message,复用避免过多的创建、销毁 Message 对象,达到优化内存和性能的目的。

public static Message obtain(Handler h) {
    Message m = obtain();//调用重载的obtain方法
    m.target = h;//并绑定的创建Message对象的handler
    return m;
}
public static Message obtain() {
    synchronized (sPoolSync) {//sPoolSync是一个Object对象,用来同步保证线程安全
    if (sPool != null) {
        //sPool是就是handler dispatchMessage 后 通过recycleUnchecked回收用以复用的Message
        Message m = sPool;
        sPool = m.next;
        m.next = null;
        m.flags = 0; // clear in-use flag
        sPoolSize--;
        return m;
        }
    }
    return new Message();
}

创建 Message 的时候通过 Message.obtain(Handler h) 这个构造方法将message和handler绑定。当然也可以在 Handler 中的 enqueueMessage() 绑定。

Handler 发送消息(消息入队)

Handler 发送消息的重载方法很多,我们用得比较多的方法就是post、sendMessage、sendMessageDelay方法,先挨个看看

post方法

public final boolean post(@NonNull Runnable r) {
    return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
     Message m = Message.obtain();
     m.callback = r;
     return m;
}

post接受一个Runnable类型的参数,并将其封装为一个Message对象,并且将Runnable参数赋值给msg的callback字段,这里要记住,后面有用——Runnable什么时候执行的呢?
最后调用的就是sendMessageDelayed

sendMessage方法

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

最后调用的也是sendMessageDelay,第二个参数是0。

sendMessageDelay方法
可见无论是post还是sendMessage方法,最后都走到了sendMessageDelayed

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
     if (delayMillis < 0) {
         delayMillis = 0;
     }
     return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 这里注意第二个参数 @param updateMillis 是一个具体的时间点。
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
     MessageQueue queue = mQueue;
     //……
     return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
       // 【关键点6】这里要注意target指向了当前Handler
         msg.target = this;
     if (mAsynchronous) {
         msg.setAsynchronous(true);
     }
     // 【关键点7】调用到了queue#enqueueMessage方法
     return queue.enqueueMessage(msg, uptimeMillis);
}

最终会走到handler中的enqueueMessage方法,然后走到quene.enqueneMessage(msg, uptimeMillis),将message放入了消息队列,那么我们来看看,是如何放入队列的吧。

boolean enqueueMessage(Message msg, long when) {
     //……
     // 【关键点8】对queue对象上锁
     synchronized (this) {
         //……
         msg.markInUse();
         msg.when = when; // msg的when时刻赋值
         Message p = mMessages;
         boolean needWake;
         if (p == null || when == 0 || when < p.when) {
             // New head, wake up the event queue if blocked.
             // 翻译:新的头结点,如果queue阻塞,则wakeup唤醒
             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.
            // 翻译:将消息插入到消息队列中,通常我不需要进行唤醒操作,除非........
             needWake = mBlocked && p.target == null && msg.isAsynchronous();
             Message prev;
             for (;;) { // for循环,break结束时when < p.when,说明按照when进行排序插入,或者尾节点
                 prev = p;
                 p = p.next;
                 if (p == null || when < p.when) {
                     break;   // 【关键点9】 找到插入位置,条件尾部或者when从小到大的位置
                 }
                 if (needWake && p.isAsynchronous()) {
                     needWake = false;
                 }
             }
           // 执行插入
             msg.next = p; // invariant: p == prev.next
             prev.next = msg;
         }

         // We can assume mPtr != 0 because mQuitting is false.
         if (needWake) {
             nativeWake(mPtr); // native方法,唤醒
         }
     }
     return true;
}

这里面将msg放到消息队列中,可以看到这个队列是一个简单的单链表结构,按照msg的when进行的排序,并且进行了synchronized加锁,确保添加数据的线程安全。之所以采用链表的数据结构,原因是链表方便插入。
初看源码的时候,应该忽略掉wakeup这些处理,关注msg是如何加入队列即可。
到这里,我们了解了message是如何加入消息队列MesssageQueue。但是消息什么时候执行,以及post(Runnable)中的Runnable什么时候才执行。

消息出队执行

前面提到的Looper.loop()中,主要调用me.mQueue.next()获取一个消息msg,注意这里可能阻塞。这里调用了msg.target.dispatchMessage(msg),这里msg.target可以回头看看前面Message创建赋值的地方。所以这里就将消息分发给了对应的Handler去处理了。待会儿再看Handler.dispatchMessage,我们接着看next方法是怎么取消息的。

 Message next() {
     // Return here if the message loop has already quit and been disposed.
     // This can happen if the application tries to restart a looper after quit
     // which is not supported.
     final long ptr = mPtr;
     if (ptr == 0) {
         return null;
     }

     //……
     int nextPollTimeoutMillis = 0;
     // 【关键点12】继续死循环
     for (;;) { 
         //……
         nativePollOnce(ptr, nextPollTimeoutMillis);

       // 加锁
         synchronized (this) {
             // Try to retrieve the next message.  Return if found.
             final long now = SystemClock.uptimeMillis(); // 当前时间
             Message prevMsg = null;
             Message msg = mMessages; // msg 指向链表头结点
             if (msg != null && msg.target == null) { // 这个if可以先忽略
                 // Stalled by a barrier.  Find the next asynchronous message in the queue.
                 do {
                     prevMsg = msg;
                     msg = msg.next;
                 } while (msg != null && !msg.isAsynchronous());
             }
             if (msg != null) {
               // 【关键点13】如果当前时间小于头结点的when,更新nextPollTimeoutMillis,并在对应时间就绪后poll通知
                 if (now < msg.when) { 
                     // Next message is not ready.  Set a timeout to wake up when it is ready.
                     nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                 } else {
                     // Got a message. 开始获取消息
                     mBlocked = false;
                     if (prevMsg != null) {
                         prevMsg.next = msg.next;
                     } else {
                         mMessages = msg.next; // 更新头结点 
                     }
                     msg.next = null; // 断链处理,等待返回
                     //……
                     return msg;
                 }
             } else {
                 // No more messages.
                 nextPollTimeoutMillis = -1;
             }

             // Process the quit message now that all pending messages have been handled.
             if (mQuitting) {
                 dispose();
                 return null; 
             }
             //……
         }
         //……
         // 下面是IdleHandler的处理,还不是很了解
     }
 }

next方法中也是一个死循环,不断的尝试获取当前消息队列已经到时间的消息,如果没有满足的消息,就会一直循环,这就是为什么会next会阻塞的原因。

看完了next方法,获取到了msg,回到刚才的msg.target.dispatchMessage(msg),接着看Handler是如何处理消息的。

 public void dispatchMessage(@NonNull Message msg) {
     if (msg.callback != null) {
       // 如果消息有CallBack则直接,优先调用callback
         handleCallback(msg);
     } else {
         // 如果Handler存在mCallback,优先处理Handler的Callback
         if (mCallback != null) {
             if (mCallback.handleMessage(msg)) {
                 return;
             }
         }
         // 此方法会被重写,用户用于处理具体的消息
         handleMessage(msg);
     }
 }

 private static void handleCallback(Message message) {
     message.callback.run();
 }

dispatchMessage方法中优先处理Message的callback,再回头看看Handler.post方法应该知道callback是个啥了吧。

如果Handler设置了mCallback, 则优先判断mCallback.handleMessage的返回值,这个机制可以让我们做一些勾子,监听Handler上的一些消息。

handleMessage(msg)这个方法一般在创建Handler时被重写,用于接收消息。

总结

1、一个线程只能有一个Looper,如何确保?
答案:ThreadLocal

2、Handler.post(Runnable)会将Runnable封装到一个Message中。

3、MessageQueue采用单链表的实现方式,并且在存取消息时都会进行加锁。

4、Looper.loop采用死循环的方式,会阻塞线程。那么为什么主线程不会被阻塞?
因为Android是事件驱动的,很多的系统事件(点击事件、屏幕刷新等)都是通过Handler处理的,因此主线程的消息队列,会一直有消息的。

5、Handler是如何实现线程切换的?
Looper和MessageQueue和线程绑定的,也就是说这个消息队列中的所有消息,最后分给对应的Handler都是在创建Looper的线程。所以无论Handler在什么线程发送消息,最后都回到创建Looper的线程中执行。

6、Thread和Looper、MessageQueue是一对一的关系,Looper、MessageQueue对于Handler是一对多的关系。这里要注意,一个具体的Handler实例,肯定只关联一个Looper和queue的哟。

👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀

相关文章

服务器端口转发,带你了解服务器端口转发
服务器开放端口,服务器开放端口的步骤
产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
如何使用 WinGet 下载 Microsoft Store 应用
百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

发布评论