Android Handler异步消息

前言

在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 了

java 复制代码
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()

java 复制代码
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)

java 复制代码
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构造函数中

java 复制代码
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() 开始轮询

java 复制代码
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 的方式:

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

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

java 复制代码
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() ;

java 复制代码
//这是Handler中定义的ThreadLocal ThreadLocal主要解多线程并发的问题
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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 对象,达到优化内存和性能的目的。

java 复制代码
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方法

java 复制代码
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方法

java 复制代码
public final boolean sendMessage(@NonNull Message msg) {
     return sendMessageDelayed(msg, 0);
}

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

sendMessageDelay方法

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

java 复制代码
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放入了消息队列,那么我们来看看,是如何放入队列的吧。

java 复制代码
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方法是怎么取消息的。

java 复制代码
 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是如何处理消息的。

java 复制代码
 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老皮!!!欢迎大家来找我探讨交流👀

相关推荐
无极程序员42 分钟前
PHP常量
android·ide·android studio
萌面小侠Plus2 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
慢慢成长的码农2 小时前
Android Profiler 内存分析
android
大风起兮云飞扬丶2 小时前
Android——多线程、线程通信、handler机制
android
L72562 小时前
Android的Handler
android
清风徐来辽2 小时前
Android HandlerThread 基础
android
HerayChen3 小时前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野3 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
hairenjing11233 小时前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
小黄人软件4 小时前
android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址
android·ide·android studio