android--Handler 二十问

1、choreographer你知道吗?
2、消息队列内部怎么排序?
3、looper源码你说一下?
4、handler,looper和线程的关系?
5、为什么一个线程只能一个looper,你知道threadlocal吗?
6、主线程为什么没有被loop阻塞?
7、Handler 同步消息屏障了解吗?
8、一个线程有几个Handler?
9、Handler内存泄漏原因?
10、为什么主线程可以直接new Handler?
11、子线程中维护的Looper,消息队列无消息的时候处理方案是什么?有什么用?
12、多个Handler往MessageQueue中添加数据,发消息时各个Handler可能处于不同线程,内部是如何确保线程安全的?
13、使用Message时如何创建它?
14、IdHandler?
15、post()与sendMessage()区别
16、IntentService是什么?
17、子线程中有几行代码需要在主线程执行,有几种方法?
18、handler延时操作
19、Android消息机制解析(native层)能说一下吗?
20、 对调用sendMessageDelayed(xxx, 5秒)发送了一个Message, 这个5s 是怎么实现的

在这里插入图片描述

1、choreographer你知道吗?

Android 之 Choreographer 详细分析

2、消息队列内部怎么排序?

Handler中的消息队列,也就是MessageQueue,从名字看是一个队列,但是底层是单链表结构,因为单链表结构比较适合插入和删除操作。
入队操作是通过final类MessageQueue的enqueueMessage方法实现的,换句话说就是MessageQueue.enqueueMessage()向消息队列添加消息,android9.0的完整代码如下:

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

可以看到
1、入参时候的 when 参数,就是我们平时调用 sendMessageDelayed 方法时传入的延时 + 当前系统时间,为了方便起见,我们在后边的分析中只取延时,忽略系统时间
2、入队之前,先将当前消息的发送时间赋值为当前消息的 when 属性,即 msg.when = when
3、使用局部变量 p 来记录当前的消息,默认值为 null,然后开始逻辑判断
4、MessageQueue.next()从消息队列中获取消息使用for循环

消息入队逻辑推演:
在主线程发送几个消息如下:

mHandler.sendEmptyMessage(100);
mHandler.sendEmptyMessageDelayed(200, 1000);
mHandler.sendEmptyMessageDelayed(300, 500)

现在我们梳理一下这三条消息入队的顺序

1、第一条消息进入enqueueMessage方法,when=0

if (p == null || when == 0 || when < p.when){}

p==null判断成立,因为mMessages默认值为null,然后将p赋值给第一条消息的next属性(标准的单链表结构,利用next属性相连),然后将第一条消息赋值给全局的mMessages对象

2、第二条消息进入 enqueueMessage 方法,when = 1000

Message p = mMessages;

现在 mMessages 对象是有值的,是第一条消息对象,mMessages.next = null,mMessages.when = 0。

那么我们来看 if 判断条件:

1、p == null,不成立,当前 p 是 mMessages 对象,即第一条消息对象
2、when == 0,不成立,第二条消息的 when 应该是 1000
3、when < p.when,即 1000 < 0,不成立

三个条件都不能满足,那就来到 else 分支,我们继续一点点梳理:

  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;

1、先声明一个 Message 对象 prev,用来标记前一条消息,即 previous message,然后开启一个死循环
2、在循环中,将当前的消息对象 p 赋值给 prev 变量,然后取 p 的下一个节点,赋值给 p,此时判断 p == null 是成立的,因为之前 p.next = null,现在 p = p.next,所以 p == null 也是成立的,跳出循环
3、跳出循环时,prev 是 mMessages 对象,即第一条插入的数据,p 是 mMessages.next,是 null
4、然后将待插入消息的 next 属性设为 p,即 null,然后将待插入消息赋值给 prev.next,即 mMessages.next,就将先后两条消息连接起来了

到现在为止,MessageQueue 的样子应该是:

mMessages = {Message(when = 0, next = {Message(when = 1000, next = null)})}

第三条消息进入 enqueueMessage 方法,when = 500

Message p = mMessages;

同样的,mMessages 不为空,指向首条插入的 Message 对象,when = 0,next 不为空,而传入的 when = 500,很明显 if 条件不能被满足:

if (p == null || when == 0 || when < p.when){}

// p 不为空,传入的 when = 500,而 p.when = 0,所以 if 条件不能成立

然后进入 else 分支:

Message prev;
for (; ;) {
    prev = p;
    p = p.next;
    if (p == null || when < p.when) {
      break;
    }
}
msg.next = p;
prev.next = msg;

依然是开启死循环,从 mMessages 开始依次向后遍历,但是与第二次插入不同的是,当遍历到第二条消息对象时,即 prev = 首条消息,p = p.next(第二条消息) 时:

p == null 不成立
when (500) < p.when (1000) 成立,跳出循环
跳出循环时,prev = 首条消息,p = 第二条消息

然后将待插入消息的 next 指向 p,即原本的第二条消息,将 prev 的 next 指向待插入消息,即将第三条消息插到了原本的第一条和第二条消息中间,到此为止也就完成了消息的插入。

小结:

当我们顺着代码逻辑,完整地走过这几次消息入队后,也就很清楚的看到 MessageQueue 的链表本质了。

概括地来说,MessageQueue 只持有一个 mMessages 的消息对象,然后利用 Message 的 next 属性进行多个消息之间的链接,同时使用 when 属性对消息进行排序,when 的值越小,在链表中的排序越靠前。

以我们上边的推演为例,MessageQueue 最后的形式应该是:

mMessages = {
  Message(when = 0, next = {
    Message(when = 500, next = {
      Message(when = 1000, next = null)
    })
  })
}

回答问题二参考:https://www.jianshu.com/p/9efe3b48b730

