同步屏障
首先需要发送一个特殊消息作为屏障消息,当消息队列检测到了这种消息后,就会从这个消息开始,遍历后续的消息,只处理其中被标记为“异步”的消息,忽略同步消息(所以叫“同步屏障”),相当于给一部分消息开设了“VIP”优先通道。
因为一个读者建议,在分析源码的时候最好是结合demo来讲,后面想想也是,源码这些东西枯燥无味,所以我虚心的接纳了意见,话不多说,先上demo。
public class HandlerActivity extends AppCompatActivity {
private Button button1,button2,button3,button4;
public static final int MESSAGE_TYPE_SYNC=1;
public static final int MESSAGE_TYPE_ASYN=2;
private int token;
private Handler mHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
initView();
initHandler();
}
private void initHandler() {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
//super.handleMessage(msg);
if (msg.what == MESSAGE_TYPE_SYNC){
Log.d("MainActivity","收到普通消息");
}else if (msg.what == MESSAGE_TYPE_ASYN){
Log.d("MainActivity","收到异步消息");
}
}
};
Looper.loop();
}
}).start();
}
private void initView() {
button1 = findViewById(R.id.send_syne);
button2 = findViewById(R.id.remove_sunc);
button3 = findViewById(R.id.send_message);
button4 = findViewById(R.id.send_async_message);
button1.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onClick(View v) {
sendSyncBarrier();
}
});
button2.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onClick(View v) {
removeSyncBarrier();
}
});
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendSyncMessage();
}
});
button4.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
@Override
public void onClick(View v) {
sendAsynMessage();
}
});
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private void sendAsynMessage() {
Log.d("MainActivity","插入异步消息");
Message message=Message.obtain();
message.what=MESSAGE_TYPE_ASYN;
message.setAsynchronous(true);//3
mHandler.sendMessageDelayed(message,1000);
}
private void sendSyncMessage() {
Log.d("MainActivity","插入普通消息");
Message message= Message.obtain();
message.what=MESSAGE_TYPE_SYNC;
mHandler.sendMessageDelayed(message,1000);
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void removeSyncBarrier() {
try{
Log.d("MainActivity","移除屏障");
MessageQueue queue=mHandler.getLooper().getQueue();
Method method=MessageQueue.class.getDeclaredMethod("removeSyncBarrier",int.class);
method.setAccessible(true);
method.invoke(queue,token);//2
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 插入同步屏障
*/
@RequiresApi(api = Build.VERSION_CODES.M)
private void sendSyncBarrier() {
try{
Log.d("MainActivity","插入同步屏障");
MessageQueue queue=mHandler.getLooper().getQueue();
Method method=MessageQueue.class.getDeclaredMethod("postSyncBarrier");
method.setAccessible(true);
token= (int) method.invoke(queue);//1
}catch (Exception e){
e.printStackTrace();
}
}
}
其实demo就是发送一个同步消息和发送一个异步消息,插入同步屏障和移除同步屏障四个操作。
结果
只发送一个同步消息
2020-09-14 20:54:02.595 4960-4960/com.example.asyntask D/MainActivity: 插入普通消息
2020-09-14 20:54:03.597 4960-5110/com.example.asyntask D/MainActivity: 收到普通消息
因为代码中是延时1s,所以1S后收到普通消息。
只发一个异步消息
2020-09-14 20:55:58.091 4960-4960/com.example.asyntask D/MainActivity: 插入异步消息
2020-09-14 20:55:59.093 4960-5110/com.example.asyntask D/MainActivity: 收到异步消息
跟第一种一样。
先插入同步屏障,再发送同步和异步消息
2020-09-14 20:57:28.940 4960-4960/com.example.asyntask D/MainActivity: 插入同步屏障
2020-09-14 20:57:32.187 4960-4960/com.example.asyntask D/MainActivity: 插入普通消息
2020-09-14 20:57:33.472 4960-4960/com.example.asyntask D/MainActivity: 插入异步消息
2020-09-14 20:57:34.474 4960-5110/com.example.asyntask D/MainActivity: 收到异步消息
我们可以看到,只收到了异步消息,而同步消息没有收到
先插入同步屏障,再发送同步和异步消息。再移除同步屏障
2020-09-14 20:57:28.940 4960-4960/com.example.asyntask D/MainActivity: 插入同步屏障
2020-09-14 20:57:32.187 4960-4960/com.example.asyntask D/MainActivity: 插入普通消息
2020-09-14 20:57:33.472 4960-4960/com.example.asyntask D/MainActivity: 插入异步消息
2020-09-14 20:57:34.474 4960-5110/com.example.asyntask D/MainActivity: 收到异步消息
2020-09-14 20:58:59.713 4960-4960/com.example.asyntask D/MainActivity: 移除屏障
2020-09-14 20:58:59.714 4960-5110/com.example.asyntask D/MainActivity: 收到普通消息
同步消息和异步消息都有收到。
从这验证可以看出,满足前面说的对同步屏障的定义。
源码分析
插入同步屏障和移除同步屏障同时MessageQueue里面得方法。
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
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;
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 = msg;
}
return token;
}
}
可以看到,postSyncBarrier是public修饰得,为什么我们再demo中还要采用反射去获取这个方法,因为再注释中,该方法时隐藏的。
同步消息与异步消息的区别就是是否有设置target.
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到,插入一个消息的时候,会把msg.target = this,this就是指当前的handler.因为message最终会被对应的target也就是handler所处理。
而在插入同步屏障之后,target == null,然后将该message插入到队列中。
而怎么发送一条异步消息为demo中的注释3所示。
插入到队列中,我们该怎样保证优先取出异步队列了?
得继续从next()方法中去寻找答案。
next()
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
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()可以知道,首先会判断异步消息,判断得条件是target == null.然后做循环去消息,如果有消息,则判断是否到了时间。如果没有消息,则nextPollTimeoutMillis = -1;这个表示需要阻塞,得等到有消息取出时才唤醒。
具体可以看我得另外一篇文章。
Handler问题问答 link