Handler

2019-03-17  本文已影响0人  啦啦哇哈哈

最近一些需求用到handler蛮多的,先把最近需求里面用的场景摆一下,后面补充一篇Handler的系统学习文章
1.handler实现定时器
2.handler实现空间定时渐入渐出

一、什么是Handler

首先了解一下为什么要用Handler,这要从主线程(UI线程,因为主线程主要负责android的UI控件的展示,事件分发等等,所以也叫UI线程)谈起,刚学的时候,在子线程更新UI会见到一个常见的错误:


非UI线程UI操作的Exception

那有没有想过为什么android要求UI更新操作只能在主线程呢?
这里有一篇,
为什么UI线程
总结一下,就是View都是线程不安全的,google限定的这个单线程模型就是为了避免View线程不安全的问题。但是为什么不把View搞成线程安全的呢?要加锁啊啥的,都是有成本的,这与移动端UI高效的展示和使用是有冲突的。所以才制定了这么一个规则,同时我们从保证UI的体验这个角度,也就能明白,为什么不能在UI线程去搞网络请求等耗时操作,否则会ANR。
还有几篇从源码角度分析了这个异常的来龙去脉,并且解释了非UI线程依旧可能更新UI但是不抛异常的情形
非UI线程真的不能更新UI吗
源码分析——Android中为什么在子线程不能更新UI
有framework层相关ViewRoot WindowMananger的知识这个可以后面整理一下,先有个概念。

在这个规则的基础上,我们引入了Handler机制,Handler机制是谷歌单线程模型的伴生产物,就是为了解决“子线程需要执行耗时任务”和“主线程不允许耗时且必须负责UI更新”这两者之间线程的矛盾问题而出现的。

现在来回答什么是Handler?


Message传递的是消息,MessageQueue是消息队列,handler里面还有个Looper的轮询器,他会从Queue里面获取消息,并交由Handler去处理消息。

二、使用Handler

使用有两个方法:
1.post(runnable)


这里通过post方法,把一个runnable对象传入了创建在主线程中的handler,通知handler进行主线程的操作
看一下源码:
post源码
发现事实上post是调用的sendMessageDelayed。里面的getPostMessage方法点进去再看:

就是新建了一个消息,把消息的callback设置成了这个runnable,就能保证Handler在处理消息时候,执行的任务是runnable里面我们写的。关于Message.obtain后面会介绍。下面看看sendMessage方法。
2.sendMessage(message)
使用实例——创建Handler
这里先创建了一个handler,然后覆写了handleMessage方法。
组建Message,并调用sendMessage发出去
Message的what\arg1\arg2都是可以带的值
这是sendMessage的源码:
sendMessage
可以发现又是sendMessageDelayed,和上面一模一样。。 sendMessageDelayed源码

这又是一个sendMessageAtTime


sendMessageAtTime

这里面就是把message加入了MessageQueue,再往下看那个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;
    }

挺长的。。需要知道的点就是MessageQueue内部就是用一个单链表维护消息列表,里面存了一个mMessge是头结点,上面的源码除过一些判空操作就是一个单链表的插入操作,只不过有一个时间的判定,根据msg.when去判断时间先后,可能插入到链表的中间末尾头部,都有可能。(开头的那个msg.target是接发这个消息的handler)
使用上就是这样简单的两个,当然还有sendMessageDeslayed直接调用。

下面来详细学习一下Handler机制

三、Handler的原理

Handler机制四大部分

Looper是每个线程独有的,创建Looper的时候就创建了MessageQueue(里面那个成员变量mQueue),二者关联在一起,Looper通过loop()方法,读取MessageQueue的消息,读到消息之后,把消息发送给handler进行处理,MessageQueue里面有一个mMessage。

handler持有mQueue和mLooper就是上面那俩对象,handler负责消息的发送和处理,它只能把消息从子线程发送到handler相关的线程的MessageQueue中,也就是它创建时候的线程。这样这几个东西就建立了联系。

还是来看一下源码:
先看Handler的最长的那个构造方法(其他也都是调它)

    /**
     * Use the {@link Looper} for the current thread with the specified callback interface
     * and set whether the handler should be asynchronous.
     *
     * Handlers are synchronous by default unless this constructor is used to make
     * one that is strictly asynchronous.
     *
     * Asynchronous messages represent interrupts or events that do not require global ordering
     * with respect to synchronous messages.  Asynchronous messages are not subject to
     * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
     *
     * @param callback The callback interface in which to handle messages, or null.
     * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
     * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
     *
     * @hide
     */
    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());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