Android消息队列中的Message入列和出列,都是基于单链表实现,排序的核心变量就是Message内部的when变量,when变量是一个时间戳,由Handler给该变量赋值,延迟消息,定时消息,都是根据when变量来实现的。在分析队列的逻辑时,发现了和jni部分的通信,主要是nativePollOnce,nativeWake方法,这两个方法实际是实现了空队列阻塞,以及唤醒功能,底层使用epoll机制实现。

拓:为什么 MessageQueue 要使用链表的形式来存储?
其实我们出去面试经常会被问到类似的问题,本质上这个问题就是:数组和链表的区别在哪里?

数组的优势在于使用角标查找方便,时间复杂度为 O(1),但是插入的时候就会很费劲,要把整个数组依次向前向后移动来为新插入的数据腾出空间,时间复杂度为 O(n);相反的,链表的优势却恰恰在于插入。

如果你看完了上面的文章,你会发现我们使用链表插入数据的时候,只需要将原本的 next 指针换一个指向,再将新数据的 next 指向原数据的 指向就可以完成插入,整个时间复杂度也是 O(1) 级别的;但是我们在找目标位置的时候就太痛苦了,需要一个死循环来从头开始遍历 Message 的 next 对象,知道找到目标对象才能完成插入,如果就是要插入到最后一个位置,那整个时间复杂度也是 O(n) 级别的。

而 Handler 的使用场景正是频繁插入,但是每次都只取最前面的消息处理,所以对于这种情况,天然的应该使用链表来进行存储而不是数组。

3、looper源码你说一下?

1、首先看一下looper是如何初始化的
Handler初始化的时候,有一段注解说,Looper.myLooper()要获取到Looper,必须要先调用Looper.prepare(),而且Looper的构造函数是私有的,不能从其他类初始化,Looper的构造函数源码:

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

Looper.prepare()源码如下:

 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));
    }
       private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

可以看到,一个线程只能调用一次 Looper.prepare(), 否则就会报 RuntimeException 。

如果是第一次调用,会创建一个 Looper 放到 sThreadLocal 中。

在构造 Looper 的时候,创建了属于这个 Looper 的消息队列 MessageQueue ,暂且不管,后面在细看。

2、开启消息循环
想要开启消息循环,需要通过Looper.loop(),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) {
                return;
            }
            //有省略代码
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
           
            //有省略代码
            msg.recycleUnchecked();
        }
    }

虽然整个方法比较长,但是主干代码其实非常简单:拿到消息队列 queue 以后,进入死循环。

循环中就做一件事:从消息队列中获取到一个可以执行的 Message,接着调用这个消息的 target(即 Handler)的 dispatchMessage 方法,消息就分发出去了。批注上might block的意思就是没有消息了,就阻塞。

如何从队列中拿到一个可执行的消息,就要靠 MessageQueue 了。

3、消息队列:MessageQueue
构造Looper的时候,会创建消息队列,即MessageQueue,源码如下:

// MessageQueue.java

private native static long nativeInit();

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

这里就和 NDK 发生关联了,在构造方法中,通过本地方法 nativeInit 创建了一个对象 mPtr ,这个对象就是 NDK 层的 NativeMessageQueue 。

4、消息压入

在了解如何获取可执行消息之前,我们需要先知道消息是如何被压入队列的。

说明:Hanlder 的消息队列是一个单向链表队列,从队列的头部一直链接到队列的尾部。

还记得 Handler 发送消息的时候,最后调用的是 MessageQueue 的 enqueueMessage 吗?来看看这个方法(注:代码省略了一些异常判断片段):

// MessageQueue.java

boolean enqueueMessage(Message msg, long when) {

    // 省略一些代码...
    
    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        
        //【1】拿到队列头部
        Message p = mMessages;
        boolean needWake;
        
        //【2】如果消息不需要延时,或者消息的执行时间比头部消息早,插到队列头部
        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 {
            //【3】消息插到队列中间
            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;
        }

        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

主要分为3个步骤(见以上代码标注)。

1、 mMessages 是队列的第一消息,获取到它
2、 判断消息队列是不是空的,是则将当前的消息放到队列头部;如果当前消息不需要延时,或当前消息的执行时间比头部消息早,也是放到队列头部。
3、如果不是以上情况,说明当前队列不为空,并且队列的头部消息执行时间比当前消息早,需要将它插入到队列的中间位置。

如何判断这个位置呢?依然是通过消息被执行的时间。

通过遍历整个队列,当队列中的某个消息的执行时间比当前消息晚时,将消息插到这个消息的前面。

可以看到,消息队列是一个根据消息【执行时间先后】连接起来的单向链表。

想要获取可执行的消息,只需要遍历这个列表,对比当前时间与消息的执行时间,就知道消息是否需要执行了。

5、获取可执行消息

在 Looper.loop() 循环中,通过 queue.next() 来获取可执行消息,直接来看看这个方法。

// MessageQueue.java

Message next() {
    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();
        }
        
        //【1】调用 NDK 层方法,线程阻塞挂起,进入等待
        // nextPollTimeoutMillis = -1 时,进入无限等待,直到有人唤醒
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //【2】判断队列是否插入了同步屏障,是则只执行异步消息
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //【3】消息没到时间,重新计算等待时间
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    //【4】消息时间已到,重新拼接链表,并返回该消息
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    
                    return msg;
                }
            } else {
                // 没有消息,进入无限等待
                nextPollTimeoutMillis = -1;
            }

            if (mQuitting) {
                dispose();
                return null;
            }

            //【5】判读是否有空闲监听器,有的话,进行回调
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            
            // 只有for循环的第一次为 -1 ,
            // 执行一次以后,pendingIdleHandlerCount变成 0,
            // 不再执行空闲监听回调
            if (pendingIdleHandlerCount <= 0) {
                // mBlocked 为true,表明线程阻塞挂起
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // 回调空闲监听器
        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);
                }
            }
        }
        
        // 这里设置为0,则上面的空闲监听器不再执行了
        pendingIdleHandlerCount = 0;
        
        nextPollTimeoutMillis = 0;
    }
}

