Android Handler机制

为何要有Handler机制?

解决工做线程更新UI的问题。
因为在Android机制中,为了保证UI操做是线程安全的,规定只容许主线程更新Activity的UI组件。但在实际开发中存在多个线程并发操做UI组件的状况,会致使UI操做线程不安全。故采用Handler机制,当工做线程须要更新UI的时候,经过Handler通知主线程,从而在主线程中更新UI。安全

ps1:为何不用锁呢?用锁会使UI的访问逻辑变得复杂,锁机制会下降UI访问的效率,锁会阻塞某些线程的执行。

Handler机制包含了什么?

重要概念

  • MainThread,UI线程,子线程(工做线程)
  • Handler 处理者
  • Looper 循环器
  • Message Queue 消息队列
  • Message 消息

UI线程与工做线程

UI线程就是APP启动时,就会开启一条ActivityThread线程,称之为主线程。网络

工做线程,则是在操做过程当中,开启的线程,如网络请求线程等。并发

Handler

image.png

首先看一下该类的注释,就能够知道该类的功能和用处了。
Handler使你能够发送和处理与线程MessageQueue相关联的Message和Runnable。每一个实例都与一个该线程的MessageQueue向关联。当建立Handler后,就会绑定MessageQueue。从绑定以后起,它就能够将Runnable和Message传入消息队列中,并在读取到对应消息时执行他们。less

从描述中,咱们能够提取到几个关键信息。Handler与线程绑定,与线程的MessageQueue绑定,大几率每一个线程就只能有一个MessageQueue。Handler能够发送处理两种类型的数据,Message和Runnable。(这些后面具体描述)异步

Looper

image.png

Looper是为了为线程提供消息循环。默认状况下,线程没有Looper,可使用prepare()获取循环,并使用loop()方法开始处理信息,只到循环中止。
与信息循环的大部分交互的都是经过Handler类。async

MessageQueue

image.png
包含Looper要发送的信息列表的低级类。消息不是直接添加到MessageQueue中,而是由与Looper关联的Handler类添加的。函数

从描述中可知,MessageQueue从属于Looper。oop

Message

就是Handler处理的消息。其中有几个比较重要的属性:post

  • target 发送和处理这个Message的Handler对象
  • callback 在主线程执行的回调
  • when 信息的传递时间
  • next 指向下一条Message(链表结构)

怎么使用Handler机制?

从前文可知,Handler须要绑定MessageQueue,而MessageQueue从属于Looper,因此从建立looper开始。ui

消息处理

1、建立Looper对象

  • 主线程 Looper.prepareMainLooper()
    主线程建立的时候会调用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();
        }
    }

咱们能够发现,这里也调用了prepare方法,咱们看一下prepare的方法。

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

这里出现了sThreadLocal字段,这个字段被static final修饰,说明是类共享的常量。(关于ThreadLocal具体讲解能够看上一篇文章)该常量,为每一个线程都提供类Looper的副本。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
ps2 : 如何保证一个线程中只有一个Looper? Looper的构造方法是private,只能在prepare中调用。而且若是一个ThreadLocal获取到相应的value,说明已经建立过。会抛出异常。
ps3:prepare的方法中携带一个布尔类型参数,用于判断是否能够退出循环。子线程的都为true,意味着能够退出;主线程的为false,意味着不能够退出。

接着来看看构造函数,每一个looper对象都会绑定当前线程与一个消息队列
image.png

2、使Looper开始工做

固然就是调用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);
                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的消息队列 MessageQueue queue = me.mQueue;
  • 而后开启死循环 for(::)
  • 经过消息队列获取MessageQueue获取信息 Message msg = queue.next();(可能形成阻塞)
  • 而后处理消息 msg.target.dispatchMessage(msg) 前文已知Message的target的就是发送和处理该对象的Handler。
ps4: 如何退出循环。looper调用quit方法,或者quitSafely方法后,队列就会放回msg == null,这样就能够结束循环。顾名思义,quit就是当即退出,而quitSafely则打上标志,当消息处理彻底以后才结束循环。
ps5:为何这里阻塞了?会不会影响主线程,或者影响CPU呢?这也是一个值得关注的问题,容后面分析。

3、MessageQueue读取数据

仍然仍是贴出关键代码

Message next() {
        ......
        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;
                }

                ......
            }

           ......
        }
    }

不难发现nextPollTimeoutMillis是很重要的参数。正常循环时,此参数为0。当有延迟消息,消息没准备好时,则会设置一个延迟时间,让下一次循环执行。当message为空时,此参数会设置成-1。

nativePollOnce方法是一个本地方法,也是阻塞这个消息队列的方法。当前面参数为-1时,就会使消息队列陷入等待状态。

注意这里有个同步方法,锁住类MessageQueue对象。因为处理的消息,是由其余线程传入的,为了保证线程安全,就得Synchronized。

4、Handler.dispatchMessage(msg)

image.png

从前文得知Hanler应该能够处理两类数据?为何这里就只有Message呢?后文再说,咱们先看处理方法:
当message有回调时(实际上就是前文的Runnable的类型数据),就会调用这个回调的run方法。