Handler在构造的时候,用Looper.myLooper来赋值mLooper。再用mLooper中的mQueue赋值给mQueue。下面我们看一下Looper.myLooper()这个方法。

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

是通过ThreadLocal的get方法,来获取当前线程关联的Looper对象。那么什么是ThreadLocal?ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
不同线程访问ThreadLocal,不管是get还是set方法,它们对数据做的读写操作仅限于线程内部。这样一来,通过ThreadLocal来获取looper,每一个线程就有单独唯一的Looper。
关于ThreadLocal的详解,看这个问题的高赞。
ThreadLocal知乎,深度好答,一定要看一下

可以在Looper的静态变量里面找到这个sThreadLocal对象和它的初始化,以及一个sMainLooper,顾名思义就是主线程的Looper对象。

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

下面看一下这个sThreadLocal是在哪里调用的set把那些Looper加进去的,以及这个sMainLooer怎么初始化并且加入到sThreadLocal里面的?看一下源码的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()}.
      */
    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));
    }

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

可以看到sThreadLooper是在prepareMainLooper里面初始化的,而第一行也上来就调了一个prepare,在prepare中我们可以看到new Looper()加入了sThreadLocal,不过主线程的quitAllowed参数是false,也就是不允许主线程的Looper退出。这样这些Looper怎么初始化的,以及怎么加入ThreadLocal的,都清楚了。另外也要注意源码中提到,在call了prepare之后,还要调用loop()方法让Looper开始工作,并且调用quit()方法,可以让Looper停止工作。

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

        ....
        Looper.loop();
        ....
}
//子线程中需要自己创建一个Looper
new Thread(new Runnable() {
            @Override
            public void run() {
               
                Looper.prepare();//为子线程创建Looper       
                Handler handler = new Handler();        
                Looper.loop(); //开启消息轮询
            }
        }).start();

MessageQueue是Looper的变量,是在上面那个new Looper()时候构造函数就创建了,不需要我们自己手动调用任何方法:

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

知道了这几者怎么关联和初始化的,下面还是来看一下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();
        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;
            }
            ......
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ......
            }

           ......
            msg.recycleUnchecked();
        }
    }

删除了一些判空和Log,留下来核心的代码,最重要的部分就是这个for死循环。死循环中关注两个点:

1.循环退出的条件msg == null

看一下msg怎么获取的,使用queue的next()方法,我们去看一下MessageQueue的这个方法:

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;
                }
                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
         ......
        }
    }

删除了一些代码之后,留下这些,可以看到还是一个死循环,简单来看还是一个链表的删除头结点并且返回的操作,不过还是要考虑时间因素。

    public void quit() {
        mQueue.quit(false);
    }
    public void quitSafely() {
        mQueue.quit(true);
    }

都是调用了MessageQueue的quit方法:

    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标志位,safe与否的差别直接看源码:

    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

就是一个链表的回收操作,处理掉了队列中的所有消息。

    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

可以看到,只是回收了未来的消息(when比now大的所有消息),即是通过sendMessageDelayed和postDelayed发送的delay的时间还没有到的消息。
这两个死循环,获取Message对象,以及死循环退出的条件搞明白了,下面来看一下是如何处理消息的,也是loop()方法中第二个需要注意的点。

2.msg.target.dispatchMessage(msg)

这个target是Messge对象对应的handler,前面已经提过了,来看一下这个Handler中的dispatchMessage()方法

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

就是回调一下handleCallback或者handleMessage方法,这几个callback第一个msg.callback是个runnable,就是post里面传的。然后这个mCallback是Handler另一个构造函数传的值,这允许我们不用去派生Handler的子类去覆写handleMessage方法,传入Callback即可。而最后那个handleMessage就是常用的这种匿名类派生Handler子类去覆写的那个handleMessage方法。

因为这些loop什么的都是在创建Handler时候后关联的线程中的Looper调用的,所以也就完成了线程切换,以上就基本上走完了整个Handler的所有源码流程。

最后在补充一下Message:


还有一个很大的问题值得思考。。等学了IPC之后来填坑
Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
Android面试:主线程中的Looper.loop()一直无限循环为什么不会造成ANR?

四、Handler引起的内存泄漏以及解决办法

主要学习和抄写了:
1.《Android开发艺术探索》
2.android的消息处理机制之Looper,Handler,Message
3.学姐的简书

上一篇下一篇

猜你喜欢

热点阅读