ITPub博客

首页 > 应用开发 > Java > Handler 源码解析(Java 层)

Handler 源码解析(Java 层)

Java 作者:Java高级架构Q 时间:2019-04-28 14:01:55 0 删除 编辑

从很早开始就认识到 Handler 了,只不过那时修为尚浅,了解的不够深刻,也没有应用自如。不过随着工作时间的增长,对 Handler 又有了更深层次的认识,于是有了这篇博客,希望尽可能的总结出多的知识点。  

Handler 在 Java 层源码主要有 4 个类:Looper、MessageQueue、Message、Handler。我归纳了他们的几个主要知识点:  

  • Looper:sThreadLocal、Looper.loop();

  • Message:数据结构、消息缓存池;

  • MessageQueue:enqueueMessage、next、管道等待、同步消息隔离、idleHandler;

  • Handler:send/post、dispatchMessage 消息处理优先级。

Looper

Looper 数据结构



static

 

final

 ThreadLocal<Looper> sThreadLocal = 

new

 ThreadLocal<Looper>();

private   static  Looper sMainLooper;   // guarded by Looper.class

final  MessageQueue mQueue;

// sThreadLocal
private   static   void   prepare ( boolean  quitAllowed)   {
     if  (sThreadLocal.get() !=  null ) {  throw  Exception ... }
    sThreadLocal.set( new  Looper(quitAllowed));
}
public   static   @Nullable   Looper  myLooper ()   {
     return  sThreadLocal.get();
}

// sMainLooper
public   static   void   prepareMainLooper ()   {
    prepare( false );
     synchronized  (Looper.class) {
         if  (sMainLooper !=  null ) {  throw  Exception ...}
        sMainLooper = myLooper();
    }
}
public   static  Looper  getMainLooper ()   {
     synchronized  (Looper.class) {
         return  sMainLooper;
    }
}

// mQueue
private   Looper ( boolean  quitAllowed)   {
    mQueue =  new  MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
public   static   @NonNull   MessageQueue  myQueue ()   {
     return  myLooper().mQueue;
}


  • sThreadLocal:静态常量,保证一个线程只有一个 Looper;

  • sMainLooper:静态变量,在 prepareMainLooper 中赋值当前线程 Looper;

  • mQueue:变量,Looper 构造函数中初始化,因为一个线程只有一个 Looper,所以也同样只有一个 mQueue。

通过以上分析,我们可以总结出一下特性:  

  1. Looper、MessageQueue 是线程唯一的;

  2. 一个进程只有一个 sMainLooper;

  3. 根据 ThreadLocal 的特性,可通过 myLooper 方法获取当前线程的 Looper。

Looper.loop()




public

 

static

 

void

 

loop


()

 

{

     final  Looper me = myLooper();
     final  MessageQueue queue = me.mQueue;
     for  (;;) { 
        Message msg = queue.next();
        ...
        msg.target.dispatchMessage(msg);
        ...
        msg.recycleUnchecked();
    }
}

Looper.loop() 方法虽然看起来很多,其实他主要就做了三件事:  

  1. 从消息队列中获取下一个消息;

  2. msg.target 就是 handler,通过 dispatchMessage 方法把消息分发下去,这个方法下面会有说到;

  3. 消息回收,放到消息缓存池里。这里需要注意的是 Message 对象并没有释放,会缓存起来。

Message

Message 数据结构



public

 

int

 what, arg1, arg2;

public  Object obj;
public  Messenger replyTo;
int  flags;
long  when;       // 消息发送时间
Bundle data;
Handler target;
Runnable callback;

Message next;

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

private   static   final   int  MAX_POOL_SIZE =  50 ;


  • 看到 next 变量,我们会想到单链表,其实 Message 就相当于单链表的 node,MessageQueue 就是一个单链表了,会持有表头的引用;

  • what、arg1、arg2、obj、data 就是我们发送的一些信息;

  • 值得注意的是 target,他是 Handler 类型,就是本消息的 Handler,会在 Handler 发送消息的时候赋值;

  • 后面的四个对象,都是和消息缓存池有关的。

Message 消息缓存池




public

 

static

 Message 

obtain


()

 

{

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

void   recycleUnchecked ()   {
    flags = FLAG_IN_USE;
    what =  ;
    arg1 =  ;
    arg2 =  ;
    obj =  null ;
    replyTo =  null ;
    sendingUid = - 1 ;
    when =  ;
    target =  null ;
    callback =  null ;
    data =  null ;

     synchronized  (sPoolSync) {
         if  (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool =  this ;
            sPoolSize++;
        }
    }
}


  • 事实上缓存池的数据结构也是一个链表,sPool 为链表头引用,最大容量为 50;

  • 回收消息时,会把消息里面所有参数重置,并把当前消息设为链表头;

  • 获取消息时,返回当前链表头,并把 next 置空。

MessageQueue

插入队列




boolean

 

enqueueMessage


(Message msg, 

long

 when)

 

{

     synchronized  ( this ) {
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
         boolean  needWake;
         if  (p ==  null  || when ==   || when < p.when) {
             // 作为表头,如果队列是阻塞状态则需要唤醒
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        }  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;  // 插入消息
            prev.next = msg;
        }

         // We can assume mPtr != 0 because mQuitting is false.
         if  (needWake) {
            nativeWake(mPtr);
        }
    }
     return   true ;
}

主要作为插入队列的方法,有下列几个特性:  

  • 把消息加入消息队列,如果当前表头为空,则把消息作为表头引用;如果不为空,则会根据时间的顺序,插入到对应的时间中;

  • nativeWake 是调用底层在管道中写操作以唤醒,在队列不是阻塞的状态下是不需要唤醒的;

  • 另外注意其中用了 synchronized 关键字,说明消息队列的插入是线性安全的,删除也是线性安全的,之后我们会说到。

MessageQueue.next()



for

 (;;) {

    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  {
                 // 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  {
             // 没有更多消息的时候,nextPollTimeoutMillis 会置为 1。
            nextPollTimeoutMillis = - 1 ;
        }

        ...
    }

     // 如果目前没有消息,已经处在空闲状态,则执行 idler.queueIdle
     for  ( int  i =  ; 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);
            }
        }
    }
    ...
}

