Android Handler机制

709 阅读7分钟
Looper是整个跨线程通信的管理者
    // 内部持有的变量如下:
    ThreadLocal<Looper>
    MainLooper
    Observer
    MessageQueue
    Thread

1.首先先回忆一下Handler怎么用

Android线程通信分为以下两种情况
1. 子线程发消息给UI线程
2. UI线程发消息给子线程
3. 子线程发消息给另个子线程

1.子线程发消息给UI线程

class FragmentContentActivity : AppCompatActivity() {
    val FLAG = 1
    lateinit var handler: Handler
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        handler = object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(msg: Message) {
                when (msg.what) {
                    FLAG -> {
                        findViewById<TextView>(R.id.text).text = msg.data["Text"].toString()
                    }
                }
            }
        }
        thread {
            Thread.sleep(2000L)
            handler.sendMessage(Message.obtain().apply {
                what = FLAG
                data = Bundle().apply {
                    this.putString("Text", "ThreadMessage")
                }
            })
        }
    }
}

2.UI线程/子线程发消息给子线程

class FragmentContentActivity : AppCompatActivity() {
    val THREAD_FLAG =2
    lateinit var threadHandler: Handler
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        thread {
            Looper.prepare()
            threadHandler = object :Handler(Looper.myLooper()!!){
                override fun handleMessage(msg: Message) {
                    when(msg.what){
                        THREAD_FLAG -> {
                            Toast.makeText(
                                this@FragmentContentActivity,
                                "${msg.data["Text"]}",
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                    }
                }
            }
            Looper.loop()
        }
    }

    override fun onResume() {
        super.onResume()
        findViewById<TextView>(R.id.text).postDelayed({
           threadHandler.sendMessage(Message.obtain().apply {
               what = THREAD_FLAG
               data = Bundle().apply {
                   putString("Text","UI Message")
               }
           })
        },2000L)
    }
}

在子线程的使用中,我们发现必须要进行Looper.prepare()和Looper.loop()前后这两个操作,因此,带着这个疑问来看一下Looper的逻辑


// 在调用prepare()之后一定要调用loop(),最后结束消息循环的时候调用quit()
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();
}

prepare()就是将初始化一个Looper对象放入到ThreadLocal中,初始化Looper,同时mQueue

    public static void loop(){
    
    Binder.clearCallingIdentity()
for (;;) {
    Message msg = queue.next(); // might block
 
    long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
    try {
        // 其实 loop()只做了这一个调用,其他的都是监控当前消息循环时间是否超时,应该和ANR有关
        msg.target.dispatchMessage(msg);
        if (observer != null) {
            observer.messageDispatched(token, msg);
        }
        dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
    } catch (Exception exception) {
        if (observer != null) {
            observer.dispatchingThrewException(token, msg, exception);
        }
        throw exception;
    } finally {
        ThreadLocalWorkSource.restore(origWorkSource);
        if (traceTag != 0) {
            Trace.traceEnd(traceTag);
        }
    }
    if (logSlowDelivery) {
        if (slowDeliveryDetected) {
            if ((dispatchStart - msg.when) <= 10) {
                Slog.w(TAG, "Drained");
                slowDeliveryDetected = false;
            }
        } else {
            if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                    msg)) {
                // Once we write a slow delivery log, suppress until the queue drains.
                slowDeliveryDetected = true;
            }
        }
    }
    if (logSlowDispatch) {
        showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
    }
    //消息实体回收
    msg.recycleUnchecked();

可以看到Looper.loop其实只是在for循环中,获取mQueue的下一个msg节点,然后调用msg.target.dispatchMessage(msg)。乍看只是msg对象内部的操作。

因为loop()其实逻辑上算死循环,这意味着,当前线程的自发的顺序执行命令到此结束了,只能通过其他线程触发handler机制,来被动的在当前线程执行命令,当前线程完全变成了一个响应线程

Looper类只是初始化并开启线程死循环的一个开关,具体工作在MessageQueue中进行

MessageQueue 消息队列

队列内消息的添加不是直接调用MessageQueue,而是由与Looper相关联的Handler调用

MessageQueue的内部持有的变量如下:

ArrayList<IdleHandler>
mMessages
SparseArray<FileDescriptorRecord>
IdleHandler[]
mBlocked