获取消息主要分为 5 个步骤(见以上代码标注):

1、进入 for 循环来遍历消息队列,调用 NDK 方法 nativePollOnce 挂起线程,第一次进入时,等待时间为 0 ,继续往下执行;
2、判断队列是否插入了同步屏障,是则只执行异步消息(同步屏障暂且放一边,后面详细介绍),如果没有同步屏障,则获取到的是队列头部消息;
3、对比当前时间与消息执行时间,如果时间没到,计算需要等待的时间,重新进入等待;
4、如果消息执行时间已到,重新拼接链表,并返回该消息,此时,Looper 将会得到一个消息,将它分发给 Handler 处理。
5、最后是一个空闲监听处理,作用是当队列中没有需要执行的消息时,说明线程进入空闲状态,这时候可以去执行一些其他的任务。

6、唤醒线程
当消息队列为空的时候,Loop 会进入无限阻塞挂起,如果这时候用户发送了一个消息,这时候如何唤醒线程呢?

细心的你可能已经发现了,在上面 enqueueMessage 的时候,有一个地方会唤醒线程,看回这个方法:

// MessageQueue.java

boolean enqueueMessage(Message msg, long when) {

    // 省略一些代码...
    
    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        
        //【1】拿到队列头部
        Message p = mMessages;
        boolean needWake;
        
        //【2】如果消息不需要延时,或者消息的执行时间比头部消息早,插到队列头部
        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 {
            //【3】消息插到队列中间
            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;
        }

        if (needWake) {
            // 唤醒
            nativeWake(mPtr);
        }
    }
    return true;
}

注意到最后的这个 needWake 标志了吗?如果该变量为 true ,就会通过 NDK 方法 nativeWake 来唤醒挂起的线程。

两种情况会唤醒线程:

1、【队列为空 || 消息无需延时 || 或消息执行时间比队列头部消息早】 && 【线程处于挂起状态时(mBlocked = true)】
2、【线程挂起(mBlocked = true)&& 消息循环处于同步屏障状态】,这时如果插入的是一个异步消息,则需要唤醒

消息机制总结:

1、准备 Looper:Looper.prepare()
2、创建消息队列 MessageQueue:在构造 Looper 的时候被创建
3、创建 Handler: 用户自定义 Handler
4、开启 Loop 循环:Looper.loop()。循环遍历消息队列,判断是否到达执行时间
5、用户发送消息:通过 Handler 的 sendMessageDelay 等方法,将消息压入队列,等待被 Looper 遍历和执行

4、handler,looper和线程的关系?

首先看发起消息的入口–Handler的源码,看他是如何被构造出来的:

默认构造:

// Handler.java

public Handler() {
    this(null, false);
}

public Handler(@Nullable 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 " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

整理后核心代码如下:

// Handler.java

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,Looper 持有一个 MessageQueue。最主要来看第一行代码 mLooper = Looper.myLooper();

// Looper.java

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

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

看到 ThreadLocal 马上就可以知道,Looper 是和线程相关的,同时也说明了一个线程中只会有一个 Looper 。

从注释中还能得到一个信息,如果要获取到一个非空的 Looper ,那么必须在这个线程中调用一次 Looper.prepare() 。从Looper.prepare()方法的源码,如下:

// Looper.java

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

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

可以看到,一个线程只能调用一次 Looper.prepare(), 否则就会报 RuntimeException 。

如果是第一次调用,会创建一个 Looper 放到 sThreadLocal 中。

在构造 Looper 的时候,创建了属于这个 Looper 的消息队列 MessageQueue 。

小结:

Handler 的消息最终在哪个线程中进行处理,与 Looper 有着直接的关联

从上面的分析知道,Looper 确实是和线程直接关联的。

5、为什么一个线程只能一个looper,你知道threadlocal吗?

ThreadLocal是用在多线程中,用于保存当前线程的上下文信息。在任意需要的地方都可以获取,在不同的线程中,通过同一个ThreadLocal获取到不同的对象。

原理如图:
在这里插入图片描述ThreadLocal的实现原理:在每个线程中使用ThreadLocalMap将键值对<ThreadLocal,Object>保存在使用线性探测法实现的hash表中(HashMap是链接法实现的hash表)。

这个Thread就是线程对象,每个线程只有一个,可以通过Thread.currentThread()方法拿到,也可以在new Thread()时拿到,不管什么方式拿到的都是同个对象,这个对象里面有一个哈希表,每一个Entry的key就是ThreadLocal的弱引用,而value就是set进来的局部对象。
可以看到这个哈希表是线程唯一的,而里面的每一个Entry,对应一个ThreadLocal对象,比如说我在线程A中用到了3个ThreadLocal,都设置了不同的局部对象,那么这个哈希表就有3个Entry对象。

ThreadLocalMap:
上面所说的线程Thread对象中的哈希表,其真实类型就是ThreadLocalMap,它其实是一个简化版哈希表,初始容量为16,当然,只有真的放东西Entry数组才会初始化,threshold为容量的2/3,超过了就触发扩容,扩容也是比较简单的,就是直接扩大为原容量的2倍,至于哈希冲突问题,如果冲突就采用线性探测的方法解决。

这里还有个问题,就是如何根据key确定放到哪个桶里,可以看下代码:

  int i = key.threadLocalHashCode & (len-1);

threadLocalHashCode在初始化时被赋值:

  public class ThreadLocal<T> {
  	......
  	private final int threadLocalHashCode = nextHashCode();

  	private static final int HASH_INCREMENT = 0x61c88647;

  	private static int nextHashCode() {
      	return nextHashCode.getAndAdd(HASH_INCREMENT);
  	}
  	......
  }

也就是说,这个threadLocalHashCode,就是0x61c88647的整数倍,然后跟len-1进行与操作,就得到了桶的下标,至于为什么是0x61c88647这个数值,网上有分析文章,有兴趣可以自己查看。

内存泄漏

根据上面的整体原理图,可以得到一条引用链:

  Thread对象 --> threadLocals --> Entry[] --> WeakReference<ThreadLocal<?>>和value

因为Thread对象的生命周期是比较长的,只有当线程退出后,Thread对象才会被回收,那么在线程退出前,我们的ThreadLocal被WeakReference包着,如果外部没有强引用,在内存不足时gc会自动回收了,但那个value就不会了,需要我们自己手动去清空引用。
因此这里存在一个内存泄漏的问题,如果某个局部对象使用ThreadLocal保存了,然后用完之后没有清除掉,线程又还没退出,就可能导致内存泄漏,当然解决的方法也很简单,调用remove方法就可以:

  sThreadLocal.remove();

另外,并不是说这种场景下我们不调用remove方法,value引用就一直不会被置空,ThreadLocal内部做了优化,在get和set时会主动置空那些key被gc自动回收的value引用,不过一般我们的ThreadLocal都是定义为static,这种情况就不可能会被gc自动回收了。

Netty的FastThreadLocal
如果同个线程用到了多个ThreadLocal,也就是Entry[]会有多个元素,那么每次在搜索某个ThreadLocal对应在哪个桶这个过程就会比较耗时,特别是对于服务端并发量很大的情况下,性能损耗会很明显,因此FastThreadLocal就做了优化,直接把ThreadLocal的哈希表去掉了,改为用变量index记录当前ThreadLocal对应的数组下标,也就是空间换时间,实现代码:FastThreadLocal.java,当然,在Android中对同个线程的ThreadLocal哈希表频繁操作这种场景可能并不多见。

在Looper.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));
    }
       private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