此方法会从消息队列中读取下一个消息返回,主要做了以下操作:  

  • nativePollOnce 函数会调用底层管道操作函数,nextPollTimeoutMillis 为 -1 时,会阻塞,为 0 时不会阻塞,大于 0 时,会阻塞相应的时间;

  • 如果有同步消息隔离,则会优先查找异步消息;

  • 获取当前时间队列的消息,并返回;

  • 如果队列没有任何消息,则会执行 idler.queueIdle,通知监听者当前队列处于空闲状态。

同步消息隔离

上面我们有提到了同步消息隔离,这里我们介绍一下。同步隔离,有时候也可以叫异步消息,说的是一个意思。在源码中主要用于优先更新 UI。



private

 IdleHandler[] mPendingIdleHandlers;


public   int   postSyncBarrier ()   {
     return  postSyncBarrier(SystemClock.uptimeMillis());
}

private   int   postSyncBarrier ( long  when)   {
     // 向消息队列中加入一个 handler 为空的消息
     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 !=  ) {
             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;
    }
}

如上 postSyncBarrier 函数中会向消息队列中加入一个 handler(即 Message 的 target) 为空的消息作为标识。在我们上面 MessageQueue.next() 的函数中,当 msg.target == null 时,会优先获取异步消息并返回。   
因此想要使用异步消息有两个条件:

  1. 消息为异步消息,即 msg.isAsynchronous() 返回 false;

  2. 需要获取当前队列并运行 postSyncBarrier() 函数。

IdleHandler

Handler 还提供了消息队列空闲状态通知。



private

 

final

 ArrayList<IdleHandler> mIdleHandlers = 

new

 ArrayList<IdleHandler>();


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

IdleHandler 的源码比较简单,就是一个 ArrayList,然后进行增加删除操作。注意,这个也是线性安全的。

Handler

post/sendMessage




public

 

final

 

boolean

 

post


(Runnable r)


{

     return  sendMessageDelayed(getPostMessage(r),  );
}

public   final   boolean   sendMessage (Message msg) {
     return  sendMessageDelayed(msg,  );
}

private   static  Message  getPostMessage (Runnable r)   {
    Message m = Message.obtain();
    m.callback = r;
     return  m;
}

private   boolean   enqueueMessage (MessageQueue queue, Message msg,  long  uptimeMillis)   {
    msg.target =  this ;
     if  (mAsynchronous) {
        msg.setAsynchronous( true );
    }
     return  queue.enqueueMessage(msg, uptimeMillis);
}
  • sendMessage 和 post 最本质的区别是之后处理任务时的优先级,post 会处理 Runnable 中的任务,而 sendMessage 会回调给 handler 处理;

  • 他们最终都会走 enqueueMessage 方法,并设置当前 Handler 为 msg.target。

dispatchMessage




public

 

void

 

dispatchMessage


(Message msg)

 

{

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

任务执行时就会运行这个函数,主要是一个优先级的问题:

  1. callback 优先级最高,也就是 post 发送的消息

  2. mCallback.handleMessage(msg),优先级第二

  3. handleMessage(msg),优先级第三

(原文完)

提前祝大家周末愉快,下周见。



来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69913485/viewspace-2642773/ ,如需转载,请注明出处,否则将追究法律责任。

下一篇: 没有了~
请登录后发表评论 登录
全部评论
欢迎工作一到五年的Java工程师朋友们加入Java高级架构:829731736 提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码, MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tom

注册时间:2019-03-17

  • 博文量
    7
  • 访问量
    8695