美文网首页Android开发Android开发经验谈Android技术知识
Handler—Handler事件分发、Handler线程切换原

Handler—Handler事件分发、Handler线程切换原

作者: 程序员叁柒 | 来源:发表于2020-08-26 15:36 被阅读0次

    Handler,无论你菜鸟还是像我一样的老菜鸟,都无法避免这个亘古不灭的话题,只有了解学会Handler的原理,你才能变成一个出的厅堂下得厨房的优秀的老菜鸟。其实同类优秀的帖子已经很多了,但是为了强化记忆我的理解,还是想再次记一下,我心中的Handler。

    要介绍Handler,就必须要介绍与它息息相关甚至“同为一体”的其它几个类:Looper、MessageQueue、Message。所以本篇文章就分为三个部分:

    • Handler、Looper、MessageQueue、Message原理及关系。
    • Handler事件分发原理。
    • Handler线程切换原理。

    1. Handler、Looper、MessageQueue、Message原理及关系。

    1.1 Message

    Message是定义一个包含描述和可以发送到Handler的任意数据对象的消息。

    我们先看Message的几个关键变量:


    target,就是负责发送且分发它的Handler;
    callback,是一个Runnable回调;
    next,下一个消息池中的它之下的message。

    现在有3个问题:

    • 1.target从哪来来,作用是什么?
    • 2.callback又是什么及其作用;
    • 3.还有next是什么?

    我们先不着急给出答案,接着往下看。

    1.2 MessageQueue 消息队列

    它持有并维护了当前线程的Looper分发的消息列表。消息并不是直接而是通过Handler 关联的Looper入队列的。

    MessageQueue看上去是消息队列,然而它实际上只是消息队列的管理者。消息列表是它持有的Message mMessage,是由Message组成的单链表结构。
    我们看它管理消息的两个核心方法:

    • boolean enqueueMessage(Message msg, long when) ;
    • Message next()。

    1.2.1 boolean enqueueMessage(Message msg, long when) ,消息入队列;

    先上代码:

     boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
    
            synchronized (this) {
                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;
                if (p == null || when == 0 || when < p.when) {
                    // New head, wake up the event queue if blocked.
                    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;
        }
    
    

    可以看到,在第一行就校验了msg.target是否为null,由此可知,msg必须关联(持有)负责分发它的handler;

    最重要的是for循环里入队的这部分,通过逐个和消息列表里消息的发生时间对比,找到与当前入队的message的 -分发时间-之前它时间最接近的preMsg,将mesage设置到preMsg的next(此处也解答了问题3:message的next是什么),最终得到的是一个按时间先后顺序排列的链表;

    此外,入队操作还做了一件事情:当消息入队时候,如果队列为空的或需要立即分发当前消息分发时间-早于消息队列中最近一个需要分发的消息的时间的(而此时MessageQueue的next()方法的循环是处于阻塞状态的),就调用一个native方法nativeWake()唤醒取出消息的next()(next方法的循环会被nativePollOnce()native方法阻塞)循环。

    1.2.2 Message 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();
                }
                  //-1-直接休眠一定的时间;
                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;
                      //-2-找出下一个需要分发的消息。
                    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) {
                        //-3- 如果时机未到,即当前时间还不到消息的处理时间,就计算休眠时间,
                        //反之,返回这个待处理的消息。并将休眠状态mBlocked置为false
                        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;
                            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.
                    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.
                    //-4-只有当消息队列是空的或者现在还不到待处理的消息的时间的时候,idleHanler(空闲handler)才会被执行
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    //-5-如果没有空闲Handler待处理(消息队列也没有需要处理的message),那么,标记一下循环被阻塞了。
                    if (pendingIdleHandlerCount <= 0) {
                        // No idle handlers to run.  Loop and wait some more.
                        mBlocked = true;
                        continue;
                    }
                    //-5-将idleHandler列表转为数组保存
                    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.
    
                //-6-开始处理IdleHandler的事务。
                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);
                    }
    
                    //-7-如果IdleHandler的queueIdle返回false,就不需要保持,这执行完一次任务后直接移除,反之,
                    //后面每次空闲的时候都执行 
                    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;
            }
        }
    
    

    看到这段代码,愈发觉得谷歌工程是真的是厉害,不知道秃到什么程度才能写出这么精妙的代码,哈哈哈哈哈😃。由于这段代码我看着太喜欢了,就按照注释逐个解释:

    • 1-这段代码上来不管三七二十一就调了native的休眠方法——nativePoolOnce()(正好对应了消息入队的nativeWake()),如果没有消息处理的时候,next()方法就会阻塞在这里。当然休眠时间nextPollTimeoutMillis为0的时候眼睛一闭一睁也没有意义啊。

    此时,突然有一个问题——循环阻塞在这里了,主线程不就卡住了嘛?为什么没有ANR?

    具体原理,可以查看这篇帖子还有这篇帖子等等很多优质讲解。简而言之调用native的nativePoolOnce方法时候,调用了linux低层,使当前线程腾出CPU的使用权,处于休眠等待状态,直到休眠时间结束或消息入队被唤醒。

    • 2- 通过内循环从消息队列中寻找出下一个有效的message;
    • 3- 判断是否到了上一步得到的message的处理时间,如果到了时间,就返回这个message(返回给Looper的调用处,然后分发该事件);如果没有到,计算需要休眠的时间。
    • 后面几点其实不在我们讨论内容,可以直接跳过
    • 4- 在3处,没有返回message,就以为着:1.要么现在还没有到下一个消息分发的时间;2.要么当前消息队列是空的。此时开始处理空闲handler(IdleHanler)的事物。
    • 5- 这一步将IdleHandler列表转为数组存储。为什么要转数组,而不是直接处理列表?此处我需要引入一段代码:
    private IdleHandler[] mPendingIdleHandlers;
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    
        /**
         * Add a new {@link IdleHandler} to this message queue.  This may be
         * removed automatically for you by returning false from
         * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
         * invoked, or explicitly removing it with {@link #removeIdleHandler}.
         *
         * <p>This method is safe to call from any thread.
         *
         * @param handler The IdleHandler to be added.
         */
        public void addIdleHandler(@NonNull IdleHandler handler) {
            if (handler == null) {
                throw new NullPointerException("Can't add a null IdleHandler");
            }
            synchronized (this) {
                mIdleHandlers.add(handler);
            }
        }
    
        /**
         * Remove an {@link IdleHandler} from the queue that was previously added
         * with {@link #addIdleHandler}.  If the given object is not currently
         * in the idle list, nothing is done.
         *
         * <p>This method is safe to call from any thread.
         *
         * @param handler The IdleHandler to be removed.
         */
        public void removeIdleHandler(@NonNull IdleHandler handler) {
            synchronized (this) {
                mIdleHandlers.remove(handler);
            }
        }
    
    

    可以看到,这个列表拥有add和remove的方法,这两个方法会在外部随时被调用,也就意味着,列表是变化的,如果一直添加的话,就会不停的处理,当前循环就会在这里耗时,消息队列的消息就有可能不能被及时处理了。

    • 6- 正式开始迭代处理idleHandler
    • 7- 我们先引入IdleHandler代码:
        /**
         * Callback interface for discovering when a thread is going to block
         * waiting for more messages.
         * 当线程即将进入阻塞状态的时候需要处理的更多消息
         */
        public static interface IdleHandler {
            /**
             * Called when the message queue has run out of messages and will now
             * wait for more.  Return true to keep your idle handler active, false
             * to have it removed.  This may be called if there are still messages
             * pending in the queue, but they are all scheduled to be dispatched
             * after the current time.
             * 当MessageQueue的消息被执行完或下一个消息需要等待时间的时候会被调用。
             * 如果仅需要被调用一次,返回false,则被调用一次就会从列表中移除此项,如果需要每次空闲
             * 时都需要被调用就返回true,则不会被移除。
             */
            boolean queueIdle();
        }
    
    

    IdleHandle是当线程空闲(MessageQueue中暂时不处理消息时)时才需要处理的事物。使用方法使用场景我不再赘述。

    • 7-总之,如果IdleHandler的queueIdle()放回fasle,就在执行完之后移除掉此项,反之不会被移除,且下一次线程空闲的时候还会被调用。

    好了,next()方法分析完了,再次揪一揪自己浓密健壮的头发感叹——谷歌工程是真的厉害!

    1.3 Looper

    Looper短短不到500行的代码,并没有什么特殊之处,它用于为一个线程运行一个消息循环。新的线程默认是没有消息循环的,在线程中调用prepare可以产生一个消息循环,然后调用loop方法可以开始运行循环直到线程终止。
    Looper中有以下几个重要的方法:

    • ThreadLocal sThreadLocal ;
    • prepare();
    • loop();

    1.3.1 sThreadLocal

    sThreadLocal 存放了当前线程绑定的Looper,除非先调用了prepare(),否则调用ThreadLocal的get方法时会返回null;

    1.3.2 prepare()

    那么我们看看prepare()方法:

    
        /** Initialize the current thread as a looper.
          * This gives you a chance to create handlers that then reference
          * this looper, before actually starting the loop. Be sure to call
          * {@link #loop()} after calling this method, and end it by calling
          * {@link #quit()}.
          *初始化当前线程的Looper,这给我们一个机会去创建引用这个Looper       *的Handler,要真正开始循环,确保调用了loop方法。
          */
        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");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
        /**
        *Looper构造函数
        */
        private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    
    

    从代码及其注释可以看出来,它为当前线程初始化一个唯一(多次调用该方法会抛错)的Looper,并保存到sThreadLocal中,同时,会在其构造函数中,初始化一个唯一的MessageQueue。

    1.3.3 loop()

     public static void loop() {
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            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 {
                    //事件分发
                    msg.target.dispatchMessage(msg);
                    //……
                } catch (Exception exception) {
                   //……
                    throw exception;
                } finally {
                  //……
                }
    
                msg.recycleUnchecked();
            }
        }
    
    

    这个方法的作用十分简单:
    维持一个死循环。在循环中不断的从当前线程的MessageQueue中取出一个待分发的消息。

    Message msg = queue.next(); // might block
    
    

    注意这行代码,表示循环在这里可能会阻塞。当消息队列中没有消息的时候,循环就会阻塞在MessageQueue的next方法中(对应MessageQueue的next())。

    然后调用message.target.dispatchMessage()方法执行事件。

    1.4 Handler

    1.4.1 Handler的构造方法

    这几个构造函数分为两类:一类是形参有Looper的构造函数,一类是没有Looper的构造函数。

    public Handler(@Nullable 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());
                }
            }
    
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread " + Thread.currentThread()
                            + " that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    
    
    1. 没有Looper的构造函数或无参的构造函数被调用时,默认绑定当前线程的Looper,这个Looper存于Looper的sThreadLocal中(注意这个不一定存在),并且会校验Looper是否存在,如果不存在,就会抛出异常.
     public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    
    

    2.而有Looper的构造函数则关联传入的Looper,而这一点与Handler的线程切换至关重要。

      public interface Callback {
            /**
             * @param msg A {@link android.os.Message Message} object
             * @return True if no further handling is desired
             */
            boolean handleMessage(@NonNull Message msg);
        }
    
    

    另外,我们还需要注意构造函数中的Callback,初始化的时候也可以选择性传入,具体作用,我们后面讲

    1.4.2 Handler发送消息的方式

    Handler的方法 在这里插入图片描述

    除了postAtFrontOfQueue方法和sendMessageAtFrontOfQueue方法,其它方法最后都调用了sendMessageAtTime方法。不过殊途同归,最终这三个方法都调用了enqueueMessage方法,最终调用到了当前线程的MessageQueue的enqueMessage方法

     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,不管是post还是send方式,最终都会在这里**将当前发送该message的handler注入到message中**,这样,就解答了**问题1:message的target从哪里来的**。
    在此有必要说明一点post方式和send方式一个不同之处——post方式发送的message会被注入我们初始化的Runnable回调,这就解答了我们前面的**问题2:message的callback从哪里来的?**
    
    
    **Handler,Message,MessageQueue,Looper的相关知识已经介绍完毕,下面开始正文了!**
    
    ## 2\. Handler事件分发原理
    
    
    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    
        // Install selective syscall interception
        AndroidOs.install();
    
        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);
    
        Environment.initForCurrentUser();
    
        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
    
        Process.setArgV0("<pre-initialized>");
    
        // -1-初始化Looper
        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);
        //-2-开始循环
        Looper.loop();
    
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    

    每每看到这段代码,我都有同样的感触。那是16年的初夏,就像我16岁的雨季,在android成长路上充满了懵懂和无知。当时面试的时候有一位面试官问题,android有没有main方法,我说有的,我看到过,她当时像笑傻子一样。那时候我还对android知之甚少,只知道初次看activity代码的时候,找不到main方法我就郁闷怎么启动呢,百度了一下找到了这个main方法……面试的时候我还和面试官争辩了几句……时至今日,细节我已经忘记,但是我还是想起了她,因为她笑起来可好看,而今天是七夕😿……

    哈哈哈,言归正传,我们接着说这个方法。

    step1 初始化Looper

    在ActivityThread类的main方法中:
    Looper调用了它的一个特殊的方法——prepareMainLooper,专门为主线程准备的,用于初始化主线程的Looper;初始化完毕后,调用Looper的loop方法,启动Looper,的loop,开始消息分发。

    step2 初始化一个用于事件分发的Handler

    结合我们前面讲到的,handler没有Looper的构造函数调用的时候,会选择性注入一个callback;同时会默认绑定当前线程的Looper。这也就是为什么我们在主线程可以new Handler()然后直接发送消息的原因。

    而在子线程,若像下面这种方式:

     new Thread(new Runnable() {
                @Override
                public void run() {
    
                    new Handler().post(() -> {
    
                        // TODO: 2020-08-25 todo sth 
                    });
    
                }
            }).start();
    
    

    一运行显然是会失败的,会抛出 “Can’t create handler inside thread ThreadName-x that has not called Looper.prepare()”。我们前面讲Handler构造函数的时候说过,初始化Handler的时候需要关联一个Looper.子线程并没有初始化Looper,怎么办呢?有两种方式:一,我们自己调用Looper的prepare方法初始化一个,然后初始化Handler的时候就会自动关联;二,传入其它线程的Handler,比如主线程的handler。

      public void startNewThread() {
            //仅用于示例,不要模仿我这种Thread写法
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //方式一
                    Looper.prepare();
                    new Handler(getMainLooper()).post(() -> {
    
                        // TODO: 2020-08-25 todo sth
                    });
                    Looper.loop();
    //                Looper.myLooper().quitSafely();
                    //方式二
                    new Handler(getMainLooper()).post(() -> {
    
                        // TODO: 2020-08-25 todo sth
                    });
    
                }
            }).start();
        }
    
    

    切记!调用完Looper.prepare()方法后记得调用Looper.loop()方法。另外,在子线程任务结束后,调用 Looper.myLooper().quitSafely()退出。

    step3 发送一个消息

    我们通过post方式发送一个消息,紧接着在后续方法中给message注入当前handler,然后通过当前Handler关联的Looper,获取当关联的Looper中的MessageQueue,再然后,调MessageQueue的enqueueMessage方法消息入队。

    step4 分发一个消息

    消息入队之后,我们就该分发消息了。

    我们再次定位到Looper的loop()方法中:

    for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
                //……
                try {
                    //事件分发
                    msg.target.dispatchMessage(msg);
                    //……
                } catch (Exception exception) {
                   //……
                    throw exception;
                } finally {
                  //……
                }
            }
    
    

    在loop循环中,通过MessageQueue的next方法取出待分发的消息,然后调用
    msg.target.dispatchMessage(msg);上面我们已经知道,target就是message被发出入队时被注入的Handler,也就是说,负责发出这个message消息的handler,还负责分发它!我们走进Handler的dispathchMessage方法:

    
        /**
         * Handle system messages here.
         */
        public void dispatchMessage(@NonNull Message msg) {
            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.run();
        }
    
          /**
         * Subclasses must implement this to receive messages.
         */
        public void handleMessage(@NonNull Message msg) {
        }
    
    

    第1部分:如果msg的calback存在,即通过post方式初始化注入的Runnable存在,那么调用这个Runnable处理消息;

    第2部分:如果mCallback存在即初始化Handler的时候初始化了Callback,就调用mCallback的方式处理message。

    第3部分:如果前两个回调都不存在,就调用Handler的handleMessage方法处理消息。而我们也看到了,这个方法是一个空的实现,这就需要我么自己去实现它。这也是为什么我们平时使用handler的时候经常会实例化Handler的子类而不是Handler本身的原因。

    – 到此为止,Handler的事件分发原理终于讲完了……

    3. Handler的线程切换原理。

    我之前看过很多关于Handler的帖子,几乎都是在讲handler事件分发,本质上,事件分发和线程切换原理是一样的,但是,两者侧重点不同!两者都包含了消息机制的整个流程,很多人在讲述的时候,也把整个过程混为一谈;也有人把线程切换错误的认为是由handler完成的,实际上他是一叶障目只看到了冰山一角,根本没有看到重点,线程切换的实质和Handler根本没有半毛钱关系!!

    线程切换并不是由Handler完成的,线程切换的重点是Looper!非要说和Handler有关系,那么只能说Handler只充当一个送快递的作用。

    接下来,我来为你细细分解:

    我们回溯从handler、looper的初始化到发出一个message,最后到message分发完成整个流程:

    • 创建一个Looper,内部初始化一个MessageQueue,运行looper;
    • 初始化一个Handler;
    • 发送一个消息,消息入队;
    • 消息出队,分发一个消息;

    在讲Handler的构造函数时,我们知道,Handler会默认关联当前线程的Looper或传入的Looper,这点至关重要!为什么至关重要呢,因为:传入的不同线程的Looper,发送的消息最终被分发到的线程就不同!Message最终被处理的线程就是负责发送和分发它的Handler初始化的时候关联的Looper所在的线程!

    是不是有点郁闷,我们接着往下捋:

    handler通过post或者send方式最终都调用了下面两个方法:

    
        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);
        }
    
        public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
            //消息队列
            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, 0);
        }
    
    

    没错,这就是前面说的入队的方法。但是你有没有一瞬间产生过疑问——mQueue从哪里来的???

    往上翻,定位到Handler的两个构造方法,赫然看到一行:

    mQueue = mLooper.mQueue;

    看到没有,Handler的MessageQueue来自关联的Loooper,也就是我们前面说的,初始化Looper的时候默认初始化的MessageQueue!再回想我上面说的,Looper的prepare()方法为每个线程初始化一个唯一的Looper和MessageQueue!

    到此,有没有想到什么?

    我们再沿着流程往下,消息入队,然后由Looper的loop取出消息,最后被分发!

    可以说,所谓线程的切换,就是把消息插入不同线程的Looper内的MessageQueue中!消息分发就是执行在不同线程的Looper的loop()方法中的,自然而然的,消息被处理的时候就已经在不同的线程中了。自此,线程也就切换完成了!而handler,开始是一个负责将消息送入队列的快递员,最后,又只起了一个回调的作用!

    到此,Handler线程切换哦不,Looper的线程切换原理已经讲完了,最后总结两点:

    • 线程切换和事件分发本质是一样的;
    • 线程切换是有Looper完成的,Handler只充当一个回调的作用。

    文末彩蛋

    对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只能提前做好职业规划、在学习中不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

    附上我从程序员到架构师学习提升的资料、书籍,其中我把架构师应学习的Android核心技术整理成了PDF共计723页(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。————(此处免费打包领取)

    Android架构师学习视频

    当程序员容易,当一个优秀的程序员不容易的,从初级程序员到高级程序员,从初级架构师到资深架构师,每个阶段都需要不断的学习来提升自己,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人————(此处免费打包领取)

    总结

    好了,大功告成,虽然描述顺序和逻辑可能有点乱,但是每一个字,每一行代码都是我的成果。如有不足,还请见谅。欢迎批评指正,非常感谢:)。

    相关文章

      网友评论

        本文标题:Handler—Handler事件分发、Handler线程切换原

        本文链接:https://www.haomeiwen.com/subject/uztfsktx.html