Android Framework系列 Handler

 一 Handler简介

1、什么是Handler

Handler是Android中的消息机制,一般用来进行线程间通讯。Android只所以少有线程问题,很大一部的原因就是Handler。Handler采用生产者设计-消费者设计模式,实际上是内存共享的方案。通过优秀的内存管理设计方案,保证了Android线程间不会互相干扰,防止内存抖动。整个工作流程如下图:

2、Handler的使用场景
Handler 在Android中一般用在多线程的线程间通信。在多线程的应用场景中, 将工作线程中需更新 UI 的操作信息 传递到 UI 主线程 ,从而实现工作线程对 UI 的更新处理,最终实现异步消息的处理。流程如下图:

二 Handler的源码解析 

Android的Handler整套机制主要包含了Handler、Looper、MessageQueue、Message、ThreadLocal这几个类互相配合共同构成了Handler消息机制。

下面先对Handler处理消息的流程进行整体的分析。

1、消息的发送

我们在平常开发过程中,一般都是在子线程中调用Handler的sendMessage、post等方法去发送消息的。通过源码我们知道最终会调用MessageQueue的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);
    }

这里总结了Handler的主要方法如下图

那么我继续向下看MessageQueue的enqueueMessage 方法是如何处理消息的:

   boolean enqueueMessage(Message msg, long when) {
                ...
                ...
            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;
    }

我们看到MessageQueue的enqueueMessage方法首先判断有没有当前消息和msg.when也就是消息的延迟时间是否为0或者小于当前消息的时间。如果条件成立那么就将新消息插入到队列的头部。否则循环比较消息的延迟时间,然后按顺序插入到合适的位置。以上就是消息的发送。

2、消息的处理

Handler机制是通过Looper来处理消息的。要使用Handler那么线程中必需有且只有一个Looper,并且要调用loop方法。Looper的loop方法就是处理消息的。还是老套路先上源码:

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        ......

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

            msg.recycleUnchecked();
        }
    }

我们看到loop函数维持了一个死循环,在这个循环中调用MessageQueue的next方法从消息队列中取消息, 然后调用msg.target.dispatchMessage(msg)进行消息回调。

那么这里next方法是如何取消息的呢?

 @UnsupportedAppUsage
    Message next() {

       ......

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            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 {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

              .......
        }
    }

我们看到next方法很简就是循环遍历消息,然后判断消息是否到达执行时间,如果没到就调用nativePollOnce方法进行线程阻塞,如果到达执行时间就将该消息返回到Looper的loop方法里。然后调用handler的dispatchMessage方法进行回调。

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

这里有没有很熟悉啊?handleMessage,没错就是我们主线程更新UI的地方。以上就是Handler消息的主流程。

总结:

  • 以Handler的sendMessage方法为例,当发送一个消息后,会将此消息加入消息队MessageQueue中。
  • Looper负责去遍历消息队列并且将队列中的消息分发给对应的Handler进行处理。
  • 在Handler的handleMessage方法中处理该消息,这就完成了一个消息的发送和处理过程。

三 Handler的 注意点

1、Handler实质是内存共享,那么它是如何内存共享的?

由开头Handler的原理图我们知道线程是Handler消息机制的动力,通过Looper.loop方法循环处理消息。每一个Message就是一个内存单元。通过Handler 将Message发送到MessageQueue队列中。Looper.loop 循环取出Message再回调到Handler的handlerMessage中。

2、一个线程有几个Handler?

一个线程可以有任意个Handler。每个Handler都会记录在其发送Message的target就是该Handler。这样Looper处理消息后就会调用对应handler的回调方法。

3、一个线程有几个Looper?如何保证?

一个线程只能有一个Looper,是通过ThreadLocal保证的。Looper有一个静态常量sThreadLocal,并且Looper的构造方法是私有的,只能通过静态方法prepare和prepareMainLooper创建。调用prepare方法会首先调用readLocal.get获取当前线程的Looper,如果不为null就会跑出异常,否则就创建Looper实例并保持到ThreadLocal。

