Andriod-消息机制Handler
学而不思则罔,思而不学则殆
参考
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-2
Process: com.example.handlerstudy, PID: 15802
android.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-2
Process: com.example.handlerstudy, PID: 16004
java.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: TextView
override 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: TextView
override 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-2
Process: com.example.handlerstudy, PID: 17155
android.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 flag
sPoolSize--;
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.
*/
@UnsupportedAppUsage
void 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
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare(); //创建Looper
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared(); //可以着这里初始化Hander
Looper.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.next
prev.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初始化传入的Callback
if (mCallback.handleMessage(msg)) {//返回true,拦截handleMessage
return;
}
}
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.java
private class Idler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
...
}
}
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
...
}
}
final class PurgeIdler implements MessageQueue.IdleHandler {
@Override
public boolean queueIdle() {
...
}
}
@UnsupportedAppUsage
void 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);
}
@Override
public 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函数中就用到了同步屏障:
@UnsupportedAppUsage
void 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() {
@Override
public 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.java
int mTraversalBarrier;
@UnsupportedAppUsage
void 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 {
@Override
public 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.