message.callback.run();

当msg.callback为空时,则此处会使用自定的hanlerMessage方法。

以上四步就是Handler机制处理消息的步骤了。那么Handler机制是如何上传Message的呢?

上传消息

1、重写Handler

从前文三种处理方法中,咱们就能够复写出三种重写Handler方法。

  • 新建Handler子类,实现handlerMessage处理方法。(对应最后一种处理)
  • 匿名内部类,经过Callback实现handlerMessag。(对应倒数第二种处理)
  • 在post方法中传入Runnable(对应第三种处理)
ps6: Handler要在处理该Handler(通常为主线程)的线程中建立,而后在工做调用。
ps7:这里的Runnable接口,只是做为一种声明,而不是像传统的要开启一个新的线程,切记。
ps8:子线程中能够用MainLooper去建立Handler吗? 子线程中Handler handler = new Handler(Looper.getMainLooper());,此时二者就不在一个线程中。

2、建立消息对象

  • 建立Runnable对象,能够直接实现一个类,也能够直接在post方法内使用匿名内部类。
  • 建立Message对象

    • 建立举例
Message msg = Message.obtain(); // 实例化消息对象
   msg.what = 1; // 消息标识
   msg.obj = "AA"; // 消息内容存放
*
ps9: 建立用了不常见的obtain,而不是new,是由于Message自带缓冲池,避免每次都使用new从新分配内存,只有当线程池无对象时,才会new新对象。

3、发送信息到队列

对应两种类型数据,亦有两条路径:

  • post(Runnable r)

    • sendMessageDelayed(getPostMessage(r), 0); getPostMessage方法中,将Runnable接口封装成了message对象,并将r设置成类msg.callback。
    • sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    • enqueueMessage(queue, msg, uptimeMillis); 将msg与当前的Handler绑定,并将其插入队列中
    • queue.enqueueMessage(msg, uptimeMillis);这步较为关键,贴一下源码。这里使用的是单链表的增添方法,链表的好处是增删便捷,可是查询不便利,这边也不多查询的方法,故使用链表也较为合适。
boolean enqueueMessage(Message msg, long when) {
        ......

        synchronized (this) {
            ......

            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;
    }
  • sendMessage(Runnable r)

    • endMessageDelayed(msg, 0);
    • sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    • enqueueMessage(queue, msg, uptimeMillis);
    • queue.enqueueMessage(msg, uptimeMillis);

步骤基本同上。

ps10:若是须要调用延迟的话,调用postDelayed方法,最后底层仍是和上面类似。
ps11:若是简单更新UI则直接使用Runnable便可(可能致使线程耦合度高)。如果要传递数据,则使用Message。

致此,怎么使用Handler机制已经讲述完成了。可是Handler机制的使用仍是存在一些问题的,让咱们继续探究。

Handler使用疑问

1、处理延迟消息:

一、MessageQueue.next()在取出Msg时,若是发现消息A有延迟且时间没到,会阻塞消息队列。
二、若是此时有非延迟的新消息B,会将其加入消息队列, 且处于消息A的前面,而且唤醒阻塞的消息队列。
三、唤醒后会拿出队列头部的消息B,进行处理。而后会继续由于消息A而阻塞。
四、若是达到了消息A延迟的时间,会取出消息A进行处理。

2、Looper 死循环为何不会致使应用卡死?会消耗愈来愈多的资源嘛?

ActivityThread实质是只是App的入口类,而不是真正的线程。

对于线程便是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,咱们是毫不但愿会被运行一段时间,本身就退出,那么如何保证能一直存活呢?简单作法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。

至于消耗资源,涉及到epoll机制(到IO的时候在继续讲解吧),该机制会再有数据到达时,才唤醒主线程工做。不然就让主线程休眠。

3、主线程的消息循环机制是什么?

此点不够清晰,之后详细研究。

4、Handler的内存泄漏问题。

  • 当Handler是非静态内部类或匿名Handler内部类时,会持有外部应用,这样会致使一个问题。当要销毁当前Activity时,可能有消息未处理彻底,Message指向了Handler,而当前Handler又持有Activity,因此当前的Activity不能被回收,可能形成内存泄漏。
  • 解决办法:

    • 一、存在“未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系
      首先将Handler设置为静态内部类,而后持有Activity的弱引用实例。保证消息队列中全部消息都能执行。
    • 二、Handler的生命周期 > 外部类的生命周期
      在OnDestory方法中,清空Handle队列,并直接终止handler,mHandler.removeCallbacksAndMessages(null);

5、补充

队列中的内容(不管Message仍是Runnable)能够要求立刻执行,延迟必定时间执行或者指定某个时刻执行,若是将他们放置在队列头,则表示具备最高有限级别,当即执行。这些函数包括有:sendMessage(), sendMessageAtFrontOfQueue(), sendMessageAtTime(), sendMessageDelayed()以及用于在队列中加入Runnable的post(), postAtFrontOfQueue(), postAtTime(),postDelay()。

相关文章
相关标签/搜索