浅析Android消息机制之Handler

456 阅读12分钟

一、概述

在Android应用程序开发中,我们常常通过一个子线程来完成某些操作,然后把获取的数据通过Handler发送到主线程,通知主线程做相出应的操作。对于这种情况,Android提供了一套异步消息处理机制Handler。

1.1、主要成员

使用Handler方式进行异步消息处理主要由Message,Handler,MessageQueue,Looper四部分组成,怎么去理解这“四大金刚”呢?

那么接下来,我们简单剖析下Handler中的几个主要成员。

二、Looper

2.1、什么是Looper

Looper是Android为线程间异步消息通信提供的一种机制,利用Looper机制可以方便我们实现多线程编程时线程间的相互沟通。Looper通常运行在一个消息的循环队列当中,线程在默认的情况下,不会给我们提供一个消息循环去管理消息队列的。如果想管理消息队列,需要在线程当中调用Looper.prepare()方法使消息循环初始化,并且调用Looper.loop()使消息循环一直处于运行状态,直到停止循环。所以Looper主要就是完成MessageQueue与Handler进行交互的功能。

2.2、窥探Lopper

首先你觉得Android应用程序入口是什么呢?你肯定会说是应用的Application中的onCreate函数。其实读了Android源码才发现application并不是安卓程序的入口,而这个入口就是ActivityThread类,ActivityThread类是Android APP进程的初始类,它的main函数是这个APP进程的入口。ActivityThread类源码点这里

  • ActivityThread类关于程序入口main函数部分源码:
public static void main(String[] args) {
       //省略...

       //创建Looper和MessageQueue对象,用于处理主线程的消息
       Looper.prepareMainLooper();

       //创建ActivityThread对象,并绑定到AMS(ActivityManagerService)
       ActivityThread thread = new ActivityThread();

       //建立Binder通道 (创建新线程)
       thread.attach(false);

       Looper.loop(); //消息循环运行
       throw new RuntimeException("Main thread loop unexpectedly exited");
   }

  • 简单分析:
  1. 应用程序开始运行开始调用了Looper.prepareMainLooper(),后创建ActivityThread,然后调用Looper.loop()开启循环。
  2. 我们在Activity用Handler handler = new Handler()来创建Handler就默认绑定到主线程了,是因为上面的代码为我们做了绑定主线程的Looper的事情.
  3. 主线程中的Looper是不能在程序中调用退出的,如果调用的话,就会抛出异常,throw new RuntimeException("Main thread loop unexpectedly exited")。