looper是在sThreadLocal.set中初始化的,我们看looper是如何和线程绑定的,看ThreadLocal的set函数的源码:

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

value就是looper,获取当前线程,然后获取当前线程对应的ThreadLocalMap ,我们发现Thread的源码里面有一个变量:

  ThreadLocal.ThreadLocalMap threadLocals = null;

也就是说每个线程都有一个ThreadLocalMap ,用来保存上下文,一个线程对应一个map。ThreadLocal根据版本不同有不同,但是原理是一样的,ThreadLocal本身作为key,都是在每个线程中使用ThreadLocalMap将键值对<ThreadLocal,Object>保存在使用线性探测法实现的hash表。

ThreadLocal和Looper是一一对应的关系,Looper中ThreadLocal对象的源码如下:

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

https://blog.csdn.net/wenyuan65/article/details/102633599

6、主线程为什么没有被loop阻塞

原回答地址:https://stackoverflow.com/questions/38818642/android-what-is-message-queue-native-poll-once-in-android
因为主线程负责绘制UI和处理各种事件,所以Runnable有一个处理所有这些事件的循环。循环由Looper管理,其工作非常简单:它处理MessageQueue中的所有消息。消息被添加到队列中,例如响应输入事件,帧渲染回调甚至您自己的Handler.post调用。有时主线程没有工作要做(即队列中没有消息),这可能发生在例如刚完成渲染单帧后(线程刚刚绘制了一帧并准备好下一帧,只需等待一段时间)。 MessageQueue类中的两个Java方法对我们来说很有趣:Message next()和boolean enqueueMessage(Message,long)。消息next(),顾名思义,接收并返回队列中的下一条消息。如果队列为空(并且没有任何内容可以返回),则该方法调用native void nativePollOnce(long,int),该块将阻塞,直到添加新消息。此时你可能会问nativePollOnce如何知道何时醒来。这是一个非常好的问题。将Message添加到队列时,框架会调用enqueueMessage方法,该方法不仅会将消息插入队列,还会调用native static void nativeWake(long),如果需要唤醒队列的话。 nativePollOnce和nativeWake的核心魔力发生在native(实际上是C ++)代码中。 Native MessageQueue使用名为epoll的Linux系统调用,该调用允许监视IO事件的文件描述符。 nativePollOnce在某个文件描述符上调用epoll_wait,而nativeWake写入描述符,这是IO操作之一,epoll_wait等待。然后内核从等待状态中取出epoll等待线程,并且线程继续处理新消息。如果您熟悉Java的Object.wait()和Object.notify()方法,您可以想象nativePollOnce是Object.wait()和NativeWake for Object.notify()的粗略等价物,因为它们的实现完全不同:nativePollOnce使用epoll,Object.wait()使用futex Linux调用。值得注意的是,nativePollOnce和Object.wait()都不会浪费CPU周期,因为当线程进入任一方法时,它会因线程调度而被禁用。如果这些方法实际上浪费了CPU周期,那么所有空闲应用程序将使用100%的CPU,加热并降低设备的速度。

循环的阻塞是通过NDK层,借助Linux的epoll实现阻塞等待功能。
拓:什么是epoll?
epoll 是 Linux 内核为处理大批量文件描述符而作了改进的 poll,是 Linux 下多路复用IO接口 select/poll 的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。epoll 是 Linux 中用来监听 IO 事件的工具。一个进入等待的句柄,一旦监听到事件发生,就会被唤醒,继续往下执行。

7、Handler 同步消息屏障?

同步屏障是用来阻挡同步消息执行的。在日常使用中,很少去关心 Handler 的消息是同步还是异步,这是因为默认的消息都是 同步消息 。来看看消息的同步和异步是怎么设置的。

通过Handler设置

// Handler.java

public Handler() {
    this(null, false);
}

public Handler(@Nullable 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 " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    
    // 设置为异步
    mAsynchronous = async;
}