那么ThreaLocal是怎么保证的呢?原来每个线程中只有有一个ThreadLocal.ThreadLocalMap,ThreadLocalMap内部有一个Entry数组。ThreadLocal.set就是以ThreadLocal作为key将Looper 存到ThreadLocalMap中的。这样每个ThreadLocal只能有一个值Looper,而Looper只有一个ThreadLocal所以一个线程只有一个Looper。

由此我们可知,每个线程只能有一个Looper和一个MessageQueue,可以有多个Handler。

4、为何主线程可以new Handler? 如果在子线程中new Handler需要做些什么?
public Handler(@Nullable Callback callback, boolean async) {

       ...

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

我看到创建Handler时会调用Looper.myLooper()获取Looper(这里内部其实是从ThreadLocal中获取当前线程的Looper)判断如果looper为null就会抛出异常。所以要想创建Handler,当前线程必需有Looper实例。

主线程之所以可以直接new Handler就是因为在ActivityThread的main函数中初始化了Looper。源码如下:

public static void main(String[] args) {
        
......

        Looper.prepareMainLooper();
......
        
        Looper.loop();

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

子线程要想new Handler那么我们就要自己手动调用Looper.prepare为线程创建Looper实例。然后还需要调用Looper.loop()使其运转起来,否则Message是不会被处理的。在子线程任务结束时还要调用Looper的quitSafely(),quitSafely()会调用MessageQueue的quit()方法,清空所有Message,并调用nativeWake()方法唤醒之前被阻塞的nativePollOnce(),使得方法next()方法中的for循环继续执行,接下来发现Message为null后就会结束循环,Looper结束。如此便可以释放内存和线程。如果不调用的话会就会一直阻塞线程,使线程不能被回收。

5、Handler的内存泄漏原因。

回答这个问题前我们要知道什么是内存泄漏,内存泄漏是指本该被GC的对象因为某种原因而没有被回收,造成内存得不到释放。在Java虚拟机中GC是通过GCRoot判断回收的。

那么Handler中产生内存泄漏的话,谁才是GcRoot呢?

这里很多人常常会搞错,认为是ThreadLocal.ThreadLocalMap导致的内存泄漏。实际上这是错误的。

非静态内部类持有外部类对象的引用。Handler持有Activity,Message又持有Handler的引用。Message最终会被添加到MessageQueue中,MessageQueue是Looper的成员,所以Looper持有MessageQueue。在Android主线程中回调用Looper.prepareMainLooper方法,在该方法中会把lopper对象赋值给sMainLooper这个static成员。所以Handler中真正导致内存泄漏的GCRoot是sMainLooper。

总结一下引用关系:ThreadLocal.ThreadLocalMap——>Entry(WeakReference)——>static Looper sMainLooper(GcRoot)——>MessageQueue——>Message——>Handler——>Activity

那么如何处理Handler的内存泄漏呢?

可以定义静态内部类继承Handler,静态内部类中要使用弱引用Activity。然后在Activity的onDestory方法执行时,调用Handler的removeCallbacksAndMessages移除所有消息和回调。

6、多个Handler向MessageQueue添加消息,内部是如何保证线程安全的。

Messageueue的enqueueMessage函数中会通synchronized对象锁,来处理消息的插入。同样的next方法取出消息时也有synchronized对象锁,并且这两个函数使用的同一个对象锁。所以无论是添加消息还是读取消息都是互斥的,能够保证线程安全。

值得注意的是,由于锁的存在,所以不能保证延迟消息的时间一定准确噢。

7、我们使用Message时应该如何创建。

Handler消息机制实际是内存共享,这里其实就是共享的Message,每个message都是一个内存单元。Handler有大量消息,如果每次都new Message,那么就会有大量对象创建和回收,造成内存抖动。Handler使用享元设计模式完美的解决了这个问题。

  void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

Message回收时会调用其recycleUnchecked方法,将数据致空,然后放到 sPool池子里缓存等待复用。我们可以调用obtain方法从池子里拿出Message进行复用。

8、Handler的消息屏障和异步消息。

Handler的消息屏障和异步消息时成对出现的,是为了处理优先级高的消息而设计的。在Android框架中View的刷新使用了该方案,解决了View刷新优先级的问题。

 @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

这里使用MessageQueue的postSyncBarrier方法发送屏障消息。所谓屏障消息就是Message的target为null的消息,它是为异步消息做标记的。发出一个屏障消息紧接发出一个异步消息,异步消息在屏障消息后面。而Looper的loop方法会从队列头部不断取出消息进行处理,当遇到屏障消息时,就会查找异步消息,并处理它。当异步消息处理完成后会调用MessageQueue的removeSyncBarrier移除屏障消息。

Message next() {
       ......
        for (;;) {
           .....
                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());
                }
                .....
        }
    }
