Handler源码解析(1)

前言

本文是自己学习Handler时的一些感受和心得,供本人复习使用,因本人技术实力有限,会出现知识错误或缺漏的问题,请大家多多谅解。本文仅涉及线程间的通信,并不讲解进程间Handler机制的作用。

什么是Handler

Handler是处理Android(进程中)线程间通信的通信机制,通过Handler我们可以实现子线程和主线程之间的通信。那么Handler到底是怎么工作的呢。在此之前,那我们先明确几个Handler的相关类
Handler:实现消息发送和接受消息
Loop:消息轮询器
Message:消息实体
MessageQueue:用于储存和管理消息的消息队列
下面我们一一进行讲解

Loop的相关说明

我们都知道mian方法是整个Java程序的入口,那么Android程序的main方法在哪呢,答案是ActivityThread的main方法
ActivityThread.java

 public static void main(String[] args) {
    ...
       //创建Loop
        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        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);
       //循环loop
        Looper.loop();

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

在main方法中我们看到了其中调用了Looper.prepareMainLooper()和Loop.loop()方法,先看Looper.prepareMainLooper()方法。

public static void prepareMainLooper() {
        //创建Loop
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //获取创建的Loop对象
            sMainLooper = myLooper();
        }
    }

首先调用了prepare方法,prepare主要是创建Loop对象,并由ThreadLoacal保证线程内仅有一份Looper实例,如果当前线程中已经持有了Looper的对象则会抛出异常,myLooper()则返回当前进程中持有的Looper对象。prepare和myLooper的代码如下:

//quitAllowed表示是否允许退出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();
    }

需要说明的是prepare的quitAllowed参数表示是在没有消息时是否可以退出Looper循环,主线程默认不允许退出。现在再来看一下Looper的构造方法

  private Looper(boolean quitAllowed) {
        //创建消息队列
        mQueue = new MessageQueue(quitAllowed);
        //保存当前的线程
        mThread = Thread.currentThread();
    }

Looper的构造方法创建了用于保存和管理消息的队列mQueue和持有了当前线程的引用。需要注意的是Looper的构造方法是私有的,只能通过调用prepareMainLooper(主线程自己调用)或者Looper.prepare(子线程调用)进行Looper的创建。每个线程只能持有一个Looper对象(由ThreadLocal保证)。至此Looper的prepareMainLooper方法调用说明结束。现在看一下Looper.loop()方法

  public static void loop() {
       //获取Looper对象
        final Looper me = myLooper();
       //获取Looper失败,抛出异常
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
      

        me.mInLoop = true;
       //获取Looper中的消息列表
        final MessageQueue queue = me.mQueue;

     ....
        //请注意这是一个死循环
        for (;;) {
            //获取消息队列中的消息
            Message msg = queue.next(); // might block
           //消息为null时退出循环
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

           ....
            try {
              //分发消息,会调用handler的dispatchMessage,该方法最终会调用到Handler的handleMesaage方法
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
          ....
        }
    }

可以看到Looper的loop中会调用一个死循环,在循环体内部会不断获取消息队列mQueue中的消息,并对消息进行分发。如果从消息队列中获取不到消息,则退出循环
这也就是说在ActivityThread的main方法中会出现死循环,其实这也正是App主线程为什么不会结束的原因,是因为其内部存在死循环。为什么主线程内部会有一个死循环呢?:对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。但我们从Looper.loop的方法中看到,当消息队列中没有消息时,会退出这个死循环,那么为什么我们从来没有看到过主线程的退出呢,这是因为主线程在消息列表中没有消息时会进行阻塞,等到消息到来时进行唤醒取出消息。根本不会返回null,自然不会退出死循环了。关于消息的阻塞和唤醒会在消息队列中进行介绍。至此,Loop的说明完毕,现在看一下消息队列MessageQueue的相关源码。

MessageQueue的说明

MessageQueue是保存和管理消息的消息队列,其组织结构是具有优先级的单链表实现的队列。调用enqueueMessage进行消息的入队,调用next实现队列的出队。首先从enqueueMessage开始吧。

boolean enqueueMessage(Message msg, long when) {
        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.");
            }
           //当前Loop循环退出(调用Loop的quit方法),抛出异常
            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;
            //如果当前队列为空,或者当前消息延迟时间为0,或当前消息的延迟时间比对头的消息少
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
               //设置新的对头,如果当前队列处于阻塞状态还需要进行唤醒(mBlocked=true表明列表已经进行了阻塞)
                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 (;;) {
                    prev = p;
                    p = p.next;
                    //寻找消息的插入位置(判断依据为消息节点为空或者当前消息的延迟执行时间小于当前的消息节点)
                    if (p == null || when < p.when) {
                        break;
                    }
                   //设置不需要进行唤醒
                    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);
            }
        }
        return true;
    }

从这里可以看出enqueueMessage充当了一个生产者,当调用该方法时就像队列中添加一个消息,并通过延迟时间找到插入位置进行插入,插入的操作也是典型的单链表插入(只维护节点的后继关系),当前队列如果没有消息,且是阻塞状态的话,则唤醒队列

在Looper.loop()方法中我们看到调用了MessageQueue的next方法,那我们就从该方法入手吧。

  public static void loop() {
       //获取Looper对象
        final Looper me = myLooper();
       //获取Looper失败,抛出异常
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
      

        me.mInLoop = true;
       //获取Looper中的消息列表
        final MessageQueue queue = me.mQueue;

     ....
        //请注意这是一个死循环
        for (;;) {
            //获取消息队列中的消息
            Message msg = queue.next(); // might block
             .....
          ....
        }
    }