可以看到用户可以创建的 Handler 都是同步的,是否同步会保在 mAsynchronous 中。

当然有一个系统可以用,但对开发者不可见的方法,是可以创建异步 Handler 的。

// Handler.java

@UnsupportedAppUsage
public Handler(boolean async) {
    this(null, async);
}

那么 mAsynchronous 是如何使用的呢?

来看看消息入队列的方法:

// Handler.java

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 的 mAsynchronous == true ,消息就会设置为 异步 。

所以,开发者是无法通过 Handler 来实现异步消息的。

但是可以直接通过消息设置呀~

通过消息体配置

其实就是上面的方法啦:

msg.setAsynchronous(true);

同步消息屏障如何运作

让我们回到消息队列获取可执行消息的地方,看代码标注【2】:

// MessageQueue.java

Message next() {
    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();
        }
        
        //【1】调用 NDK 层方法,线程阻塞挂起,进入等待
        // nextPollTimeoutMillis = -1 时,进入无限等待,直到有人唤醒
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //【2】判断队列是否插入了同步屏障,是则只执行异步消息
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //【3】消息没到时间,重新计算等待时间
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    //【4】消息时间已到,重新拼接链表,并返回该消息
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    
                    return msg;
                }
            } else {
                // 没有消息,进入无限等待
                nextPollTimeoutMillis = -1;
            }

            if (mQuitting) {
                dispose();
                return null;
            }

            // 省略无关代码...
        }

        // 省略无关代码...
    }
}

可以看到,当消息队列的头部插入了一个消息,并且这个消息的 target == null 的时候,就是启用了同步消息屏障。

这时候,会判断消息是否是同步的,如果是同步的则直接跳过,继续寻找队列中的异步消息。

换而言之,同步消息都被挡住了,只有异步消息可以执行。

如何启动/清除同步消息屏障

依然是在 MessageQueue 中:
/**
 * @hide
 */
public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

/**
 * @hide
 */
public void removeSyncBarrier(int token) {
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        final boolean needWake;
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();

        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}

很简单,同步消息屏障就是根据屏障启动的时间,插入到消息队列中对应的位置,和普通的消息压入是类似的。

你可能已经注意到了,这几个方法对开发者都是不可见的,都有 @hide 的标记,也就是说,开发者是不被允许启动同步消息屏障的。

同步消息屏障的作用
如果你看过 Android 系统 View 的绘制流程,应该会知道 View 的绘制也是通过 Handler 来驱动的。

如果在启动绘制之前,用户(开发者)插入了一个非常耗时的消息到队列中,那就会导致 UI 不能按时绘制,导致卡顿掉帧。

同步消息屏障就可以用来保证 UI 绘制的优先性。

当你请求绘制 View 的时候,最终调用是在系统的 ViewRootImpl 中,来看下相关的代码:

// ViewRootImpl.java

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        
        // 启动同步消息屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

先启动同步消息屏障,之后 mChoreographer 中的消息都采用了异步的方式,保证消息的流畅,最终会回调 mTraversalRunnable 。最后在绘制时,解除同步消息屏障,详见以下代码:

// ViewRootImpl.java

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        
        // 清除同步消息屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        
        // 启动绘制流程
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

八、一个线程有几个Handler?

只要内存够用,想搞几个搞几个啊。

九、Handler内存泄漏原因?

如果直接在Activity中初始化一个Handler对象,会报如下错误:
在这里插入图片描述
在Java中,非静态内部类会持有一个外部类的隐式引用,可能会造成外部类无法被GC;
比如这里的Handler,就是非静态内部类,它会持有Activity的引用从而导致Activity无法正常释放。

而单单使用静态内部类,Handler就不能调用Activity里的非静态方法了,所以加上「弱引用」持有外部Activity。

private static class MyHandler extends Handler {
    //创建一个弱引用持有外部类的对象
    private final WeakReference<MainActivity> content;

    private MyHandler(MainActivity content) {
        this.content = new WeakReference<MainActivity>(content);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        MainActivity activity= content.get();
        if (activity != null) {
            switch (msg.what) {
                case 0: {
                    activity.notifyUI();
                }
            }
        }
    }
}

转换成Kotlin:(Tips:Kotlin 中的内部类,默认是静态内部类,使用inner修饰才为非静态)

private class MyHandler(content: MainActivity) : Handler() {
    //创建一个弱引用持有外部类的对象
    private val content: WeakReference<MainActivity> = WeakReference(content)

    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        val activity = content.get()
        if (activity != null) {
            when (msg.what) {
                0 -> {
                    activity.notifyUI()
                }
            }
        }
    }
}

十、为什么主线程可以直接new Handler?

ActivityThread.java 的main()源码如下:

   public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

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

已经执行了 Looper.prepareMainLooper(); Looper.loop(); ,也就是说系统已经帮我们完成了Looper的初始化操作。

十一、子线程中维护的Looper,消息队列无消息的时候处理方案是什么?有什么用?

根据MessageQueue.java的源码分析:

///5、如果是-1,表示无限等待,直到有事件发生为止。如果值为0,无需等待,立即返回。
///nativePollOnce会调用linux函数,最终会触发epoll机制,epoll机制中,在native层级也会有一个messageQueue,native层级的messageQueue调用epoll函数,根据传的timeoutMillis值(也就是之前几步中的nextPollTimeoutMillis)进行变化。
 private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
    private native static void nativeWake(long ptr);
   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();
            }
            **///4、睡眠,也就是线程挂起了。第三步开始新一轮循环之后,nextPollTimeoutMillis此时为-1**
            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.
                    ///2、如果没有消息了,nextPollTimeoutMillis 设为-1
                    nextPollTimeoutMillis = -1;
                }

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

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                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;
                    ///3、nextPollTimeoutMillis此时=-1(第二步设置的),continue跳出此次循环,开始下一次循环
                    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;
        }
    }

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

        synchronized (this) {
        ///9、让Looper.loop()中的msg为空
            if (mQuitting) {
                return;
            }
            ///10、mQuitting = true;
            mQuitting = true;
///6、在调用looper的quit的时候,先把消息都recycleUnchecked()
            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            ///7、唤醒线程
            nativeWake(mPtr);
        }
    }
  private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

