参考链接:https://www.jianshu.com/p/bc79cc25829a
同步屏障
首先需要发送一个特殊消息作为屏障消息,当消息队列检测到了这种消息后,就会从这个消息开始,遍历后续的消息,只处理其中被标记为“异步”的消息,忽略同步消息(所以叫“同步屏障”),相当于给一部分消息开设了“VIP”优先通道。
demo
package com.example.testdemo;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.example.testdemo.databinding.ActivityMainBinding;
import java.lang.reflect.Method;
public class MainActivity extends AppCompatActivity {
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(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
initView(binding);
initHandler();
}
private void initView(ActivityMainBinding binding) {
binding.button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendSyncMessage();
}
});
binding.button2.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
@Override
public void onClick(View v) {
sendAsynMessage();
}
});
binding.button3.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onClick(View v) {
sendSyncBarrier();
}
});
binding.button4.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onClick(View v) {
removeSyncBarrier();
}
});
}
private void initHandler() {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what == MESSAGE_TYPE_SYNC) {
Log.d("MainActivity", "收到普通消息");
} else if (msg.what == MESSAGE_TYPE_ASYN) {
Log.d("MainActivity", "收到异步消息");
}
}
};
Looper.loop();
}
}).start();
}
@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();
}
}
}
打印log
D/MainActivity: 插入普通消息
D/MainActivity: 收到普通消息
D/MainActivity: 插入异步消息
D/MainActivity: 收到异步消息
//
D/MainActivity: 插入同步屏障
W/xample.testdem: Accessing hidden method Landroid/os/MessageQueue;->postSyncBarrier()I (greylist,test-api, reflection, allowed)
D/MainActivity: 插入普通消息
D/MainActivity: 插入异步消息
D/MainActivity: 收到异步消息
//
D/MainActivity: 移除屏障
W/xample.testdem: Accessing hidden method Landroid/os/MessageQueue;->removeSyncBarrier(I)V (greylist,test-api, reflection, allowed)
D/MainActivity: 收到普通消息
源码分析
插入同步屏障和移除同步屏障同时MessageQueue里面得方法。
@UnsupportedAppUsage
@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所处理。
如何保证优先取出异步队列?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;这个表示需要阻塞,得等到有消息取出时才唤醒。