Handler消息机制

1.handler设计的初衷

1.1Java多线程

Java多线程通信是通过synchronized关键字以“上锁”机制实现线程间通信,多个线程持
有同一个对象,他们可以访问同一个共享变量,利用 synchronized“上锁”
机制,哪个线程拿到了锁,它就可以对共享变量进行修改;
或者Object 类的 wait/notify 机制,执行代码 obj.wait();后这个对象
obj 所在的线程进入阻塞状态,直到其他线程调用了 obj.notify();方法
后线程才会被唤醒。
两个 Java 多线程通信的方法中都有一个共同的特点,那就是线程的阻
塞。

1.2Android 多线程的特殊性

由于其特殊性 Android 系统强制要求以下两点:
1.为保持用户界面流畅 UI 线程不能被阻塞,如果线程阻塞界面会卡死,若
干秒后 Android 系统抛出 ANR。
2.除 UI 线程外其他线程不可执行 UI 操作。
既然 UI 线程中不能被阻塞,那么查询数据库和访问网络这类的耗时操作肯定就
不能在 UI 线程中执行了,我们就需要单独开个线程操作。
但是除 UI 线程外其他线程又不可执行 UI 操作,最后还是要回到 UI 线程更新 UI,
这就需要多线程之间的通信。
可 Java 中线程间通信又都是阻塞式方法,所以传统的 Java 多线程通信方式在
Android 中并不适用。

1.3Android 多线程通信

Google 开发人员设计一套 UI 线程与 Worker 线程通信的方法。既
能实现多线程之间的通信,又能完美解决 UI 线程不能被阻塞的问题。具体方法
有以下几类:
1.view.post(Runnable action)系列,通过 View 对象引用切换回 UI 线程。
2.activity.runOnUiThread(Runnable action),通过 Activity 对象引用切换
回 UI 线程。
3.AsyncTask,内部封装了 UI 线程与 Worker 线程切换的操作。
4.Handler,异步消息处理机制,多线程通信。

2.Handler源码分析

2.0Handler机制相关类

Hanlder:发送和接收消息
Looper:用于轮询消息队列,一个线程只能有一个Looper。为了实现消息接收端只存在线程内部私有化
Message:消息实体,让传输数据比较规范化
MessageQueue:消息队列用于存储消息和管理消息。多线程同时发送数据 Looper 分发消息处理时会产生的问题

Handler 是线程间共享的而 Looper 是线程独立的,MessageQueue 跟 Looper 又是一对一的

2.1Message源码分析

Message作为Handler线程间通信的载体,实现了Parcelable序列化;
利用了 Java 的高级语言特性,即类中持有着一个类自身的属性作为经典数据结构中的链表 next 指针,以静态属性属于类本身的特性实 现了链表的表头。

    Message next;
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;

Message创建方式一:Message message = new Message()

   /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message() {
    }

构造方法里什么也没有,不过它的注释却告诉我们想要得到 Message 对象首选的方法应该是调用静态方法 Message.obtain();

   public static Message obtain() {
        synchronized (sPoolSync) { //sPoolSync是一个Object对象,用来同步保证线程安全
            if (sPool != null) {//当前链表缓存池的入口,链表结构的head
              //    声明一个  Message  引用指向缓存池中的  pool  对象
                Message m = sPool;
         // 让缓存池中  spool  引用指向它的  next  引用的对象
                sPool = m.next;
              //    因为该对象已经从缓存池中被取出,所以将  next  指针置空
                m.next = null; 
                m.flags = 0; // clear in-use flag
                sPoolSize--;//缓存池数量减一
                return m;
            }
        }
        return new Message();
    }

Message内部维持了一个链表形式的Meesage 对象缓存池,这样会节省重复实例化对象产生的开销成本。
数据结构中的链表结构:一个单元有两个值,当前单元的值 (head)和下一个单元的地址指针(next),如果下一个单元不存在那么 next 就是 null 的


链表结构
从缓存池中取对象

看完上述源码,有个很大的疑惑就是缓存池是怎么添加数据的呢?
其实在Loop.loop()循环取消息的时候,MessageQueue.removeMessages移除消息的时候都会调用Message.recycleUnchecked()

void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
       ......此处省略清除当前  Message  对象的所有数据属性
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
              //    存入缓存池
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

