Andriod-消息机制Handler

  • 参考
  • Handler问题三连
    • 1.Handler是什么
    • 2.Handler有什么用
    • 3.为什么要用Handler,不用行不行
  • Handler怎么用
    • 1.sendMessage() + handleMessage()
    • 2.post(runnable)
    • 3.附:其他两个种在子线程中更新UI的方法
  • Handler底层原理解析
    • 1.涉及到的几个类
    • UI线程的Looper
    • 3.消息队列的运行
    • 4.HandlerThread
    • 5.当我们用Handler发送一个消息发生了什么?
    • 6.Looper是怎么拣队列的消息的?
    • 7.分发给Handler的消息是怎么处理的?
    • 8.IdleHandler是什么?
      • IdleHandler在Android源码中的应用
  • 一些其他问题
    • 1.Looper在主线程中死循环,为啥不会ANR?
    • 2.Handler泄露的原因及正确写法
    • 同步屏障机制
      • 自测试屏障消息1
      • 屏障消息系统应用

学而不思则罔,思而不学则殆

参考

大牛的博客,非常全面

Handler问题三连

1.Handler是什么

答:Android Framework 架构中的一个 基础组件,用于子线程与主线程间的通讯,实现了一种非堵塞的消息传递机制

2.Handler有什么用

答:把子线程中的 UI更新信息传递 给主线程(UI线程),以此完成UI更新操作。

个人理解:子线程通知主线程更新UI;或者主线程有什么耗时操作扔到子线程

3.为什么要用Handler,不用行不行

答:不行,Handler是android在设计之初就封装的 一套消息创建、传递、处理机制

Android要求:

在主线程(UI)线程中更新UI

是要求,建议,不是规定,你不听,硬要:

在子线程中更新UI,也是可以的!!!

    fun updateUI(view: View) {Thread(Runnable {Looper.prepare()val dialog = AlertDialog.Builder(this@BActivity).apply {setTitle("子线程更新UI")setIcon(R.drawable.ic_launcher_background)}.create()dialog.show()Looper.loop()}).start()}

却可以弹窗Dialog

当我们用这个方法尝试在子线程更新UI的时候:
测试一:

    fun updateUI2(view: View) {Thread(Runnable {//dialog.show()textView.text = "测试子线程更新UI"}).start()}

会报如下错误:

2020-08-09 09:11:16.493 15802-15863/com.example.handlerstudy E/AndroidRuntime: FATAL EXCEPTION: Thread-2Process: com.example.handlerstudy, PID: 15802android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

异常翻译:只有创建这个view的线程才能操作这个view;
引起原因:在子线程中更新了主线程创建的UI;
也就是说:子线程更新UI也行,但是只能更新自己创建的View;
换句话说:Android的UI更新(GUI)被设计成了单线程;

测试二:
子线程没有执行 Looper.prepare()的方法,再来弹出Dialog,却报错了。

    fun updateUI3(view: View) {Thread(Runnable {val dialog = AlertDialog.Builder(this@BActivity).apply {setTitle("子线程更新UI")setIcon(R.drawable.ic_launcher_background)}.create()dialog.show()}).start()}

错误log:

2020-08-09 09:13:40.224 16004-16036/com.example.handlerstudy E/AndroidRuntime: FATAL EXCEPTION: Thread-2Process: com.example.handlerstudy, PID: 16004java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()at android.os.Handler.<init>(Handler.java:227)at android.os.Handler.<init>(Handler.java:129)at android.app.Dialog.<init>(Dialog.java:133)at android.app.AlertDialog.<init>(AlertDialog.java:204)at android.app.AlertDialog$Builder.create(AlertDialog.java:1105)at com.example.handlerstudy.BActivity$updateUI3$1.run(BActivity.kt:60)at java.lang.Thread.run(Thread.java:923)

你可能会问,为啥不设计成多线程?

答:多个线程同时对同一个UI控件进行更新,容易发生不可控的错误!

那么怎么解决这种线程安全问题?

答:最简单的 加锁,不是加一个,是每层都要加锁(用户代码→GUI顶层→GUI底层…)这样也意味着更多的 耗时,UI更新效率降低;如果每层共用同一把锁的话,其实就是单线程。

所以,结论是:

Android没有采用「线程锁」,而是采用「单线程消息队列机制」,实现了一个「伪锁」

再来看一个网上很常见的例子:

class BActivity : BaseActivity() {private lateinit var textView: TextViewoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_b)textView = findViewById<TextView>(R.id.title)textView.text = "测试子线程更新UI"Thread(Runnable {textView.text = "测试子线程更新UI ${Thread.currentThread().name}"}).start()}...
}

在OnCreate的时候子线程更新UI,却没有报错:

这是要搞我吗?但如果在子线程中加线程休眠模拟耗时操作的话:

class BActivity : BaseActivity() {private lateinit var textView: TextViewoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_b)textView = findViewById<TextView>(R.id.title)textView.text = "测试子线程更新UI"Thread(Runnable {Thread.sleep(100)textView.text = "测试子线程更新UI ${Thread.currentThread().name}"}).start()}...
}

