Handler运行机制和源码分析

   日期:2020-05-19     浏览:101    评论:0    
核心提示:Handler的用处我们都知道Android是禁止在非主线程更新UI的,其主要原因是UI控件并不是线程安全的,如果多线程访问UI,将会出现非常多的问题。你可能会想到使用sychronized给UI控件加锁,但是加锁会带来两个问题,一方面影响运行效率,另一方面会使得UI访问逻辑变得很复杂。为了避免这些问题,所以才采用了单线程更新UI的方式。那么当我们正处于子线程时,如何能够更新UI呢?这时就可以使用Handler来进行线程的切换了,这也是我们最常用的方法。与Handler相关的主要成员功能Threadui

Handler的用处

我们都知道Android是禁止在非主线程更新UI的,其主要原因是UI控件并不是线程安全的,如果多线程访问UI,将会出现非常多的问题。你可能会想到使用sychronized给UI控件加锁,但是加锁会带来两个问题,一方面影响运行效率,另一方面会使得UI访问逻辑变得很复杂。为了避免这些问题,所以才采用了单线程更新UI的方式。那么当我们正处于子线程时,如何能够更新UI呢?这时就可以使用Handler来进行线程的切换了,这也是我们最常用的方法。

与Handler相关的主要成员功能

  • ThreadLocal 用于存储不同线程中的Looper,是每一个线程,都已唯一与之对应的Looper。
  • Looper 主要用于从MessageQueue中取出消息,并交给handler处理消息。
  • MessageQueue 用于以队列的方式存储取出消息,实质是链表的形式。
  • Handler 用于发送信息以及接受信息,并对信息进行处理。
    下面我将会结合源码对以上四个方面进行讲述

ThreadLocal

TreadLocal是一个数据存储类,数据存储后,只有在指定的线程中才能取出存储的数据,对于其它线程则无法获得数据。ThreadLocal使得每一个线程中都能与当前线程相对应的数据,且每个线程对应的数据互不干扰。
我们首先来看一下它的set方法。

//ThreadLocal
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

可以看到set方法首先会尝试通过thread获得TreadLocalMap类,这个类是TreadLocal的静态内部类

static class ThreadLocalMap {
	private Entry[] table;
	static class Entry extends WeakReference<ThreadLocal<?>> {
            
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
}

可以看到里面是有一个Entry数组,TreadLocal中的数据就是存在这里的,顺便一提这里的WeakReference指的是弱引用方式,这种引用的特殊之处在于,当存储的数据没有强引用时,如果GC执行回收时,就会自动销毁数据,从而防止了内存泄漏,这里就不多说了。

然后在每一个Thread中都有着一个TreadLocalMap的成员变量,getMap方法就是获得所给Thread中的TreadLocalMap变量。接着是判断返回对象map是否为null对象,如果不是,就初始化Tread中的TreadLocalMap对象,如果是,就用自身作为key以及value值来创建Entry,存入相应的Thread中,具体存入过程如下

//ThreadLocalMap
private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

可以看到大概逻辑就是简单的更新数据。这里就不多说了。

接着我们再来看下ThreadLocal中的get方法

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

首先根据所在线程取出当前的map,然后查找map中存入的数据并返回,由于之前存的时候存入了TreadLocal对象的值,在取的时候就可直接把TreadLocal对象作为key进行查找。getEntry的代码如下

private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

整体是很简单的,通过key值在Thread中的TreadLocalMap对象中查找数据。

最后总结一下过程,ThreadLocal通过当前的线程来获取相应线程中存储数据的TreadLocalMap,TreadLocalMap就是真正的存储数据的对象,通过对他进行修改,获取,就实现了TreadLocal在不同线程中拥有不同的数据的功能。

MessageQueue

MessageQueue用于以队列的形式存储消息,即先进先出,但实质上它是以链表的形式实现的。其中比较重要的方法有enqueueMessage、next,分别用于消息的添加和取出。
首先我们来看一下enqueueMessage方法:

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

总体逻辑就是根据所给时间when将消息添加到链表的适当位置,添加链表的逻辑相信大多数人都是会的,就不多说了,重点是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();
            }

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

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
				......     

    }

这里省略了一些不太重要的代码,可以看出当MessageQueue中没有消息即代码中的msg为null时并且mQuitting为false时,这个方法将无限循环下去,不断的读取是否有消息的加入,阻塞线程。当消息队列中有消息时,就会读取消息返回,并且删除该消息。

Looper

Looper在流程中类似于一个中间商,不断的从message中获取消息,然后传递给handler进行处理。每一个线程只会有一个Looper,我们很容易想到它是通过TreadLocal实现的
在它的内部存在着ThreadLocal的静态对象

	//looper
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

通过ThreadLocal我们就可以使的每一个线程中的Looper的值独立,不会相互干扰。我们在子线程中使用Looper时,必须先调用prepare方法创建当前线程的Looper

//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");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

前面讲了ThreadLocal的实现原理,这里就不难理解了。简单的存储对象罢了。同时我们能看到每一个线程中只能创建一个Looper,否则会报错。在MainThread中即我们更新UI的线程中,在创建线程时系统便会自动调用prepareMainLooper()为主线程创建Looper

//looper
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创建的同时, 会创建MessageQueue

//looper
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

也就是说一个Looper对应一个MessageQueue。创建完looper后Looper还并不能开始从消息队列读取消息。只有调用了Loop函数才行:

//looper
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;

        // 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();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // 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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...     
        }
    }

同样这里我们只列出了比较关键的代码。在Loop中,会调用MessageQueue的next方法来获取消息,同时调用handler的dispatchMessage方法处理消息,只有当msg==null时才会结束循环,否则如果消息队列中没有信息时,next()方法会堵塞,无法返回值,这就使得Loop也被堵塞在那里。如果需要结束Looper可以调用Looper中的quit和quitSafety方法,
这两个方法的区别是,quit将直接结束读取消息,而quitSafety会先将消息队列中的消息处理完毕,在结束读取消息。这两个方法都会调用MessageQueue的quit方法:

//MessageQueue
void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

这里会改变mQuitting的值为true。这个值我们在MessageQueue的next就见过:

//MessageQueue中的next()
// Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

返回null之后就能使Looper结束循环,使得方法执行完毕。

Handler

Handler用于发送消息并且处理消息。发送消息可以通过post和send的一系列方法实现,比如:

//handler
public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
public boolean sendMessageAtTime(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);
    }
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

很容易看出发送信息的过程,实质上就是设置消息的target, 再把消息加入到消息队列中。此后Looper就会通过MessageQueue的next()读取消息,并且读取 成功后调用handler的dispatchMessage方法

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

这里面msg.callback是一个Runnable对象,可以在生成信息时设置。mCallback则是Handler中的一个成员变量,具体如下:

public interface Callback {
        
        public boolean handleMessage(Message msg);
    }

紧接着handleeMessage是Handler中的一个空方法。通过源码我们很容易知道,我们设置handler如何处理消息时有三种方法:

  • 在消息中设置callback参数
  • 在Handler的构造函数中传入Callback的实现
  • 重写Handler中的handleMessage方法

以上三种方式的优先级依次减弱。

码字不易,谢谢拜读。

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服