理论上我们希望 sPool 引用指向了链表缓存池中的第一个对象,让它作为整个缓 存池出入口。所以我们把它设置成 static 的,这样它就与实例化出来的对象无关,也就是说无论我们在哪个 Message 对象中进行操作,sPool 还是 sPool。

包含 Handler 参数的 obtain()方法

实 际上是先把消息传 入了 MessageQueue 中 ,Looper 再 从 MessageQueue 依 次取 出 消息分发给 Handler。
Looper 是线程独立的,Looper 和 MessageQueue 是一对一的。但是,你有没有想过 Looper 和 Handler 是不是一对一的?答案当然是否定的, MessageQueue 只负责队列消息,Looper 只负责取出消息分发。他们的功能很明 确而且通用。所以,无论当前线程有多少个 Handler,同样都只有一个 Lopper 和一个 MessageQueue。
既然每个线程只有一个 Looper 和 MessageQueue 的话那么 Looper 分发消息的时
候要如何判断当前这个 Message 是哪个 Handler 的呢?所以开发人员就给 Message 内部配置了一个 Handler 属性,这样 Looper 分发消息时直接调用 Messgae 内部的 Handler 属性就能找到它对应的 handleMessage(Message msg)接收消息的方法了。

2.2MessageQueue 的源码分析

MessageQueue 属于底层类且依附于 Looper,Looper 外其他类不应该单独创建它, 如果想使用 MessageQueue 可以从 Looper 类中得到它。
从Handler源码分析所有的sendMessage最后都会走到queue.enqueueMessage(msg, uptimeMillis);所以我们先从MessageQueue 的enqueueMessage方法开始分析:

//消息入队
 boolean enqueueMessage(Message msg, long when) {
//    如果此消息的  target 也就是宿主  handler  是空的抛异常
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //   如果此消息是  in-use 状态抛异常,in-use  的消息不可拿来使用
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
         //    如果当前  MessageQueue  已经退出了抛异常并释放掉此消息
            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;
            }
           //    将消息标记为  in-use  状态
            msg.markInUse();
           //设置消息处理时间
            msg.when = when;
           //    拿到队列头
            Message p = mMessages;
          //    是否需要唤醒线程
            boolean needWake;
            // p 等于空说明队列是空的
            //when 等于  0  表示强制把此消息插入队列头部,最先处理
           // when 小于队列头的  when  说明此消息应该被处理的时间比队列中第一个要处 
             //理的时间还早
          //    以上情况满足任意一种直接将消息插入队列头部
            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;
    }

总结一下消息入队的逻辑大致分为如下几步:
检查消息合法性,包括宿主target 是否为空,是否为 in-use 状态,队列是否还存活。
如果满足条件【队列为空、when 等于 0、此消息应被处理的时间比队列 中第一个要处理的时间还早】中的任意一个直接将此消息插在队列头部最先被处理。
如果以上三个条件均不满足,那么就从头遍历队列根据被处理时间找到它的位置。

同步消息拦截器

除了 enqueueMessage()方法可以向队列中添加消息外,还有一个 postSyncBarrier()方法也可以向队列添加消息,但它不是添加普通的消息,我 们将它添加的特殊 Message称为同步消息拦截器。
消息拦截器与普通消息的差异在于拦截器的 target 是空的,正常我们通过
enqueueMessage()方法入队的消息由于限制 target 是不能为空的。

//   标识拦截器的  token
private    int    mNextBarrierToken;
private    int    postSyncBarrier(long    when)    {
synchronized (this)    { 
//    得到拦截器  token
final int    token =    mNextBarrierToken++; 
//    实例化一个消息对象
final Message    msg =    Message.obtain(); 
//    将对象设置为  in-use  状态
msg.markInUse(); 
//    设置时间
msg.when =    when;
//    将  token  存于消息的常用属性  arg1  中 
msg.arg1 =    token;
Message    prev =    null; 
Message    p =    mMessages;
//    如果  when  不等于  0  就在队列中按时间找到它的位置 
if    (when !=    0)    {
while    (p !=    null    &&    p.when <=    when)    {
prev =    p; 
p =    p.next;
} 
}
// 如果 prev 不等于空就把拦截器插入 
// 如果 prev 等于空直接插入队列头部
if (prev != null) { // invariant: p == prev.next
msg.next =    p; 
prev.next =    msg;
}    else    {
msg.next =    p; 
mMessages =    msg;
}
//    拦截器入队成功,返回对应  token 
return    token;
} 
}