MessageQueue类的功能主要有:元素插入队列,获取队列的头部元素,查找队列中元素,综述就是对队列的增删改查,其中 mMessage就是这个队列的入口也是这个队列的头结点

  boolean enqueueMessage(Message msg,long when) //msg 元素插入队列
  boolean hasMessages(Handler h,int what,Object object) //查找handler下的msg.what/object相同的Msg
  boolean hasEqualMessages(Handler h,int what,Object obj)//查找 msg.object.equal(obj)的msg
  removeMessages(Handler h,int what,Object obj)/(Handler h,Runnable r,Object obj)
  removeEqualMessages(...) //删除与参数msg.object相同或equal的msg
  
  Message next() //获取队列中的头部元素

可以看出,这些方法内部都调用了 synchronized(this),队列的操作都是线程同步的

Message next() ->
...

// linux机制下的总线进入轮询,线程相当于挂起状态,nextPollTimeOut是挂起多长时间
nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {

   final long now = SystemClock.uptimeMillis();
   Message prevMsg = null;
   Message msg = mMessages;
   //先判断msg.target是否为null,表示当前消息是不是异步消息
   if (msg != null && msg.target == null) {
   // Stalled by a barrier.  Find the next asynchronous message in the queue.
   //同步屏障:取出当前队列中的异步消息
   do {
       prevMsg = msg;
       msg = msg.next;
       } while (msg != null && !msg.isAsynchronous());
   }
   if (msg != null) {
       if (now < msg.when) {
           // Next message is not ready.  Set a timeout to wake up when it is ready.
           //重新计算线程进入挂起状态的时间
           nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
       } else {
           // Got a message.
               mBlocked = false;
               if (prevMsg != null) {
               prevMsg.next = msg.next;
           } else {
               mMessages = msg.next;
           }
           msg.next = null;
           if (DEBUG) Log.v(TAG, "Returning message: " + msg);
           msg.markInUse();
           return msg;
       }
   } else {
   // No more messages.
       nextPollTimeoutMillis = -1;
}
...

可以看出next()内部主要有两种获取msg的逻辑

1.当前消息都是普通消息,按照msg.when的大小排序,每一次循环执行,通过检测when是否大于now来决定是否获取msg,或是挂起当前线程。

2.当前消息中有异步消息,优先获取msg.isAsynchronous()==true的,或者按照此异步消息的等待时间,来重新设置挂起线程的时间,从而达到精准的获取异步消息。

通俗的来讲就是说,当前所有普通消息按照预约的执行时间的先后来排队,这样可基本上既可以达到按照预约时间执行消息,也可以最大效率的在一定时间段内执行最多的消息,但是这忽略了每个消息的执行消耗的时间,比如A消息是队列内的No.1,A消息预约执行时间是1s之后,整个队列是等待状态的,这个时候来了B消息,B消息预约的时间是0.999s之后,按照预约时间的排队机制,B消息要插队到A消息之前,B成了这个队列的No.1,A成了No.2,整个队列的等待时间还是1s(因为之前设置了等待时间,所以不用唤醒),但是B消息的执行过程长达0.5s,已经超过了之后的很多消息的预约执行时间点了,这样就不能保证某些重要的消息按时执行。

于是就有了异步消息同步屏障的机制,这相当于普通消息排队时来了一个VIP消息,先按照预约时间找到自己的位置,然后大喝一声:“都把脚给我挪开,我的前面不允许有人”,这个时候排在他之前的普通消息就只能全部挪到队列的一边,然后队列重新按照这位VIP消息,设置等待时间,期间新来的普通消息也插到队边等待,保证精准按时执行VIP消息。等VIP消息执行完,之后再把之前等待普通消息的队列合并执行。当然之前等待的消息全耽误了,但毕竟是普通消息不重要。

 // 同步屏障的方法,此方法只在 ViewRootImpl类中调用
private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
        //没有设置target
        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变为同步屏障消息,next()下一次循环,首先获取到的是同步屏障
            mMessages = msg;
        }
        return token;
    }
    // ViewRootImpl
 void scheduleTraversals() {
        if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
    
//设置同步屏障之后,通过设置了Aysnc标记位的Handler发送的Msg都是异步消息,
//MessageQueue也优先处理此类异步消息,直到移除同步屏障标记位,再恢复到普通消息队列。

由此可见,同步屏障的设置和View刷新机制有关,因为要保证Vsync信号按时完成刷新操作,具体分析待续... 综述,异步消息可以保证精准的执行,但也因此消息事件的先后顺序被打乱,有可能在代码执行中执行了Handler.sendMsg(1,time0.2)->AsyncHandler.sendMsg(2,time0.5),但是实际执行的是 2->1。

再看Handler

Handler的成员变量如下

mLooper :初始化时获取当前线程的Looper对象引用
mQueue :通过Looper.mQueue 获取到的MessageQueue队列引用
mAsynchronous :标记当前Handler是否发送异步消息
mCallback : Handler自身的callback接口,此callback调用在Message.callback之前
mMessenger :IMessager 和进程通信相关

以上成员变量大都是final类型,表示Handler也是在其使用上也是final类型,也就是说,没有办法通过将Handler与context的生命周期相剥离来避免内存泄漏

Handler的方法如下

    //Handler 发送Message第一种方法,设置Message的what,data
    //不设置 runnable:Callback 
    boolean sendMessage(Message msg) -> boolean sendMessageDelayed(Message msg,long delayTime)
    -> boolean sendMessageAtTime(Message msg,SystemClock.uptimeMillis()+delayTime)
    -> mQueue.enqueueMessage(msg,uptime)
    //第二种方法,Message只设置runnable:Callback
    boolean postAtTime(Runnable r,Object token,long uptime)
    -> sendMessageAtTime(getPostMessage(r,token),uptime)
    --> Message getPostMessage(Runnable r,Object token){
        Message.obtain().callback=r
        ...
        }
   //移除Message和检验Message
   removeMessages() 
   hasMessages()
   ...
   //Message 回调执行
   void dispatchMessage(Message msg){
       if(msg.callback!=null){
           handleCallback(msg) ->
       }else{
           if(mCallback!=null){
            mCallback.handleMessage(msg)
           }
           handleMessage(msg)
   }
   //可以看到 Message的回调分为三个等级
   //No.1 msg自身的callback
   //No.2 Handler自身的mCallback成员变量,mCallback是final类型
   //No.3 Handler的子类重载的handleMessage方法
Message

Message 实现了Parcelable接口,也就是说可以作为进程间通信的载体

Message成员变量如下

    int what //Handler发送主体下的Message的消息码
    int arg1 //低成本的参数传递
    int arg2 
    Object obj //可以为空的token对象,一般在进程通信中用到
    Bundle data //线程通信中常用的参数容器
    Handler target //发送主体
    Runnable callback //Message自身回调
    Messenger replyTo   //进程通信,一般在AMS中用到
    ------
    // Message缓存池相关
    Object sPoolSync = new Object() // 同步标记
    Messsage next 
    static Message sPool
    static int sPoolSize

Message方法如下

    //可以看出这是一个非常巧妙的方法
    static Message obtain(){
        synchronized(sPoolsSync){
            if(sPools!=null){
                Message m= sPool;
                sPool = m.next;
                m.next = null;
                sPoolSize--;
                return m;
              }
        }
        return new Message();
    }
    //主体上是一个带缓存池链表的同步工厂模式,同时也考虑到较多线程阻塞时
    //可以直接声明初始化对象
    
    //回收Message对象到缓存池链表
    void recycleUnchecked(){
        ...参数=null
        synchronized(sPoolSync){
            if(sPoolSize < MAX_SIZE){
                next = sPool;
                sPools = this;
                sPoolSize++;
          }
       }
    }   

关于Handler的其他的边边角角

1.runWithScissors() // 待续

2.Message分为哪三类

普通Message
异步Message : isAsynchronous(true)
屏障标记Message : target==null

3.IdleHandler // 待续

4.为什么要使用nativePollOnce() /nativeWeak(mPtr)

Linux消息总线的阻塞,这样可以保证在native层也可以收到其他线程的消息,JVM内部用wait/notify就可以解决

5.nativeWeak(mPtr)什么时候唤醒当前线程

1.当前新加入的msg.when < mMessage.when || msg.when=0||msg.target==null 就是当前的消息预约时间效率队列头的等待时间,或者当前消息是同步屏障标记,这时需要唤醒队列
2.当移除同步屏障标记时removeSyncBarriar(),mQueue变成普通消息队列也需要唤醒

6.Handler可以用来IPC通信吗?

因为Handler内部的Looper对象存在于ThreadLocal内,这样只能在同一进程内才可以获取到,所以不能在进程间通信,但是Message应该可以作为一个进程间通信的容器,有Messenger引用