Handler

134 阅读4分钟

Handler主要构成:

  • Handler
  • Lopper
  • MessageQueue
  • Message

Handler的构造方法:

在使用Handler的时候,先创建一个Handler对象,Handler的构造函数很多,

第一种 
public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

 第二种
 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());
            }
        }
        //获取当前线程的Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        //给Handler中的成员变量初始化
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

第一种传入了Looper,就使用传入的Looper对象,

第二种构造方法中第一个if判断当前Handler类是否有内存泄漏的隐患,主要是检验当前类是否是匿名类、成员类、局部类是否静态修饰等. 接着就是  mLooper = Looper.myLooper(); 如果为空就会报异常,表示当前线程中没有Looper对象,在当前线程中没有Looper的话就会抛异常,所以在初始化handler的时候必须要有Looper对象,所以在子线程中创建Handler必须要先通过Looper.prepare()方法创建Looper,接着就是对Handler中一些成员变量进行初始化,将Looper中的消息队列引用传递给mQueue

MessageQueue 中的 Message有顺序吗?如果有是按什么顺序排列的?

通过之前的源码阅读知道,是有顺序的,是根据Message.when这个相对时间排列的

MessageQueue内部实现是一个队列吗?

不是,内部实现其实是一个单链表。

Looper的quit方法和quitSafely方法有什么区别?

quit方法会清空消息队列中的所有消息,quitSafely方法只会清除所有延迟消息,非延迟消息还是分发出去交给Handler处理

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

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

这里实际上是调用了MessageQueuequit方法

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

quit方法传入的是false调用的是removeAllMessagesLocked()方法,quitSafely传入的是true调用的是removeAllFutureMessagesLocked方法

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

removeAllMessagesLocked方法中直接将所有消息清空

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

removeAllFutureMessagesLocked方法中先做判断如果首节点when大于当前时间说明全是延迟消息,就同样调用removeAllMessagesLocked处理全部清空,否则循环找到队列中when大于now也就是大于当前时间的节点位置,将该节点消息同其后的所有消息清空。

为什么主线程不会因为Looper.loop()里的死循环卡死?

这个涉及到LinuxEpoll机制。简单来说就是Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程

内存泄漏解决方法

  • Handler定义为静态,静态内部类不会持有外部类的引用。
  • 因为静态内部类不持有外部类引用,所以在Handler中无法访问外部类的成员,需要用一个外部类的弱引用来访问外部成员,又因为是弱引用,在GC时可以将其回收,不会造成内存泄露。
  • ActivityonDestory方法中调用removeCallbacksAndMessages方法清除消息队列。

总结

  • Handler是Android提供的一种线程间通信方式。因为Android中UI控件不是线程安全的,多线程并发访问会出现同步问题,所以如果子线程想更新UI通常通过Handler来完成线程间通信。

  • Handler的工作流程主要是由Handler发送消息,将消息添加到消息队列MessageQueue中,再通过轮询器Looper从消息队列中取出消息,交给Handler去分发处理消息对应任务。

  • Handler使用时容易发生内存泄露,记得通过静态内部类+弱引用的方式使用Handler,并且在Activity的onDestory方法里记得清除消息队列。