总体来说添加拦截器的方法跟正常消息入队差不多,值得一提的就是 Message 的 target 是空的,然后 arg1 保存着拦截器的唯一标识 token。token 的作用是找到对应的拦截器删除

队列空闲处理器 IdleHandler

由于在从队列中取出消息时队里可能是空的,这时候就会阻塞线程等待消息到来。每次队列中没有消息而进入的阻塞状态,我们叫它为“空闲状态”。
讲道理实际使用中队列空闲状态的情况还是很常见的,为了更好的利用资源,也为了更好的掌握线程的状态,开发人员就设计了这么一个“队列空闲处理器"IdleHandler”。
IdleHandler 是 MessageQueue 类下的一个子接口,只包含了一个方法:

public    static    interface    IdleHandler    { 
/**
*    当线程的  MessageQueue  等待更多消息时会调用该方法。 
*    返回值:true  代表只执行一次,false  代表会一直执行它 
*/
boolean queueIdle(); 
}

消息出队管理 next()方法

Message    next()    {
//    mPtr  是从  native  方法中得到的  NativeMessageQueue  地址 
//   MessageQueue(boolean quitAllowed) {
// mQuitAllowed = quitAllowed;
//    mPtr = nativeInit();
//  }
//源码中在创建的时候获取
//    如果  mPtr  等于  0  说明队列不存在或被清除掉了
final    long    ptr =    mPtr; 
if    (ptr ==    0)    {
return    null; 
}
//    线程将被阻塞的时间 
//    -1:一直阻塞
//    0:不阻塞
//    >0:阻塞  nextPollTimeoutMillis    毫秒 
int    nextPollTimeoutMillis =    0;
//    开始死循环,下面的代码都是在循环中,贼长! 
for    (;;)    {
//    如果  nextPollTimeoutMillis    不等于  0  说明要阻塞线程了 
if    (nextPollTimeoutMillis !=    0)    {
//    为即将长时间阻塞做准备把该释放的对象都释放了 
Binder.flushPendingCommands();
}
//    阻塞线程操作
nativePollOnce(ptr,    nextPollTimeoutMillis); 
synchronized    (this)    {
//    得到当前时间
final    long    now =    SystemClock.uptimeMillis(); 
Message    prevMsg =    null;
Message    msg =    mMessages;
//    判断队列头是不是同步拦截器
if    (msg !=    null    &&    msg.target ==    null)    { 
//    如果是拦截器就向后找一个异步消息 
do    {
prevMsg =    msg; 
msg =    msg.next;
}    while    (msg !=    null    &&    !msg.isAsynchronous()); 
}
//    判断队列是否有可以取出的消息 
if    (msg !=    null)    {
if    (now <    msg.when)    {
//    如果待取出的消息还没有到应该被处理的时间就让线
程阻塞到应该被处理的时间
nextPollTimeoutMillis = (int) Math.min(msg.when -now,  Integer.MAX_VALUE);
}    else    {
//    直接就能取出消息,所以不用阻塞线程 
mBlocked =    false;
将消息从队列中剥离出来
if    (prevMsg !=    null)    {
prevMsg.next =    msg.next; 
}    else    {
mMessages =    msg.next; 
}
//    让消息脱离队列 
msg.next =    null;
if  (DEBUG)  Log.v(TAG,  "Returning   message:   "  +  msg); 
//    设置为  in-use  状态
msg.markInUse();
//    返回取出的消息,结束循环,结束  next()方法 
return    msg;
}
}    else    {
//    队列中没有可取出的消息,nextPollTimeoutMillis等于-1 让线程一直阻塞
nextPollTimeoutMillis =-1;
}
//    如果队列已经退出了直接注销和结束方法 
if    (mQuitting)    {
dispose(); 
return    null;
}
// IdleHandler 初始化为-1,所以在本循环中该条件成立的次数 
if (pendingIdleHandlerCount < 0 && (mMessages == null ||
now <    mMessages.when))    {
//    得到  IdleHandler  的数量
pendingIdleHandlerCount =    mIdleHandlers.size(); 
}
//    pendingIdleHandlerCount    小于或等于  0  说明既没有合适的
消息也没有合适的闲时处理
if    (pendingIdleHandlerCount <=    0)    { 
//    直接进入下次循环阻塞线程
mBlocked =    true; 
continue;
}
//    代码执行到此处就说明线程中有待处理的  IdleHandler
//    那么就从 IdleHandler 集合列表中取出待处理的 IdleHandler 
if    (mPendingIdleHandlers ==    null)    {
//    初始化待处理  IdleHandler  数组,最小长度为  4 
mPendingIdleHandlers =    new
IdleHandler[Math.max(pendingIdleHandlerCount,    4)];
}
//    从  IdleHandler  集合中获取待处理的  IdleHandler 
mPendingIdleHandlers =
mIdleHandlers.toArray(mPendingIdleHandlers);
}
//    ==========到此处同步代码块已经结束========== 
for    (int    i =    0;    i <    pendingIdleHandlerCount;    i++)    {
//    取出一个  IdleHandler
final    IdleHandler    idler =    mPendingIdleHandlers[i]; 
//    释放掉引用
mPendingIdleHandlers[i]    =    null;
//    IdleHandler  的执行模式,true=执行一次,false=总是执行 
boolean    keep =    false;
try    {
//    执行  IdleHandler  的  queueIdle()代码,得到执行模式 
keep =    idler.queueIdle();
}    catch    (Throwable    t)    {
Log.wtf(TAG,   "IdleHandler    threw    exception",    t); 
}
//    通过执行模式判断是否需要移除掉对应的  IdleHandler 
if    (!keep)    {
synchronized    (this)    { 
mIdleHandlers.remove(idler);
} 
}
}
//    处理完了所有  IdleHandler  把数量清  0 ,pendingIdleHandlerCount =    0;
//    因为执行了  IdleHandler  的代码块,有可能已经有新的消息入队 
//    所以到这里就不阻塞线程,直接去查看有没有新消息
nextPollTimeoutMillis =    0;
} 
}