MessageQueue的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 pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //根据nextPollTimeoutMillis进行阻塞
           //-1一直阻塞直到enqueueMessage有新的消息到来进行唤醒
         // 0 不进行阻塞
         //大于0阻塞多少毫秒后唤醒
            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;
               //这一块暂不讲解()
                if (msg != null && msg.target == null) {
                    // 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) {
                     //当前时间小于消息执行时间
                    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 {
                            //获取message
                            mMessages = msg.next;
                        }
                       //message后继置为null
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        //标记为使用状态
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    //当前列表中没有数据需要进行阻塞
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                 //没有执行quit方法(主线程不能执行quit方法,否则会导致主线程退出)
                if (mQuitting) {
                    dispose();
                    return null;
                }
  // 空闲句柄仅在队列为空或将来要处理队列中的第一条消息(可能是屏障)时才运行。(暂不进行说明)
                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            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);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

如果当前队列里有消息,则计算队头消息的执行时间是否大于当前时间,如果大于则计算需要阻塞的时间(nextPollTimeoutMillis),对队列进行阻塞(这种阻塞会自动进行唤醒)特定的时间,然后唤醒继续执行。小于的话则执行从队列中出队,并返回该message。如果当前对列中没有消息则会将nextPollTimeoutMillis置为-1,mBlocked =true,继续进行循环,再次进入循环就会调用nativePollOnce进行阻塞(-1时的阻塞无法自动唤醒,必须等到enqueueMessage添加入消息才能再次唤醒)在这里不难发现enqueueMessage不断的往消息列表中发送数据,next不断从消息列表中取出数据,符合生产者消费者模式。有关消息队列的说明结束,现在我们看一下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 回 收用以复用的MessageMessage
 m = sPool;
 sPool = m.next; 
m.next = null;
 m.flags = 0; // clear in-use flag 
sPoolSize--;
 return m; 
}
 }
return new Message();
 }

那么Message是如何进入到消息队列中进行分发的呢的呢?当然是通过Handler进行的。

Handler

Hander的简单使用如下:
1.创建Handler
2.发送消息
3.接受消息

class MainActivity : AppCompatActivity() {
    //1.创建mandler并指定Loop和消息处理的回调函数
    private  val mHandler=Handler(Looper.getMainLooper()){
        //4处理消息
        when(it.what){
            110 -> {
                Log.e("handler", "${it.obj.toString()}", )
            }
        }
        true
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //2.创建消息
        val obtain = Message.obtain(mHandler)
        obtain.obj="啦啦啦德玛西亚"
        obtain.what=110
        //3.发送消息
        mHandler.sendMessage(obtain)
    }
}

结果打印如下:

2021-09-06 09:27:36.206 24557-24557/com.zxl.handlerdemo E/handler: 啦啦啦德玛西亚

首先看一Handler的sendMessage的方法源码

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

sendMessage方法调用了sendMessageDelayed方法

 public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

sendMessageDelayed方法调用了sendMessageAtTime方法。

 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);
    }

sendMessageAtTime会将消息添加到消息队列queue中,那么消息队列从哪里获取呢,其实是从我们创建Handler时传入的Loop中获取(Loop的prepare方法中创建的消息队列)。方法最后调用了enqueueMessage,并传入消息队列和消息

    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);
    }

在这个方法中将消息的target与handler进行绑定,消息的处理方法回调就需要调用handler.dispatchMessage进行调用。最后将消息加入到消息队列中。现在我们知道了Handler如何发送数据的再来看一下handler是如何接受消息的。

首先我们知道在Looper的loop方法中会将消息从队列中进行取出,并调用message.target.dispatchMessage()方法,而message中的target正是发送该消息的handler。现在我们看一下Handler的dispatchMessage做了什么。

 public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

如果msg自身存在回调方法则调用message的自身的回调方法,如果不存在的话如果Handler设置了消息回调的方法(创建Handler时传入的回调方法)则调用该方法,否则调用Handler默认的handleMessage方法,该方法是一个空方法实现。至此整个Handler的发送消息和消息处理的流程说明完毕。总结一下流程:

1.在主线程创建时会在Main方法中创建Looper(Loop.prepareMainLoop)并执行Loop.loop方法实现死循环,在该循环中会不断调用next方法从消息队列中取出消息并调用相关的handler的dispatchMessage方法进行消息处理(消息列表由Loop创建并由ThreadLocal保证线程内唯一)
2.调用Handler的sendMessage方法(或其他发送消息的方法),最终会调用到sendMessageAtTime方法,该方法会将需要发送的消息发送到消息队列中(消息队列从创建Handler的Loop提供)。

image.png

handler机制就是一个传送带的运转机制。
1)MessageQueue就像履带。
2)Thread就像背后的动力,就是我们通信都是基于线程而来的。
3)传送带的滚动需要一个开关给电机通电,那么就相当于我们的loop函数,而这个loop里面的for循环就会带着不断
的滚动,去轮询messageQueue
4)Message就是 我们的货物了。
以上就是Handler机制的基本内容,其他相关内容(内存共享,消息屏障,线程同步等)会在后面专门写一篇文章进行介绍。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容