程序就崩溃了,崩溃日志如下:

2020-08-09 10:40:01.813 17155-17217/com.example.handlerstudy E/AndroidRuntime: FATAL EXCEPTION: Thread-2Process: com.example.handlerstudy, PID: 17155android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8743)at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1597)at android.view.View.requestLayout(View.java:25372)at android.view.View.requestLayout(View.java:25372)at android.view.View.requestLayout(View.java:25372)at android.view.View.requestLayout(View.java:25372)at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)at android.view.View.requestLayout(View.java:25372)at android.widget.TextView.checkForRelayout(TextView.java:9737)at android.widget.TextView.setText(TextView.java:6311)at android.widget.TextView.setText(TextView.java:6139)at android.widget.TextView.setText(TextView.java:6091)at com.example.handlerstudy.BActivity$onCreate$1.run(BActivity.kt:19)at java.lang.Thread.run(Thread.java:923)

这是要搞我啊?
那为什么添加延时过后就崩溃了呢?
那我们先打印日志看看:
不加延时:

2020-08-09 10:47:45.109 17582-17582/com.example.handlerstudy D/zhangyu.BActivity: onCreate
2020-08-09 10:47:45.110 17582-17641/com.example.handlerstudy D/zhangyu.BActivity: 子线程更新UI
2020-08-09 10:47:45.115 17582-17582/com.example.handlerstudy D/zhangyu.BActivity: onStart
2020-08-09 10:47:45.116 17582-17582/com.example.handlerstudy D/zhangyu.BActivity: onResume

添加延时:

2020-08-09 10:48:17.611 17687-17687/com.example.handlerstudy D/zhangyu.BActivity: onCreate
2020-08-09 10:48:17.620 17687-17687/com.example.handlerstudy D/zhangyu.BActivity: onStart
2020-08-09 10:48:17.620 17687-17687/com.example.handlerstudy D/zhangyu.BActivity: onResume
2020-08-09 10:48:17.713 17687-17745/com.example.handlerstudy D/zhangyu.BActivity: 子线程更新UI

前面说了 Android的UI更新被设计成单线程,这里妥妥滴会报错,但却发生在延迟执行后?限于篇幅,这里就不去跟源码了,直接说原因:

ViewRootImp 在 onCreate() 时还没创建;在 onResume()时,ActivityThread 的 handleResumeActivity() 执行后才创建;
调用 requestLayout(),走到 checkThread() 时就报错了。

Handler怎么用

1.sendMessage() + handleMessage()

代码示例如下:


其中黄色部分与如下警告;

This Handler class should be static or leaks might occur (anonymous android.os.Handler)

Handler不是静态类可能引起「内存泄露」,原因以及正确写法等下再讲。
另外,建议调用 Message.obtain() 函数来获取一个Message实例,为啥?点进源码:

    /*** Return a new Message instance from the global pool. Allows us to* avoid allocating new objects in many cases.*/public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}

加锁,判断Message池是否为空
① 不为空,取一枚Message对象,正在使用标记置为0,池容量-1,返回;
② 为空,新建一个Message对象,返回;

该Message 是一个链表结构,每次获取对象的时候是从头部获取。
然后问题来了,Message信息什么时候加到池中?

答:当Message 被Looper分发完后,会调用 recycleUnchecked()函数,回收没有在使用的Message对象。