消息出队的核心代码的逻辑都在一个庞大的死循环 for(;;)中,其流程如下:
0,循环开始。
1,根据 nextPollTimeoutMillis 值阻塞线程,初始值为 0:不阻塞线程。
2,将【待取出消息指针】指向队列头。
3,如果队列头是同步拦截器的话就将【待取出消息指针】指向队列头后面最近
的一个异步消息。
4,如果【待取出消息指针】不可用(msg == null)说明队列中没有可取出的消
息,让 nextPollTimeoutMillis 等于-1 让线程一直阻塞,等待新消息到来时唤醒它。
5,如果【待取出消息指针】可用(msg != null)再判断一下消息的待处理时间。
如果消息的待处理时间大于当前时间(now < msg.when)说明当前消息还
没到要处理的时间,让线程阻塞到消息待处理的指定时间。
如果消息的待处理时间小于当前时间(now > msg.when)就直接从队列中
取出消息返回给调用处。(此处会直接结束整个循环,结束 next()方法。)
6,如果队列已经退出了直接结束 next()方法。
7,如果是第一次循环就初始化 IdleHandler 数量的局部变量
pendingIdleHandlerCount 。
8,如果 IdleHandler 数量小于等于 0 说明没有合适的 IdleHandler,直接进入下次
循环阻塞线程。(此处会直接结束本次循环。)
9,初始化 IdleHandler 数组,里面保存着本地待处理的 IdleHandler。
10,遍历 IdleHandler 数组,执行对应的 queueIdle()方法。
11,执行完所有 IdleHandler 之后,将 IdleHandler 数量清 0。
12,因为执行了 IdleHandler 的代码块,有可能已经有新的消息入队了, 所以让
nextPollTimeoutMillis 等于 0 不阻塞线程,直接去查看有没有新消息。
13,本次循环结束,开始新一轮循环。

2.3 Looper 的源码分析

Looper 的职责很单一,就是单纯的从 MessageQueue 中取出消息分发给消息对应
的宿主 Handler。
Looper 是线程独立的且每个线程只能存在一个 Looper。
Looper 会根据自己的存活情况来创建和退出属于它自己的 MessageQueue。

2.3.1创建Looper