源码中注解的5步骤解释了挂起过程。looper如何退出呢?在阻塞状态想停掉,首先要唤醒,在调用looper的quit的时候,先把消息都recycleUnchecked(),然后 nativeWake(mPtr)唤醒线程。
如果想退出for循环,一定是Looper中loop的msg返回空,源码如下:

///8、想退出for循环,一定是Looper中loop的msg返回空
 public static void loop() {
     ///省略代码
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
 ///省略代码
          }

怎么让msg为空?一定要走MessageQueue中的next中的

 if (mQuitting) {
                    dispose();
                    return null;
                }

同上面代码的批注9,此时看到批注10设置了mQuitting = true; ,那么这里返回的就是null,Looper.loop循环中的msg是null,也就直接return了。

子线程释放了资源,其他地方也可以调用子线程了。主线程能释放吗?肯定不能释放,因为,看MessageQueue.java的quit源码如下:

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

Main thread not allowed to quit. 主线程不允许退出,会抛出异常。这么设计的原因,看ActivityThread.java的handleMessage源码,看到Android是handler作为驱动的,AMS最终是用handler进行管理的,围绕handler来的,所以不可能被替换。当然四大组件的底层是binder。

十三、多个Handler往MessageQueue中添加数据,发消息时各个Handler可能处于不同线程,内部是如何确保线程安全的?

因为加了锁,看MessageQueue.java中的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;
    }

synchronized (this) ,synchronized 是一个内置锁,因为加锁和解锁都是由JVM完成的,
这里的this代表对一个线程的messageQueue访问的时候,其他的对象不能访问。

十三、使用Message时如何创建它?

涉及到享元设计模式,obtain,先看一下MessageQueue.quit中remove消息的源码过程如下:

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

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;
///1、remove   message
            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
    
    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            //2、message进行了recycleUnchecked()
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }
    //3、并不是真的将消息干掉,而是将消息里的内容都去掉。message是有一个内存块,里面的内容处理掉之后,放到另一个消息链表。
 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 = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;
  ///4、可以看到是头插,每生成一个节点放到头部
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

以上四步解释了MessageQueue.quit中remove消息的过程,下面看一下Message.obtain源码:

 public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

从消息池取出一个消息,放到需要用到的链表,没有了创建和销毁的过程,避免内存抖动。

14、IdleHandler?

Q:IdleHandler 有什么用?
简单来说,IdleHandler 是 Handler 中提供的一种可以在 Looper 事件循环的过程中,MessageQueue 出现空闲的时候,允许我们执行一些任务的机制。
Q:IdleHandler 如何使用?
1、IdleHandler 被定义在 MessageQueue 中,它是一个接口,需要实现 queueIdle() 方法,返回值的含义是 keep,表示是否是一个持续的回调。keep 为 false 时,这个 IdleHandler 在执行一遍后就会被移除。

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

2、IdleHandler 被 MessageQueue 管理,对应的提供了 addIdleHandler() 和 removeIdleHandler() 方法。将其存入 mIdleHandlers 这个 ArrayList 中。

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

Q:IdleHandler 的 queueIdle() 什么时候被调用?

队列出现空闲的 2 种场景:

1、 MessageQueue 为空,没有 Message;
2、MessageQueue 中最近待处理的 Message,是一个延迟消息(when>currentTime),需要滞后执行;

Q:当 mIdleHandlers 不为空时,为什么不会进入死循环?

1、关键在于 pendingIdleHanderCount 的值。
2、在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;
3、pendingIdleHandlerCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;

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

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
          ///...
          ///step2
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            ///step3
            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);
            ///...
        }
///step4
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

Q:IdleHandler 适合做什么?

1、适合执行一些不重要的任务,说白了就是对执行时机没有那么高要求的任务;
2、因为 IdleHandler 只有在事件循环空闲时才会执行,所以它处理任务的时机,是不可控的。

详细请看:
https://mp.weixin.qq.com/s/mR7XIVbaKsB4q-Rxe1ip2g

15、post()与sendMessage()区别

1、post和sendMessage功能其实差不多,post其实也是通过sendMessage来实现的,都是发送消息到Handler所在的线程的消息队列中
2、post的用法更方便,经常会post一个Runnable,处理的代码直接写在Runnable的run方法中,其实就是将这个Runnable发送到Handler所在线程(一般是主线程)的消息队列中。sendMessage方法主线程处理方法一般则是写在handleMessage中。代码示例如下:
post

final Handler handler = new Handler();
new Thread(new Runnable() {
            @Override
            public void run() {
                final String response = get(url);
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        //doSomeThing
                    }
                });
            }
        }).start();

sendMessage

final Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 1) {
                    //doSomeThing
                }
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                msg.what = 1;
                handler.sendMessage(msg);
            }
        });

view.post和handler.post区别:

view.post其实内部是获取到了view所在线程(即ui线程)的handler,并且调用了handler的post方法

16、IntentService是什么?

IntentService是google在原生的Service基础上通过创建子线程的Service。也就是说IntentService是专门为android开发者提供的能在service内部实现耗时操作的service。我们可以通过重写onHandleIntent方法实现耗时操作的回调处理,而且IntentService在耗时操作完成后,会主动销毁自己,IntentService可以通过多次启动来完成多个任务,而IntentService只会被创建一次,每次启动的时候只会触发onStart方法。内部是实现了Handler异步处理耗时操作的过程,一般多用在Service中需要处理耗时操作的功能。
提问:为什么IntentService中能实现耗时操作?