源码如下:

    /*** Recycles a Message that may be in-use.* Used internally by the MessageQueue and Looper when disposing of queued Messages.*/@UnsupportedAppUsagevoid 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 = UID_NONE;workSourceUid = UID_NONE;when = 0;target = null;callback = null;data = null;//加入到头部,头插法synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {next = sPool; //当前节点的下一个节点指向头结点sPool = this; //头结点指向当前节点sPoolSize++;}}}

标志设置为FLAG_IN_USE,表示正在使用,相关属性重置,加锁,判断消息池是否满,
未满,「单链表头插法」把消息插入到表头。(获取和插入都发生在表头,像不像 栈~)

2.post(runnable)

    fun ues2(view: View) {Thread {//            mHandler.post(Runnable {//                textView.text = "用法2"
//            })mHandler.post {textView.text = "用法2" //设置UI}}.start()}

实际调用的是sendMessageDelayed方法

    public final boolean post(@NonNull Runnable r) {return  sendMessageDelayed(getPostMessage(r), 0);}//获取Message对象private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;}

3.附:其他两个种在子线程中更新UI的方法

runOnUiThread

    fun ues4(view: View) {Thread {runOnUiThread {textView.text = "通过runOnUiThread在子线程更新UI"}}.start()}

View.post()和View.postDelayed()

    fun ues3(view: View) {Thread {textView.post {textView.text = "通过View.post在子线程更新UI"}textView.postDelayed({textView.text = "通过View.postDelayed在子线程更新UI"}, 1000)}.start()}

Handler底层原理解析

1.涉及到的几个类

UI线程的Looper

我们在主线程可以直接使用Handler,是因为主线程在创建的时候已经帮我们初始化了Looper,所以我们可以直接使用。请查看源码,移除干扰项:

    //ActivityThread public static void main(String[] args) {...Looper.prepareMainLooper(); //创建主线程Looper...ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);...Looper.loop(); //开启消息轮询,死循环,正常不会执行后续代码throw new RuntimeException("Main thread loop unexpectedly exited");}

定位到:Looper → prepareMainLooper函数

    public static void prepareMainLooper() {prepare(false); //新建Looper,设置Looper不可关闭synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}

定位到: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)); //新建Looper对象,保存到THreadLocal中}

定位到:Looper → Looper构造函数

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

定位到:MessageQueue→ quit构造函数:
另外这里的 mQuitAllowed 变量,直译「退出允许」,具体作用是?跟下 MessageQueue:

    void quit(boolean safe) {if (!mQuitAllowed) { //MainLooper不允许退出throw new IllegalStateException("Main thread not allowed to quit.");}synchronized (this) {if (mQuitting) {return;}mQuitting = true;if (safe) {removeAllFutureMessagesLocked();} else {removeAllMessagesLocked();}// We can assume mPtr != 0 because mQuitting was previously false.nativeWake(mPtr);}}

3.消息队列的运行

前戏过后,创建了Looper与MessageQueue对象,接着调用Looper.loop()开启轮询。
定位到:Looper → 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...处理消息...}}

loop方法是一个无限循环,开启消息轮询逻辑

4.HandlerThread

HandlerThread = 继承Thread + 封装Looper

    //HandlerThread.java@Overridepublic void run() {mTid = Process.myTid();Looper.prepare();   //创建Loopersynchronized (this) {mLooper = Looper.myLooper();notifyAll();}Process.setThreadPriority(mPriority);onLooperPrepared(); //可以着这里初始化HanderLooper.loop(); //轮询mTid = -1;}

继承Thread,getLooper()加锁死循环wait()堵塞线程;
run()加锁等待Looper对象创建成功,notifyAll()唤醒线程;
唤醒后,getLooper返回由run()中生成的Looper对象;

虽然HanderThread再开发中可以方便我们的做一些线程切换的工作。但是如果HandlerThread被滥用的话,会导致消息处理不过来,让用户感觉点击没有效果。所以实际开发中需要对子线程处理的消息进行分级,创建不同的HanderThread处理不同优先级的消息。跟UI相关的耗时操作或者数据存储的优先级比一般纯后台的,不及时的消息(比如:埋点上报等)高。不要让低优先级的消息阻塞高优先级的消息。

5.当我们用Handler发送一个消息发生了什么?

刚讲到 ActivityThread 在 main函数中调用 Looper.prepareMainLooper
完成主线程 Looper初始化,然后调用 Looper.loop() 开启消息循环 等待接收消息。
上面说了,Handler可以通过sendMessage()和 post() 发送消息,
上面也说了,源码中,这两个最后调用的其实都是 sendMessageDelayed()完成的:

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}