Looper 是线程独立的且每个线程只能存在一个 Looper。所 以构造 Looper 实例的方法类似于单例模式。隐藏构造方法,对外提供了两个指定 的获取实例方法 prepare()和prepareMainLooper()。

// 应用主线程(UI 线程)Looper 实例 
private static Looper sMainLooper;
//   Worker  线程  Looper  实例,用  ThreadLocal  保存的对象都是线程独立的 
static    final    ThreadLocal    sThreadLocal =    new
ThreadLocal();
//   与当前  Looper  对应的消息队列 
final    MessageQueue    mQueue;
//   当前  Looper  所在的线程 
final    Thread    mThread;
/**
*    对外公开初始化方法 
*    在普通线程中初始化  Looper  调用此方法 
*/
public    static    void    prepare()    { 
//    初始化一个可以退出的  Looper 
prepare(true);
} 
/**
*    对外公开初始化方法 
*    在应用主线程(UI  线程)中初始化  Looper  调用此方法 
*/
public    static    void    prepareMainLooper()    {
//    因为是主线程,初始化一个不允许退出的  Looper 
prepare(false);
synchronized    (Looper.class)    {
//    如果  sMainLooper  不等于空说明已经创建过主线程  Looper  了,不 应该重复创建
if    (sMainLooper !=    null)    {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper =    myLooper(); 
}
} 
/**
*    内部私有初始化方法
*    @param    quitAllowed    是否允许退出  Looper 
*/
private    static    void    prepare(boolean    quitAllowed)    { 
//    每个线程只能有一个  Looper
if    (sThreadLocal.get()   !=    null)    {
throw  new  RuntimeException("Only  one  Looper  may  be  created  perthread");
}
//    保存实例
sThreadLocal.set(new    Looper(quitAllowed)); 
}
/**
*    私有构造方法
*    @param    quitAllowed    是否允许退出  Looper 
*/
private    Looper(boolean    quitAllowed)    { 
//    初始化  MessageQueue
mQueue =new  MessageQueue(quitAllowed); 
//    得到当前线程实例
mThread = Thread.currentThread(); 
}

真正创建 Looper 实例的构造方法中其实很简单,就是创建了对应的 MessageQueue 实例,然后得到当前线程,值得注意的是 MessageQueue 和线程 实例都是被 final 关键字修饰的,只能被赋值一次。
对外公开初始化方法 prepareMainLooper()是为应用主线程(UI 线程)准备的,
应用刚被创建就会调用该方法,所以我们不该再去调用它。
开发者可以通过调用对外公开初始化方法 prepare()对自己的 worker 线程创建 Looper,但是要注意只能初始化一次。
调用 Looper.prepare()方法初始化完成后,可以调用 myLooper()和 myQueue() 方法得到当前线程对应的实例。

public    static    @Nullable    Looper    myLooper()    { 
return    sThreadLocal.get();
}
public    static    @NonNull    MessageQueue    myQueue()    { 
return    myLooper().mQueue;
}

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有再指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。

2.3.2退出 Looper

退出 Looper 有安全与不安全两种退出方法,其实对应的就是 MessageQueue 的 安全与不安全方法:

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

MessageQueue的退出方法

//   退出队列
void    quit(boolean    safe)    {
//    如果不是可以手动退出的,抛出异常 
if    (!mQuitAllowed)    {
throw    new    IllegalStateException("Main hread  not  allowed    to quit."); 
}
synchronized    (this)    {
if    (mQuitting)    {
return; 
}
//    标记为已退出状态 
mQuitting =    true;
//    两种清除队列中消息的方法 
if    (safe)    {
removeAllFutureMessagesLocked(); 
}    else    {
removeAllMessagesLocked(); 
}
//    注销 
nativeWake(mPtr);
} 
}

方法 quit(boolean safe)中的参数 safe 决定了到底执行哪种清除消息的方法:
 removeAllMessagesLocked(),简单暴力直接清除掉队列中所有的消息。
 removeAllFutureMessagesLocked(),清除掉可能还没有被处理的消息。
removeAllMessagesLocked()方法的逻辑很简单,从队列头中取消息,有一个算一个,全部拿出来回收掉。

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    {
//    n  就是那个晚于当前时间的消息 
//    从  n  开始之后的消息全部回收 
p =    n;
n =    p.next;
p.recycleUnchecked(); 
}    while    (n !=    null);
}
} 
}
2.3.3 运行Looper
public    static    void    loop()    {
//    得到当前线程下的  Looper 
final    Looper    me =    myLooper();
//    如果还没初始化过抛异常 
if    (me ==    null)    {
throw    new    RuntimeException("No    Looper;    Looper.prepare()
wasn't    called    on    this    thread."); 
}
//    得到当前线程下与  Looper  对应的消息队列 
final    MessageQueue    queue =    me.mQueue;
// 得到当前线程的唯一标识(uid+pid),作用是下面每次循环都判断一下线程有没有被切换
//    不知道为什么要调用两次该方法 
Binder.clearCallingIdentity();
final    long    ident =    Binder.clearCallingIdentity(); 
//    进入死循环不断取出消息
for    (;;)    {
//    从队列中取出一个消息,这可能会阻塞线程 
Message    msg =    queue.next();
//    如果消息是空的,说明队列已经退出了,直接结束循环,结束方 
法
if    (msg ==    null)    {
return;
}
//    打印日志
final    Printer    logging =    me.mLogging; 
if    (logging !=    null)    {
logging.println(">>>> Dispatching   to   "   +  msg.target +   "msg.callback +    ":    "    +    msg.what);
}
//    性能分析相关的东西
final    long    traceTag =    me.mTraceTag;
if    (traceTag !=    0    &&    Trace.isTagEnabled(traceTag))    { 
Trace.traceBegin(traceTag,"+msg.target.getTraceName(msg));
} 
try    {
//尝试将消息分发给宿主(Handler) 
//dispatchMessage  为宿主  Handler  的接收消息方法 
msg.target.dispatchMessage(msg);
}    finally    {
//    性能分析相关的东西
if    (traceTag !=    0)    {
Trace.traceEnd(traceTag); 
}
}
//打印日志
if    (logging !=    null)    {
logging.println("<<<<<   Finished   to   "   +   msg.target +   "   "   +msg.callback);
}
//得到当前线程的唯一标识
final long newIdent = Binder.clearCallingIdentity(); 
//如果本次循环所在的线程与最开始不一样,打印日志记录
if    (ident !=    newIdent)    {
Log.wtf(TAG,    "Thread    identity    changed    from   0x"
+    Long.toHexString(ident)    +    "    to    0x"
+   Long.toHexString(newIdent)   +   "   while   dispatching to    "
+    msg.target.getClass().getName()    +    "    " 
+    msg.callback +    "    what="    +    msg.what);
}
//消息分发完毕,回收消息到缓存池 
msg.recycleUnchecked();
} 
}

Looper 的功能很简单,核心方法 Looper.loop()就是不断的从消息队列中取出消 息分发给对应的宿主 Handler,它与对应 MessageQueue 息息相关,一起创建, 一起退出。
Looper 更想强调的是线程的独立性与唯一性,利用 ThreadLocal 保证每个线程只 有一个 Looper 实例的存在。利用静态构造实例方法保证不能重复创建 Looper。

2.4 Handler源码分析

2.4.1初始化

Handler 有 7 个构造方法,分别对应不同的参数来初始化不同的 Handler 属性, 但是真正完成初始化操作的只有两个构造方法:

/**
*    @hide    隐藏的构造方法,外部不可见 
*/
public    Handler(Callback    callback,    boolean    async)    {
//    检查是否存在内存泄漏的可能 
if    (FIND_POTENTIAL_LEAKS)    {
final    Class    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();
//    如果  Looper  还没初始化抛出异常 
if    (mLooper ==    null)    {
throw    new    RuntimeException(
"Can't    create    handler    inside    thread    that    has    not    called
Looper.prepare()"); 
}
//    得到当前线程的  MessageQueue 
mQueue =    mLooper.mQueue;
mCallback =    callback;
mAsynchronous =    async; 
}

而我们常用的
public Handler(Callback callback) {
this(callback, false);
}
就是调用的该方法

public    Handler(Looper    looper,    Callback    callback,    boolean    async)    {
mLooper =    looper; 
mQueue =    looper.mQueue; 
mCallback =    callback; 
mAsynchronous =    async;
}
2.4.2 发送消息

使用 Handler 发送消息时我们知道它分为两类:
 postXXX()方法切换回原线程。
 sendMessageXXX()方法发送消息到原线程。
其实这两种方法本质都是发送一个 Message 对象到原线程,只不过 PostXXX()方 法是发送了一个只有 Runnable callback 属性的 Message 对象。
所有的sendMesageXXX()方法最后都会调用enqueueMessage

private  boolean enqueueMessage(MessageQueue queue,  Message msg,  long 
uptimeMillis)    {
//    将消息的宿主设置为当前  Handler  自身 
msg.target =    this;
//如果  Handler  被设置成了异步就把消息也设置成异步的 
if    (mAsynchronous)    {
msg.setAsynchronous(true); 
}
//    执行消息队列的入队操作
return    queue.enqueueMessage(msg,    uptimeMillis); 
}

到这里终于明白了为啥 Looper 和 MessageQueue 一直在使用 Message 的 target
属性而我们却从来没有给它赋值过,是 Handler 在发送消息前自己赋值上去的。

public    final    boolean    post(Runnable    r){
return      sendMessageDelayed(getPostMessage(r),    0); 
}
private    static    Message    getPostMessage(Runnable    r)    { 
Message    m =    Message.obtain();
m.callback =    r; 
return    m;
}
private    static    Message    getPostMessage(Runnable    r,    Object    token)    { 
Message    m =    Message.obtain();
m.obj =    token; 
m.callback =    r; 
return    m;
}

postXXX()方法就是构造了一个只有 Runnable callback 的 Message 对象,然后走正常发送消息的方法。

2.4.3接收消息

在 Looper 的源码中我们知道每当从 MessageQueue 中取出一个消息时就会调用
这个消息的宿主 target 中分发消息的方法:
// Looper 分发消息
msg.target.dispatchMessage(msg);
而这个宿主 target 也就是我们的 Handler,所有 Handler 接收消息就是在这个
dispatchMessage()方法中了:

public    void    dispatchMessage(Message    msg)    {
//    如果  Message 的 callback 不为空,说明它是一个通过  postXXX()方法 发送的消息
if (msg.callback != null) { 
// 直接运行这个 callback 
handleCallback(msg);
}    else    {
//如果  mCallback    不为空说明  Handler  设置了  Callback  接口 
//    先执行接口处理消息的方法
if    (mCallback !=    null)    {
//    如果  callback  接口处理完消息返回  true  说明它将消息拦截 
//    不再执行  Handler  自身的处理消息方法,直接结束方法
if    (mCallback.handleMessage(msg))    {
return; 
}
}
//    调用  Handler  自身处理消息的方法 
handleMessage(msg);
} 
}
private    static    void    handleCallback(Message    message)    { 
//    运行  callback,也就是这个  Runnable  接口
message.callback.run(); 
}
//   Handler  自身处理消息的方法,开发者需要重新该方法来实现接收消息 
public    void    handleMessage(Message    msg)    {
}

由此可见,如果是单纯的 PostXXX()方法发送的消息,Handler 接收到了之后直 接运行 Message 对象的 Runnable 接口,不会将它当做一个消息进行处理。
而我们的 mCallback 接口是完全可以替代 Handler 自身接收消息的方法,因为其 高优先处理等级,它甚至可以选择拦截掉 Handler 自身的接收消息方法。

3Handler 面试题全解析

3.1一个线程有几个 Looper?几个 Handler?

一个 Thread 只能有一个 Looper,一个 MessageQueen,可以有多个 Handler
以一个线程为基准,他们的数量级关系是: Thread(1) : Looper(1) :
MessageQueue(1) : Handler(N)
源码分析:
创建handler时:

  public Handler(@Nullable Callback callback, boolean async) {
  mLooper = Looper.myLooper();
     ....
   mQueue = mLooper.mQueue;
}
 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
static final ThreadLocal sThreadLocal = new ThreadLocal();
  @UnsupportedAppUsage
    final MessageQueue mQueue;
  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();
    }

用ThreadLocal保证了Loop的线程独有,而MessageQueue是Loop的内部类,一一对应。

3.2 Handler 内存泄漏原因? 以及最佳解决方案?

泄露原因:
Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity, 那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而 又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露
解决方案

  1. 最直接的思路就是避免使用非静态内部类。使用 Handler 的时候,放在一个新建的 文件中来继承 Handler 或者使用静态的内部类来替代。静态内部类不会隐含的持有 外部的引用,因此这个 activity 也就不会出现内存泄漏问题。
  2. 如果你需要在 Handler 内部调用外部 Activity 的方法,你可以让这个 Handler 持这 个 Activity 的弱引用,这样便不会出现内存泄漏的问题了。
  3. 另外,对于匿名类 Runnable,我们同样可以设置成静态的,因为静态内部类不会持 有外部类的引用。
  4. 注意:如果使用 Handler 发送循环消息,最好是在 Activity 的 OnDestroy 方法中调用
    mLeakHandler.removeCallbacksAndMessages(null);移除消息。(这不是解决内存泄漏 的方法)

3.3.为何主线程可以 new Handler?如果想要在子线程中 new Handler 要做些什么

准备?
每一个 handler 必须要对应一个 looper,主线程会自动创建 Looper 对象,不需要我们手动创建,所以主线程可以直接创建 handler。
源码分析:在Activity中有ActivityThread mMainThread,在他的main函数中

 Looper.prepareMainLooper();
...
 Looper.loop();

在 new handler 的时候没有传入指定的 looper 就会默认绑定当前创建 handler 的 线程的 looper,如果没有 looper 就报错。
因为在主线程中,Activity 内部包含一个 Looper 对象,它会自动管理 Looper,处 理子线程中发送过来的消息。而对于子线程而言,没有任何对象帮助我们维护 Looper 对象,所以需要我们自己手动维护。
所以要在子线程开启 Handler 要先创建 Looper,并开启 Looper 循环,如果在子线程中创建了一个 Handler,那么就必须做三个操作:

  1. prepare();
    
  2. loop();
    
  3. quit();
    

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

在 Handler 机制里面有一个 Looper,在 Looper 机制里面有一个函数,叫做
quitSafely()和 quit()函数,这两个函数是调用的 MessageQueue 的 quit()。
它会remove 消息,把消息队列中的全部消息给干掉。 把消息全部干掉,也就释放了内存。最后会执行 nativeWake(mPtr);

3.5.既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同线程),那它内部是如何确保线程安全的?

MessageQueue 的消息存取即可,看源码内部的话,在往消息队 列里面存储消息时,会拿当前的 MessageQueue 对象作为锁对象,这样通过加锁就可以确保操作的原子性和可见性了。消息的读取也是同理,也会拿当前的 MessageQueue 对象作为锁对象,来保证多线程读写的一个安全性。

3.6我们使用 Message 时应该如何创建它?

创建的它的方式有两种:
一种是直接new一个Message对象,
另一种是通过调用Message.obtain()的方式去复用一个已经被回收的 Message,
当然日常使用者是推荐使用后者来拿到一个 Message,因为不断的去创建新对象
的话,可能会导致垃圾回收区域中新生代被占满,从而触发 GC。

3.7Looper 死循环为什么不会导致应用卡死?

Launch 桌面的图标第一次启动 Activity 时,会最终走到 ActivityThread 的 main 方 法,在 main方法里面创建 Looper 和 MessageQueue 处理主线程的消息,然后 Looper.loop()方法进入死循环,我们的 Activity 的生命周期都是通过 Handler 机制 处理的,包括onCreate、onResume 等方法。
主线程的主要方法就是消息循环,一旦退出消息循环,那么你的应用也就退出了,
Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞, 能一直处理事件就不会产生 ANR 异常。
造成 ANR 的不是主线程阻塞,而是主线程的 Looper 消息处理过程发生了任务阻 塞,无法响应手势操作,不能及时刷新 UI。
总结:应用卡死压根与这个 Looper 没有关系,应用在没有消息需要处理的时候,
它是在睡眠,释放线程;卡死是 ANR,而 Looper 是睡眠。
源码分析:
在Loop.loop()方法的死循环中

 for (;;) {
            Message msg = queue.next(); // might block
}

messageQueue.next方法是从消息队列中取当前时间可以执行的消息,如果没有消息就是阻塞线程等待下一个消息的唤醒。在MessageQueue入队的时候会调nativeWake(mPtr);唤醒线程。

Message next() {
.....
   for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);
 ......
}
}

你可能感兴趣的:(Handler消息机制)