在onCreate中,通过HandlerThread来开启一条线程,而HandlerThread线程中会跟我们平常用的Handler不太一样,在run方法中创建了looper对象,所以HandlerThread能让IntentService在子线程中使用handler达到耗时操作。

17、子线程中有几行代码需要在主线程执行,有几种方法?Android中线程按功能分的话,可以分为两个,一个是主线程(UI线程),其他的都是子线程

主线程不能执行那些耗时过长的代码或任务(执行耗时过长的代码会出现应用未响应的提示),所以都是使用子线程来执行耗时过长的代码,比如说下载文件等任务

一般情况,子线程中执行过长的代码,都是需要进行更新UI操作。

但是Android中,为了防止安全,是不允许在子线程更新UI的,但是我们可以使用到Android官方给予的API来实现子线程更新UI的操作(本质上,这些API也是切换回了主线程来进行更新UI)

例子:点击一个按钮,过了1s后完成了下载任务,返回了数据,此数据会显示在界面上

在这里插入图片描述
具体解释:

点击按钮,之后开启一个子线程来模拟下载过程(线程休眠1s),之后任务执行完毕会返回数据(一个String),使用返回的数据更新UI

新建一个方法,用来模拟下载任务

/**
 * 模拟下载
 */
fun download(): String {
	Thread.sleep(1000)
	return "this is data"
}

下面的使用6种方式和上面的模拟下载任务的方法,来实现上面例子的效果

1.Activity.runOnUiThread()
runOnUiThread是Activity中的方法,只有当前对象是Activity,就可以直接使用,如果当前的对象不是Activity,需要找到Activity对象,才能执行此方法

runOnUiThread方法的参数为一个Runnable接口,我使用的kotlin,所以有很多东西都是省略了

设置按钮的点击事件,点击按钮开启一个线程

btn_start.setOnClickListener {
	thread {
		val data = download()
		runOnUiThread({
			//这里进行更新UI操作
			tv_show.text = data
		})
	}
}

Java版

btn_start.setOnClickListener(new OnClickListener(){
	new Thread(new Runnable(){
		String data = download();
		runOnUiThread(new Runnable(){
			@Override
			public void run() {
				tv_show.setText(data);
			}
		})
	}).start();
});

2.View.post()
post方法是View对象的方法,参数也是接收一个runnable接口

这里我选用的view对象是需要进行更新textview的本身,当然也可以选用其他的View对象,只要是在当前Activity的对象都可以

btn_start.setOnClickListener {
	thread {
		val data = download()
		//选择当前Activity的View对象都可以
		tv_show.post { 
			tv_show.text = data 
		}
	}
}

3.View.PostDelayed()

此方法和上面的post方法类似,只是多一个参数,用来实现延迟更新UI的效果

btn_start.setOnClickListener {
	thread {
		val data = download()
		tv_show.postDelayed({
			tv_show.text = data
		},2000)
	}
}

上面的代码实现的效果是点击按钮之后,过了3s后才会看到界面发生改变

4.Handler.post()

new一个Handler对象(全局变量)

private val handler = Handler()

使用post方法更新UI,此post方法和之前的post方法一样,参数都是为Runnable接口

btn_start.setOnClickListener {
	thread {
		val data = download()
		handler.post {
			tv_show.text = data
		}
	}
}

5.AsyncTask(推荐)

说明

AsyncTask是一个抽象类,必须创建一个子类类继承它

这里介绍一下关于AsyncTask的三个泛型参数和几个方法

泛型参数可以为任意类型,为空的话使用Void

在这里插入图片描述抽象方法说明:

在这里插入图片描述简单来说,如果子类继承了AsyncTask,它的抽象方法的参数都会变成泛型对应的类型

例子

下面的代码是取自我的APP,简单地说明一下AsyncTask<String, DownloadingItem, DownloadedItem>

我传入的是3个泛型参数分别为String,DownloadingItem,DownloadedItem,分别对应的params,progress和result泛型

这里我是根据自己的需要而两个类DownloadingItem和DownloadedItem,从下面的代码可以看到,抽象方法的参数变为了我们的泛型的类型

internal inner class DownloadingTask : AsyncTask<String, DownloadingItem, DownloadedItem>() {

	override fun onPreExecute() {
		//一些初始化操作
	}

	override fun doInBackground(vararg params: String?): DownloadedItem {
		//params是一个参数数组,如果创建DownloadingTask对象只传入了一个参数,直接取下标为0的那个即可(需要转型)
		//耗时操作(如下载操作),获得进度数据
		
		//将新的进度数据传递到onProgressUpdate方法,更新UI
		publishProgress(messageItem)
		
		//任务执行完毕,返回结果(回调onPostExecute方法)
	}

	override fun onProgressUpdate(vararg values: DownloadingItem?) {
		//这里使用最新的进度来进行相关UI的更新
		//values是一个DownloadingItem数组,取末尾那个即为最新的进度数据
	}

	override fun onPostExecute(result: DownloadedItem?) {
		//下载成功提示或者是其他更新UI的操作
	}
}

执行:
执行Task的时候需要在主线程(UI线程调用)

DownloadingTask().execute("参数")

批量下载:

//允许在同一时刻有5个任务正在执行,并且最多能够存储50个任务
private val exec = ThreadPoolExecutor(5, 50, 10, TimeUnit.SECONDS, LinkedBlockingQueue<Runnable>())
DownloadingTask().executeOnExecutor(exec, url)

6.Handler机制实现(核心)
其实,Handler机制是子进程更新UI的核心
我们上面的五种实现子进程更新UI的方式,都是基于Handler机制实现的
在这里插入图片描述具体机制本文就不多说了,网上有许多的机制说明,这里就只讲一下实现的步骤