2.2.1、Looper.prepare()创建

    //将当前线程初始化为应用程序的主循环程序,prepare(false)函数参数表示为不可退出。
    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中的。ThreadLocal是一个线程内部的数据存储类,每个线程都有自己独立访问的变量副本。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    public static void prepare() {
        prepare(true);
    }

  private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
	//创建Lopper对象
        sThreadLocal.set(new Looper(quitAllowed));
    }

   private Looper(boolean quitAllowed) {
	//创建MessageQueue消息栈,并且获取和绑定当前线程
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

  • 通过阅读创建Looper源码可以明白:
  1. Android应用程序初始化创建了一个默认的Looper,创建默认Looper时,设置参数的为false,即消息队列是不能够退出的,如上述ActivityThread源码Looper.prepareMainLooper()。
  2. 一个线程只能创建一个Looper,每次调用prepare()都在ThreadLocal寻找当前线程的Lopper实例,不存在则创建,否则会抛出RuntimeException。
  3. Looper对象保存在ThreadLocal中的,ThreadLocal是一个线程内部的数据存储类。
  4. Looper创建,即创建了一个内部消息队列MessageQueue,且保存了此MessageQueue的引用,并绑定了当前线程。此MessageQueue属于Looper所在的线程。

2.2.2、 Looper.loop()循环

在ActivityThread类中,我们通过源码看到Looper在调用prepareMainLooper()之后,又调用了loop()函数。我们通过这个疑问来看下SDK源码:

public static void loop() {
    ......
    for (;;) {
        //不断取出下一条消息,mgs为null即消息队列退出,若没有消息且没有退出 消息队列一直阻塞的
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ......
        try {
            //分派消息,通过Handler处理
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
        ......
        msg.recycleUnchecked();
    }
}

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

我们看到在Looper.loop()函数中使用了一个for(;;)死循环,即通过MessageQueue.next()从消息队列中取出Message。next()不断取出下一条消息,mgs为null即消息队列退出,循环停止。若没有消息且没有退出,消息队列一直阻塞的。 当next()返回一条消息,Looper调用msg.target.dispatchMessage(msg)进行分派处理。这里的msg.target就是一个Handler,即调用了Handler的dispatchMessage()方法进行数据分发操作,在dispatchMessage最终调用了handleMessage(msg)方法。这样我们就可以正常处理发送到主线程的消息了。

2.3、Lopper小结

  1. Looper是循环器(理解为一个传送带,不停轮询),为一个线程运行消息循环,不断检查消息队列中是否有新的消息,并且一个线程只有一个Lopper。
  2. Looper.prepare()为当前线程创建一个looper,把创建的Lopper实例存放在ThreadLocal中,并在其内部维护一个MessageQueue。
  3. Looper.loop()即开始轮询获取MessageQueue中的Message,获取后通过dispatchMessage分发。
  4. Looper.quit() Looper.quitSafely()-退出Looper,自主创建的Looper建议在不使用的时候退出

三、Handler

3.1、什么是Handler

Handler主要包含消息的发送和接收处理(类似快递员,负责收发快递[数据])。

3.2、创建一个Handler

//在UI线程里面,我们可以最简单直接new一个Handler实例
Handler handler = new Handler();

//然后我们点进源码查看构造函数
 public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
	//获取当前线程仅存的Looper对象
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
	//返回Looper对象的消息队列
        mQueue = mLooper.mQueue;
	//消息回调
        mCallback = callback;
        mAsynchronous = async;
    }
  • 通过阅读创建Handler源码可以明白:
  1. Handler初始化都需要4个参数Looper、Callback(消息回调)、async(是否异步)、MessageQueue(消息队列);
  2. 如果当前线程没有Looper实例,Handler将无法初始化实例。

3.3、通过Handler发送消息

Handler.sendxxx()相关API 截屏20210309 下午5.06.29.png

//hand.sendMessage
public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
//...此处省略其他发送方法

//沿着源码往下看,发现所有发送消息的函数最后都调用了MessageQueue中的一个函数:
 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

举个栗子看下如何在子线程中发送Message:

图片

此时Handler发送Message,并且携带了what、obj、arg1、arg2等数据,调用enqueueMessage函数,把Message打包入MessageQueue队列中。此时通过Looper运输数据到了UI线程中,Hanler开始获取数据并做出对应操作。

3.4、Handler计划任务

Handler.postxxx()相关API: 截屏2021-03-09 下午5.38.32

post(Runnable);//提交计划任务马上执行

postAtTime(Runnable, long);//提交计划任务在未来的时间点执行

postDelayed(Runnable, long);//提交计划任务延时Nms执行

接下来我们看看sdk是怎么实现的:

//hand.post
 public final boolean post(@NonNull Runnable r) {
	//sendMessageDelayed()怎么和Handler.postxxx()调用同样的方式?
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
//...此处省略其他计划任务方法

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

发现Handler.postxx()把Runnable作为一个参数赋值给了Message.callback,返回Message对象,接着和Handler.sendxxx()一样的调用enqueueMessage()把Message依次放到消息队列中。

举个栗子:

截屏20210310 上午11.02.35.png

3.5、Handler机制扩展

查阅资料发现,Android为了更方便使用Handler消息机制,有几种基于Handler的扩展方式:runOnUiThread(Runnable)、view.post(Runnable)

图片.png

看下他们的实现方式:

    //runOnUiThread(Runnable)
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

    //view.post(Runnable)
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        getRunQueue().post(action);
        return true;
    }

至此,Handler不是负责收发消息吗?别急,我们继续往下:

3.6、通过Handler接收消息

前面我们说到Looper的时候,在Looper中loop()函数,有一个for循环,循环体中有这么一行代码:msg.target.dispatchMessage(msg); 这才是Handler接收数据回调的重点(五星),下面代码就是loop中调用的函数体:

    /** 子类必须重写该方法进行接收数据
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

    public void dispatchMessage(Message msg) {
 
    // 1. 若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息
    // 则执行handleCallback(msg),即回调Runnable对象里复写的run()
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

private static void handleCallback(Message message) {
        //  Message对象的callback属性 = 传入的Runnable对象
        // 即回调Runnable对象里复写的run()
        message.callback.run();
    }

代码中告诉我们接收数据时,子类必须重写handleMessage来回调数据给UI线程操作: 图片.png

3.7、Handler小结

  1. Handler的创建一定要基于Looper,没有Looper则无法创建Handler;
  2. Handler通过post/send系列函数来打包Message数据到MessageQueue,仅是将消息加入消息队列中;
  3. Looper循环通过queue.next()获取到一条消息,再通过Handler的dispatchMessage()分发处理。

四、MessageQueue

4.1、何为MessageQueue

MessageQueue简单来说就是一个消息队列。主要负责携带Handler发送过来的信息入队列(enqueueMessage)和按顺序取出要执行的Message,。消息队列是单链表实现的。

4.2、创建一个MessageQueue

//调用Looper.looper()时就默认创建
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

 //用于标示消息队列是否可以被关闭,主线程的消息队列不可关闭(分析见上面[2.2、窥探Lopper])
 private final boolean mQuitAllowed;

  MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

4.3、MessageQueue之next()

以下源码注释参考:Handler用法及解析

//不停提取下一条message
Message next() {
    final long ptr = mPtr;
    //判断是否退出消息循环
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; 
    //代表下一个消息到来前,还需要等待的时长
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //native层阻塞cpu。如果被阻塞,唤醒事件队列
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();        
            Message prevMsg = null;
            Message msg = mMessages;
 
            //如果当前消息是异步消息,都将赋值给prevMsg,过滤掉,直到取到了非异步消息
            if (msg != null && msg.target == null) {
              do {
                    prevMsg = msg;
                    msg = msg.next;
              } while (msg != null && !msg.isAsynchronous()); 
            }
            //获取到了非异步消息
            if (msg != null) {
                //任务执行时间大于现在的时间
                if (now < msg.when) {
                //设置下一次轮询的超时时长
        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    mBlocked = false;//指定为非阻塞任务
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    //设置消息的使用状态,即flags |= FLAG_IN_USE
                    msg.markInUse();
                    return msg;   //成功地获取MessageQueue中的下一条即将要执行的消息
                }
            } else {
                //表示消息队列中无消息,会一直等待下去
                nextPollTimeoutMillis = -1;
            }
            ......
            //IdleHandler为发现线程何时阻塞的回调接口
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; //去除handler引用
            boolean keep = false;
 
        //queueIdle返回true会被空闲的处理器处理,false就会被移走
            try {
                keep = idler.queueIdle();  //idle时执行的方法
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);  //被移走
                }
            }
        }
        //重置idle handler个数为0,保证不会再次重复运行
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

next()方法中,做了异步Message消息的判断,特殊的是这个Message没有设置target,即msg.target为null。

4.4、MessageQueue之enqueueMessage()

以下源码注释参考:Handler用法及解析

boolean enqueueMessage(Message msg, long when) {
    // 每一个普通Message必须有一个target-handler
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    //已在使用状态
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }
    synchronized (this) {
        //消息在退出状态->被回收到消息池
        if (mQuitting) {  
            msg.recycle();
            return false;
        }
        //标记使用状态,记录执行时间
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        //p为null代表MessageQueue没有消息或者msg的触发时间是队列中最早的
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; //当阻塞时需要唤醒
        } else {
            //将消息按时间顺序插入到MessageQueue。
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p;
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

队列中的Message触发时间是有先后顺序的。当消息加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序(内部遍历队列中Message,找到when比当前Message的when大的Message,将Message插入到该Message之前,如果没找到则将Message插入到队列最后)。一般是当前队列为空的情况下,next那边会进入睡眠,需要的时候MessageQueue这边会唤醒next方法。

4.5、MessageQueue之其他

MessageQueue除了让队列中存放/取出数据,还有移除消息数据removeMessages(),removeCallbacksAndMessages()以及退出消息队列quit()等...

五、Message消息

我们通过Handler发送数据的时候,我们把数据包装成一个个Message,那么这个Message有什么特点呢?

5.1、Message简单属性

//消息类别,相当于一个标记码
public int what;

//参数1
public int arg1;

//参数2
public int arg2;

//消息内容
public Object obj;

//消息触发时间
public long when;

在操作Message的时候,我们一般是给Message上述对象赋值我们对应数据。但是在实际开发中,我们不可能频繁去创建我们的Message对象,我们看源码得知,Message提供了一个消息池:

   //从全局池返回一个新的Message实例。 在许多情况下,我们可以避免分配新对象。 
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
		//消息池中如有,则取出一个Message 
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
	// 当消息池为空时,直接创建Message对象
        return new Message();
    }

六、Handler系列之总结

6.1、Handler运行图示意

QQ图片20210308231328

如上述运行流程图:要使用Handler实现异步消息处理,首先我们需要在主线程中创建Handler对象并重写handleMessage()方法,然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handlerr将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后通过handler.dispatchMessage(msg)分发回Handler的handleMessage()方法中。由于Halldler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,从而实现子线程通过Handler机制实现UI线程操作的目的。

6.2、Handler系列总结

  • Message,线程之间传递的消息,用于不同线程之间的数据交互。Message中的what字段用来标记区分多个消息,arg1、arg2 字段用来传递int类型的数据,obj可以传递任意类型的字段。
  • MessageQueue,消息队列(先进先出),用于存放Handler发送的消息,一个线程只有一个消息队列。
  • Handler,用于发送和处理消息。其中的sendMessage()用来发送消息,handleMessage()用于消息处理,进行相应的UI操作。
  • Looper,作为消息队列的管理者,当发现MessageQueue中存在消息,Looper就会将消息传递到handleMessage()方法中,同样,一个线程只有一个Looper。

届时,已经浅析完了Handler相关系列“四大金刚”之Message,Handler,MessageQueue,Looper。鄙人水平有限,有什么不对的地方欢迎大佬们批评指正!