9、IdleHandler是什么?怎么使用,能解决什么问题?
IdleHandler MessageQueue 内定义的一个接口,一般可用于做性能优化。当消息队列内没有需要立即执行的 message时,会主动触发 IdleHandler queueIdle 方法。返回值为 false ,即只会执行一次;返回值为 true ,即每次当消息队列内没有需要立即执行的消息时,都会触发该方法。具体的细节说明如下:
IdleHandler 是 闲时 Handler 机制,可以在 Looper 事件循环的过程中,当出现空闲的时候,允许我们执行任务。 IdleHandler 被定义在 MessageQueue 中,它是一个接口。
    /**
     * 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.
         */
        boolean queueIdle();
    }
可以看出,定义时需要实现其 queueIdle() 方法。同时返回值为 true 表示重复使用 IdleHandler ,返回 false 表示是 一个一次性的 。 既然 IdleHandler 被定义在 MessageQueue 中,使用它也需要借用 MessageQueue 。在MessageQueue 中申明了对应的 add remove 方法。
   public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }

 public void removeIdleHandler(@NonNull IdleHandler handler) {
        synchronized (this) {
            mIdleHandlers.remove(handler);
        }
    }
可以看到 add remove 其实操作的都是 mIdleHandlers ,它的类型是一个 ArrayList 。 既然 IdleHandler 主要是在MessageQueue 出现空闲的时候被执行,那么什么时候出现空闲? MessageQueue 是一个基于消息触发时间的优先级队列,队列出现空闲存在两种情况。
1. 其一 MessageQueue 为空,没有消息;
2. 其二 MessageQueue 中最近需要处理的消息,是一个延迟消息( when>currentTime ),需要滞后执行;
这两个场景,都会尝试调用 IdleHandler 。 处理 IdleHandler 的情况,就在 MessageQueue.next() 这个获取消息队列下一 个待执行消息的方法中,我们看下具体的逻辑。
 @UnsupportedAppUsage
    Message next() {
       .....
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            .....
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
              .....
                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 {
                        .....
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

               ......
                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;
        }
    }
我们先了解一下 next() 中关于 IdleHandler 执行的主要逻辑:
1. 准备调用 IdleHandler 时,说明当前待调用的消息为 null ,或者这条消息的执行时间没有到;
2. pendingIdleHandlerCount < 0 时,根据 mIdleHandlers.size() 赋值给 pendingIdleHandlerCount ,它是后面循环的基础;
3. mIdleHandlers 中的 IdleHandler 复制到 mPendingIdleHandlers 数组中,这个数组是临时的,之后进入for 循环;
4. 循环中从 mPendingIdleHandlers 数组中取出 IdleHandler ,并执行其 queueIdle() 记录返回值存到 keep 中;
5. keep false 时,从 mIdleHandler 中移除当前循环的 IdleHandler ,反之则保留;
可以看到 IdleHandler 机制中,最重要的就是在 next() 中,如果 messageQueue 队列空闲 ( 没有消息需要立刻处理 ), 那么就循环 mIdleHandler 中的 IdleHandler 对象,如果其 queueIdle() 返回为 false 时,就会从 mIdleHander 中移除。 需要特别注意的是,对 mIdleHandler 这个 List 的所有操作,都是通过 synchronized 来保证线程安全的。
IdleHandler 能解决什么问题?
通过上面的分析我们不难发现, IdleHandler 是在主线程空闲的时候被执行。因此基于这个点,我们可以将一些相对耗时或者一些影响整个线程运行的事务放到IdleHandler 里面处理,譬如当我们需要收到调用 GC 的时候, GC 一般会带来STW 问题,于是我们可以将这个动作在 IdleHandler 里面执行,而 android 源码确实也是这样进行的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值