Message中有几个属性,what,arg1,arg2,这三个都是接收一个Int,所以,传递数据不是很友好,这里就不准备实现之前的例子效果了

what表示来源,arg1和arg2用来传递Int数据

1.重写Handler类中的handleMessage方法

一般都是规定好一个Int的常量,来表示what

private val handler =object : Handler(){
	override fun handleMessage(msg: Message?) {
		if (msg.what == 1) {
			//来源为1,则
		}
	}
}

2.发送Message

val msg = handler.obtainMessage()
//一般都是规定好一个Int的常量,来表示what
msg.what = 1
//传递Int数据
msg.arg1 = 20
handler.sendMessage(msg)

HandlerThread
HandlerThread本身也是Thread,只是在Thread基础上封装上了Handler的载体,并且在run方法中创建了looper对象,这也是为什么在IntentService中能在HandlerThread中直接用handler的原因。而我们知道一个线程是可以有多个handler,所以用HandlerThread更加方便我们不用关心Handler的创建,一般用在多线程中直接处理任务。

四个主要的类handler、message、looper、messagequeue和thread
https://www.jianshu.com/p/43e21be8d849
Looper的工作流程:

ActivityThread.main();//初始化入口
    1. Looper.prepareMainLooper(); //初始化
          Looper.prepare(false); //设置不可关闭
              Looper.sThreadLocal.set(new Looper(quitAllowed)); //跟线程绑定
                    1.1.Looper.mQueue = new MessageQueue(quitAllowed); //Looper和MessageQueue绑定
                    1.2.Looper.mThread = Thread.currentThread();
    2. Looper.loop();
        2.1.myLooper().mQueue.next(); //循环获取MessageQueue中的消息
              nativePollOnce(); //阻塞队列
                  native -> pollInner() //底层阻塞实现
                        native -> epoll_wait();
        2.2.Handler.dispatchMessage(msg);//消息分发

myLooper().mQueue.next()实现原理
通过myLooper().mQueue.next() 循环获取MessageQueue中的消息,如遇到同步屏障 则优先处理异步消息.

同步屏障即为用Message.postSyncBarrier()发送的消息,该消息的target没有绑定Handler。在Hnandler中异步消息优先级高于同步消息。

可通过创建new Handler(true)发送异步消息。ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保证UI绘制优先执行。
Handler.dispatchMessage(msg)实现原理

    优先回调msg.callback。
    其次回调handler构造函数中的callback。
    最后回调handler handleMessage()。

Hander发送消息的流程

1.Handler handler = new Handler();//初始化Handler
        1.Handler.mLooper = Looper.myLooper();//获取当前线程Looper。
        2.Handler.mQueue = mLooper.mQueue;//获取Looper绑定的MessageQueue对象。

2.handler.post(Runnable);//发送消息
        sendMessageDelayed(Message msg, long delayMillis);
            sendMessageAtTime(Message msg, long uptimeMillis);
                Handler.enqueueMessage();//Message.target 赋值为this。
                    Handler.mQueue.enqueueMessage();//添加消息到MessageQueue。

MessageQueue.enqueueMessage()方法实现原理

如果消息队列被放弃,则抛出异常。

如果当前插入消息是即时消息,则将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,并通过needWake唤醒Looper线程。

如果消息为异步消息则通过Message.when长短插入到队列对应位置,不唤醒Looper线程。

Handler的机制:Handler通过执行其绑定线程的消息队列(MessageQueue)中不断被Looper循环取出的消息(Message)来完成线程间的通信。

一个线程可以多个handle,只能有一个looper,怎么保证只有一个looper,用threadLocal

附:
https://blog.csdn.net/qian520ao/article/details/78262289#1-looper-%E6%AD%BB%E5%BE%AA%E7%8E%AF%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E4%BC%9A%E5%AF%BC%E8%87%B4%E5%BA%94%E7%94%A8%E5%8D%A1%E6%AD%BB?tdsourcetag=s_pcqq_aiomsg

https://blog.csdn.net/cpcpcp123/article/details/104136413

https://www.jianshu.com/p/53e740d6d8f0

https://blog.csdn.net/coder_pig/article/details/106197731

https://mp.weixin.qq.com/s?__biz=MzA5ODQ1ODU5NA==&mid=2247483775&idx=1&sn=f6fb944f29165379d5d74d56838cdd72&chksm=90900ff2a7e786e4d864b3d7a9f9f39a8fee178744cc4cf31b8037e8f2d4ce975f1c42251b5e&token=1509135634&lang=zh_CN#rd

https://www.cnblogs.com/stars-one/p/11666803.html

18、handler延时操作

https://blog.csdn.net/qq_43556200/article/details/110478562

     Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                dealSpeechAction(intent);
            }
        }, 500);

19、Android消息机制解析(native层)能说一下吗?为什么java层的loop方法是死循环但却不会消耗性能

https://blog.csdn.net/Rain_9155/article/details/87031308

20、调用sendMessageDelayed(xxx, 5秒)发送了一个Message, 这个5s 是怎么实现的?

从队列取出message的时候,要进行时间控制, 在MessageQueue的Message next()中有这句 nativePollOnce(ptr, nextPollTimeoutMillis)。 那么延伸一下,此时有一个不延时的消息进来了,nativewake了,这个五秒怎么继续计时?假如messageA 延时5秒,messageB不延时, B必然排在A前边,B插入到队列后,会唤醒阻塞,于是就把B从队列取出,执行,然后遍历下一个元素,就是A了,假如B的处理函数耗时3秒, 则重新计算阻塞时间 5s-3s = 2s, 继续调用 nativePollOnce(ptr, 2秒);在这里插入图片描述
https://juejin.cn/post/7020060105773154312#heading-11
内存泄漏:https://mp.weixin.qq.com/s/MwDlFsrH0LbohmrLZyGoEA

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值