第二个参数:当前系统时间(从手机开机时开始统计,更改手机时间不会影响这个时间)+延时时间,这个会影响「调度顺序」,跟 sendMessageAtTime()

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);}
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {msg.target = this; //target 保存Hander,所以需要注意内部类Hander造成的内存泄漏msg.workSourceUid = ThreadLocalWorkSource.getUid();if (mAsynchronous) { //设置屏障消息msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}

这里的 mAsynchronous 是 异步消息的标志,如果Handler构造方法不传入这个参数,默认false:
这里涉及到了一个「同步屏障」的东西,等等再讲,跟:MessageQueue -> 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; //这是msg的执行时间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.nextprev.next = msg; }// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}

6.Looper是怎么拣队列的消息的?

MessageQueue里有Message了,接着就该由Looper来分拣了,定位到:Looper → 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(); // 取出队列中的消息...处理消息...}}

queue.next() 从队列拿出消息,定位到:MessageQueue -> next函数:


这里的关键其实就是:nextPollTimeoutMillis,决定了堵塞与否,以及堵塞的时间,三种情况:

等于0时,不堵塞,立即返回,Looper第一次处理消息,有一个消息处理完;
大于0时,最长堵塞等待时间,期间有新消息进来,可能会了立即返回(立即执行);
等于-1时,无消息时,会一直堵塞;

Tips:此处用到了Linux的pipe/epoll机制:没有消息时阻塞线程并进入休眠释放cpu资源,有消息时唤醒线程;

7.分发给Handler的消息是怎么处理的?

通过MessageQueue的queue.next()拣出消息后,调用msg.target.dispatchMessage(msg)
把消息分发给对应的Handler,跟到:Handler -> dispatchMessage

    /*** Handle system messages here.*/public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) { //有callback 说明是post方法发送的Runnable,直接run()handleCallback(msg); } else {if (mCallback != null) { //Handerl初始化传入的Callbackif (mCallback.handleMessage(msg)) {//返回true,拦截handleMessagereturn;}}handleMessage(msg); //处理事件}}

8.IdleHandler是什么?

在 MessageQueue 类中有一个 static 的接口 IdleHanlder

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

翻译下注释:当线程将要进入堵塞,以等待更多消息时,会回调这个接口;
简单点说:当MessageQueue中无可处理的Message时回调;
作用:UI线程处理完所有View事务后,回调一些额外的操作,且不会堵塞主进程;

接口中只有一个 queueIdle() 函数,线程进入堵塞时执行的额外操作可以写这里,
返回值是true的话,执行完此方法后还会保留这个IdleHandler,否则删除。
使用代码如下;

            mHandler.looper.queue.addIdleHandler {Log.d("zhangyuCActivity", "空闲消息1")false}mHandler.looper.queue.addIdleHandler {Log.d("zhangyuCActivity", "空闲消息2")true}mHandler.looper.queue.addIdleHandler {Log.d("zhangyuCActivity", "空闲消息3")false}
2020-08-09 16:19:46.603 21174-21174/com.example.handlerstudy D/zhangyuCActivity: 空闲消息1
2020-08-09 16:19:46.603 21174-21174/com.example.handlerstudy D/zhangyuCActivity: 空闲消息2
2020-08-09 16:19:46.603 21174-21174/com.example.handlerstudy D/zhangyuCActivity: 空闲消息3
2020-08-09 16:19:46.624 21174-21174/com.example.handlerstudy D/zhangyuCActivity: 空闲消息2
2020-08-09 16:19:46.651 21174-21174/com.example.handlerstudy D/zhangyuCActivity: 空闲消息2

看下源码,了解下具体的原理:MessageQueue,定义了一个IdleHandler的列表和数组


定义了添加和删除IdleHandler的函数:


在 next() 函数中用到了 mIdleHandlers 列表:

IdleHandler在Android源码中的应用

在ActivityThread中有三个实现了IdleHandler的内部类

    //ActivityThread.javaprivate class Idler implements MessageQueue.IdleHandler {@Overridepublic final boolean queueIdle() {...}}final class GcIdler implements MessageQueue.IdleHandler {@Overridepublic final boolean queueIdle() {...}}final class PurgeIdler implements MessageQueue.IdleHandler {@Overridepublic boolean queueIdle() {...}}@UnsupportedAppUsagevoid scheduleGcIdler() {if (!mGcIdlerScheduled) {mGcIdlerScheduled = true;Looper.myQueue().addIdleHandler(mGcIdler); //添加GC空闲消息}mH.removeMessages(H.GC_WHEN_IDLE);}void unscheduleGcIdler() {if (mGcIdlerScheduled) {mGcIdlerScheduled = false;Looper.myQueue().removeIdleHandler(mGcIdler); //移除GC空闲消息}mH.removeMessages(H.GC_WHEN_IDLE);}void schedulePurgeIdler() {if (!mPurgeIdlerScheduled) {mPurgeIdlerScheduled = true;Looper.myQueue().addIdleHandler(mPurgeIdler);}mH.removeMessages(H.PURGE_RESOURCES);}void unschedulePurgeIdler() {if (mPurgeIdlerScheduled) {mPurgeIdlerScheduled = false;Looper.myQueue().removeIdleHandler(mPurgeIdler);}mH.removeMessages(H.PURGE_RESOURCES);}

这些消息会在主线程没有事件处理的时候被处理。还有一些第三方框架在使用比如:LeakCanary,Glide

一些其他问题

1.Looper在主线程中死循环,为啥不会ANR?

答:上面说了,Looper通过queue.next()获取消息队列消息,当队列为空,会堵塞,此时主线程也堵塞在这里,好处是:main函数无法退出,APP不会一启动就结束!

你可能会问:主线程都堵住了,怎么响应用户操作和回调Activity声明周期相关的方法?

答:application启动时,可不止一个main线程,还有其他两个Binder线程:ApplicationThread 和 ActivityManagerProxy,用来和系统进程进行通信操作,接收系统进程发送的通知。

当系统受到因用户操作产生的通知时,会通过 Binder 方式跨进程通知 ApplicationThread;
它通过Handler机制,往 ActivityThread 的 MessageQueue 中插入消息,唤醒了主线程;
queue.next() 能拿到消息了,然后 dispatchMessage 完成事件分发;PS:ActivityThread 中的内部类H中有具体实现

死循环不会ANR,但是 dispatchMessage 中又可能会ANR哦!如果你在此执行一些耗时操作,
导致这个消息一直没处理完,后面又接收到了很多消息,堆积太多,就会引起ANR异常!!!

2.Handler泄露的原因及正确写法

在Java中,非静态内部类会持有一个外部类的隐式引用,可能会造成外部类无法被GC;
比如这里的Handler,就是非静态内部类,它会持有Activity的引用从而导致Activity无法正常释放。而且Message的target又持有handler,Message在队列中

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

代码示例如下:

    private static class MyHandler extends Handler {private final WeakReference<Activity> activityWeakReference;public MyHandler(Activity activity) {this.activityWeakReference = new WeakReference<>(activity);}@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);Activity activity = activityWeakReference.get();if (activity != null) {//...}}}

同步屏障机制

通过上面的学习,我们知道用Handler发送的Message后,MessageQueue的enqueueMessage()按照 时间戳升序 将消息插入到队列中,而Looper则按照顺序,每次取出一枚Message进行分发,一个处理完到下一个。这时候,问题来了:有一个紧急的Message需要优先处理怎么破?你可能或说直接sendMessage()不就可以了,不用等待立马执行,看上去说得过去,不过可能有这样一个情况:

一个Message分发给Handler后,执行了耗时操作,后面一堆本该到点执行的Message在那里等着,这个时候你sendMessage(),还是得排在这堆Message后,等他们执行完,再到你!

对吧?Handler中加入了「同步屏障」这种机制,来实现「异步消息优先执行」的功能。

添加一个异步消息的方法很简单:

1、Handler构造方法中传入async参数,设置为true,使用此Handler添加的Message都是异步的;
2、创建Message对象时,直接调用setAsynchronous(true)

一般情况下:同步消息和异步消息没太大差别,但仅限于开启同步屏障之前。可以通过 MessageQueue 的 postSyncBarrier 函数来开启同步屏障:


行吧,这一步简单的说就是:往消息队列合适的位置插入了同步屏障类型的Message (target属性为null)
接着,在 MessageQueue 执行到 next() 函数时:


遇到target为null的Message,说明是同步屏障,循环遍历找出一条异步消息,然后处理。在同步屏障没移除前,只会处理异步消息,处理完所有的异步消息后,就会处于堵塞。如果想恢复处理同步消息,需要调用 removeSyncBarrier() 移除同步屏障:

在API 28的版本中,postSyncBarrier()已被标注hide,但依旧可在系统源码中找到相关应用,比如:
为了更快地响应UI刷新事件,在ViewRootImpl的scheduleTraversals函数中就用到了同步屏障:

    @UnsupportedAppUsagevoid scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //设置屏障消息mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}void unscheduleTraversals() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);//取消屏障消息mChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);}}

自测试屏障消息1

通过反射放屏障消息:

    private void postSyncBarrier() {try {Method method = messageQueue.getClass().getDeclaredMethod("postSyncBarrier");Log.d("zhangyu", "method:" + method);method.setAccessible(true);token = (int) method.invoke(messageQueue);Log.d("zhangyu", "token:" + token);new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}//5秒后移除屏障消息removeSyncBarrier();}}).start();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}private void removeSyncBarrier() {try {Method method = messageQueue.getClass().getDeclaredMethod("removeSyncBarrier", int.class);Log.d("zhangyu", "method:" + method);method.setAccessible(true);method.invoke(messageQueue, token);} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}

发送屏障消息过后,所有的主线程UI消息都不能执行,处于堵塞状态,只有取消屏障效果过后才能继续主线程消息:

    public void send111(View view) {Log.d("zhangyu", "send111");postSyncBarrier(); //发送屏障消息}public void remove111(View view) {Log.d("zhangyu", "UI消息一");}public void show(View view) {Log.d("zhangyu", "UI消息二");}
}
2020-08-09 17:14:56.485 23196-23196/com.example.handlerstudy D/zhangyu: send111
2020-08-09 17:14:56.486 23196-23196/com.example.handlerstudy D/zhangyu: method:public int android.os.MessageQueue.postSyncBarrier()
2020-08-09 17:14:56.486 23196-23196/com.example.handlerstudy D/zhangyu: token:17
2020-08-09 17:15:01.497 23196-23254/com.example.handlerstudy D/zhangyu: method:public void android.os.MessageQueue.removeSyncBarrier(int)
2020-08-09 17:15:01.498 23196-23196/com.example.handlerstudy D/zhangyu: UI消息一
2020-08-09 17:15:01.502 23196-23196/com.example.handlerstudy D/zhangyu: UI消息二

发送屏障消息过后,有发送消息一和消息二,但是没有执行,而是等屏障消息移除后才被执行。

屏障消息系统应用

在View绘制流程中有用到屏障消息,View绘制之前设置屏障消息,绘制结束后在取消屏障消息,在这中间UI主线程Handler收到的异步消息被阻塞,等待屏障被移除。

    //ViewRootImpl.javaint mTraversalBarrier;@UnsupportedAppUsagevoid scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //设置屏障消息mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //发送屏障消息...}}void unscheduleTraversals() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); //取消屏障消息mChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);}}final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal(); //View绘制流程三大步骤}}final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
   void doTraversal() {...performTraversals();...}private void performTraversals() {...performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //测量...performLayout(lp, mWidth, mHeight);//布局...performDraw();//绘制...}

PS:这里存在一个问题,当多线程调用scheduleTraversals方法时,会生成多个屏障消息(实际发生概率很小),但是这儿只保存了最新的屏障消息token,后面删除的时候也只删除了最新的屏障消息。导致主线程事件一直被阻塞,不能被响应,造成ANR.

Andriod-消息机制Handler相关推荐

  1. Android消息机制Handler用法

    这篇文章介绍了Android消息机制Handler用法总结,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 1.简述 Handler消息机制主要包括: Messa ...

  2. Android消息机制(Handler机制) - 线程的等待和唤醒

    我们都知道,Android的Handler机制,会在线程中开启消息循环,不断的从消息队列中取出消息,这个机制保证了主线程能够及时的接收和处理消息. 通常在消息队列中(MessageQueue)中没有消 ...

  3. Android 消息机制 Handler总结

    老久就想着写一篇 关于消息机制的文章来总结一下. Android的消息机制主要是指Handler 的运行机制.我们在开发时有的时候需要在子线程进行耗时的I/o 操作,可能是读取文件或者 访问网络等,有 ...

  4. Android笔记 消息机制handler+http之 网络图片浏览器demo

    在Android中,只有主线程(UI线程)才能修改界面,包括Toast显示,改变Imageview中的图片等操作必须通过消息机制通知主线程修改界面 如不采用handler消息机制极有可能出现如下错误 ...

  5. android 手机内存uri_Android消息机制Handler原理解析

    关注[搜狐技术产品]公众号,第一时间获取技术干货 导读 在Android中,Handler一直是一个热点,在开发过程中,它的使用频率很高,而且在Android源码中Handler都是常客.那么Hand ...

  6. 通过源码简要分析之Android消息机制Handler、Looper、MessageQueue运行机制

    用了许久的Handler,对于Handler的使用确实是比较熟悉,但是具体内部是如何运作的,却只是模糊的.Handler发出的消息怎么到达MessageQueue?MessageQueue的数据怎么被 ...

  7. android 消息轮训,Android消息机制Handler,有必要再讲一次

    我们在日常开发中,总是不可避免的会用到 Handler,虽说 Handler 机制并不等同于 Android 的消息机制,但 Handler 的消息机制在 Android 开发中早已谙熟于心,非常重要 ...

  8. Android:Handler的消息机制

    前言 Android 的消息机制原理是Android进阶必学知识点之一,在Android面试也是常问问题之一.在Android中,子线程是不能直接操作View,需要切换到主线程进行.那么这个切换动作就 ...

  9. Android Handler消息机制源码分析

    一,前言 众多周知, Android 只允许在主线程中更新UI,因此主线程也称为UI线程(ActivityThread). 如此设计原因有二: (1) 由于UI操作的方法都不是线程安全的,如果多个线程 ...

  10. Android中Handler消息机制

    作用:跨线程通信. 应用:AsyncTask.retrofit都对Handler进行了封装. 四要素:Message.MessageQueue.Looper.Handler Message简介: 线程 ...

最新文章

  1. GHOST WIN8 64位软件自选安装专业优化版 201307 V1.0
  2. MySQL中有哪些锁?
  3. python学习官网-Python学习(一)—— 初识python
  4. Spark详解(七):SparkContext源码分析以及整体作业提交流程
  5. Flask实现成绩查询接口
  6. css细节(实习第1天)
  7. MySql主从同步最小配置
  8. Java用freemarker导出word
  9. 在Qt Creator以外编写Qt程序
  10. cf914F. Substrings in a String(bitset 字符串匹配)
  11. 解决多标签分类问题(包括案例研究)
  12. 计算机中库的创建方法,win7系统下库的创建方法
  13. GPS测量原理及应用 知识总结
  14. bugku--never_give_up
  15. 教程:GIMP中怎样移动选区
  16. 春风十里不如Node中的一股清流
  17. 大学计算机实验六文件管理与磁盘恢复,做“文件管理与磁盘恢复”实验完成以下实验报告表并提交...
  18. Tita绩效宝:2022年,如何实施持续绩效管理
  19. Java英文技术网站
  20. Java虚拟机学习笔记整理

热门文章

  1. 关于LNK2000 _main 已经在 某某某.obj 中定义的问题
  2. Permission is only granted to system apps解决方法
  3. FlashFXP官方U盘绿色版
  4. 自己动手搭建家庭局域网(三),千兆网+NAS存储+低成本
  5. 桌面计算机右键管理没反应,右键计算机(我的电脑)管理选项打不开解决措施
  6. 蓝桥杯 第39级台阶
  7. 一款不错的Android环形进度条
  8. 100天python、github_GitHub - 100440175/Python-100-Days: Python - 100天从新手到大师
  9. 密码忘了怎么办? 5种破密方法轻松搞定
  10. CH6202·黑暗城堡