Android Handler机制详解原理
沙雕mars · 243浏览 · 发布于2021-11-04 +关注

Handler主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作

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 mMessages SparseArray 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++;
      }
   }
}


 


相关推荐

android下vulkan与opengles纹理互通

talkchan · 819浏览 · 2020-11-23 10:37:39
Android 使用RecyclerView实现轮播图

奔跑的男人 · 1850浏览 · 2019-05-09 17:11:13
微软发布新命令行工具 Windows Terminal

吴振华 · 700浏览 · 2019-05-09 17:15:04
Facebook 停止屏蔽部分区块链广告

· 594浏览 · 2019-05-09 17:20:08
加载中

0评论

评论
做自己,让别人去说,欢迎各位关注!
分类专栏
小鸟云服务器
扫码进入手机网页