Android Handler那些事儿,消息屏障?IdelHan
Handler 是Android SDK中用来处理异步消息的核心类,子线程可以通过handler来通知主线程进行ui更新。
备注:本文源码截图 基于Android sdk 28
Handler机制 消息发送主要流程如图
消息发送流程图.jpg一,Handler机制
1,MessageQueue创建
应用程序启动后,zygote fork一个应用进程后,和普通java程序一样,程序会首先执行ActivityThread中的main函数。在main函数中,程序首先会创建Looper对象并绑定到主线程中,然后开启loop循环。(ps:主线程loop循环不能退出)
public static void main(String[] args) {
......
Looper.prepareMainLooper();
......
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在prepareMainLooper方法中,最终会创建Looper,MessageQueue对象 以及创建native层MessageQueue对象。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
2,消息发送
使用Handler.sendMessageXXX或这 postDedayXXX发送消息后,最终会调用到SendMessageAtTime方法中。
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
......
return enqueueMessage(queue, msg, uptimeMillis);
}
然后调用MessageQueue.enqueueMessage将消息存到消息队列中。
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
//将新消息通过 when大小排序,存到消息队列中。
//消息队列实际上是一个单链表,when最小,即表示最先触发的消息,会放在链表头部
......
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
存入消息后,然后通过调用native方法 唤醒主线程进行消息处理。
3,消息循环
当应用程序启动,做完一些必要工作之后,便会开启Loop循环,除非系统异常,否则该循环不会停止。loop循环中,主要做两件事,第一,从消息队列中取消息。第二,进行消息分发处理。
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
......
Message msg = queue.next(); // might block
......
msg.target.dispatchMessage(msg);
......
}
Message next() {
for (;;) {
......
nativePollOnce(ptr, nextPollTimeoutMillis);
......
//从队列中取出当前需要处理的消息并返回。
//若当前消息队列不为空,则将nextPollTimeoutMillis赋值为下一次消息将要触发的时间。
//当前消息队列为空,则将nextPollTimeoutMillis赋值为-1,无限进入阻塞状态,
//直到下一条消息进入消息队列时, 唤醒。
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) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}
nextPollTimeoutMillis = 0;
......
}
}
MessageQueue.next() 方法 通过调用 native方法 nativePollOnce(ptr, nextPollTimeoutMillis)实现无消息处理时,进入阻塞的功能。
当nextPollTimeoutMillis 值为0时,该方法会立刻返回;
当nextPollTimeoutMillis 值为-1时,该方法会无限阻塞,直到被唤醒;
当nextPollTimeoutMillis 值大于0时,该方法会将该值设置为超时时间,阻塞到达一定时间后,返回;
4,消息分发
在loop循环中 ,通过调用 msg.target.dispatchMessage(msg) 进行消息的分发处理
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
......
msg.target.dispatchMessage(msg);
......
}
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
二,IdelHandler是什么,有啥用?
1,简介
使用当前线程的MessageQueue.addIdleHandler方法可以在消息队列中添加一个IdelHandler。
MessageQueue messageQueue = Looper.myQueue();
messageQueue.addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
return false;
}
});
当MessageQueue 阻塞时,即当前线程空闲时,会回调IdleHandler中的方法;
注:a,添加IdelHandler时,消息队列不为空,当消息处理完或者剩下消息还没到触发时间,会回调方法
b,当添加IdelHandler时,消息队列为空,则当时不会触发回调
当IdelHandler接口返回false时,表示该IdelHandler只执行一次,
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
......
synchronized (this) {
......
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
}
}
2,常用场景
a,延迟执行
例如,当启动Activity时,需要延时执行一些操作,以免启动过慢,我们常常使用以下方式延迟执行任务,但是在延迟时间上却不好控制。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
//do something
}
},1000);
}
其实,这时候使用IdelHandler 会更优雅
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
//do something
return false;
}
});
}
b,批量任务,任务密集,且只关注最终结果
例如,在开发一个IM类型的界面时,通常情况下,每次收到一个IM消息时,都会刷新一次界面,但是当短时间内, 收到多条消息时,就会刷新多次界面,容易造成卡顿,影响性能,此时就可以使用一个工作线程监听IM消息,在通过添加IdelHandler的方式通知界面刷新,避免短时间内多次刷新界面情况的发生。
三,消息屏障是啥?
在Android的消息机制中,其实有三种消息: 普通消息、异步消息及消息屏障。
消息屏障也是一种消息,但是它的target为 null。可以通过MessageQueue中的postSyncBarrier方法发送一个消息屏障(该方法为私有,需要反射调用)。
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;
//按照时间顺序将消息插入到消息队列中
......
return token;
}
}
在消息循环中,如果第一条消息就是屏障消息,就往后遍历,看看有没有异步消息:
如果没有,则无限休眠,等待被唤醒
如果有,就看离这个消息被触发时间还有多久,设置一个超时时间,继续休眠
异步消息和普通消息一样,只不过它被设置setAsynchronous 为true。有了这个标志位,消息机制会对它有些特别的处理,我们稍后说。
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private void createAsyncMessage(){
Message msg = Message.obtain();
msg.setAsynchronous(true);
}
所以消息屏障和异步消息的作用很明显,在设置消息屏障后,异步消息具有优先处理的权利。
这时候我们回顾将消息添加到消息队列中时,可以发现,其实并不是每一次添加消息时,都会唤醒线程。
当该消息插入到队列头时,会唤醒该线程;
当该消息没有插入到队列头,但队列头是屏障,且该消息是队列中 靠前的一个异步消息,则会唤醒线程,执行该消息;
调用MessageQueue.removeSyncBarrier 方法可以移除指定的消息屏障
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
......
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
四,ANR是什么?有啥关系?
ANR 即 Application Not Response, 是系统进程对应用行为的一种监控,如果应用程序没有在规定时间内完成任务的话,就会引起ANR。
ANR类型
Service Timeout: 前台服务20s, 后台服务200s
BroadcastQueue Timeout: 前台广播 10s,后台广播60s
ContentPrivider Timeout: 10s
InputDispatching Timeout: 5s
比如,在启动一个服务时, AMS端通过应用进程的Binder对象创建Service, 在scheduleCreateService()方法中 会调用到当前service的onCreate()生命周期函数;
private final void realStartServiceLocked(...){
...
bumpServiceExecutingLocked(r, execInFg, "create");
...
app.thread.scheduleCreateService(...);
...
serviceDoneExecutingLocked(...)
}
bumpServiceExecutingLocked()方法内部实际上会调用到scheduleServiceTimeoutLocked()方法,发送一个ActivityManagerService.SERVICE_TIMEOUT_MSG类型消息到AMS工作线程中。
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
消息的延时时间,如果是前台服务,延时20s, 如果是后台服务,延时200s;
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000;
// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
如果Service的创建 工作在 上诉消息的延时时间内完成,则会移除该消息,
private void serviceDoneExecutingLocked(...){
...
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
...
}
否则,在Handler正常收到这个消息后,就会进行服务超时处理,即弹出ANR对话框。
public void handleMessage(Message msg) {
case SERVICE_TIMEOUT_MSG: {
mServices.serviceTimeout((ProcessRecord)msg.obj);
}
五,性能优化
复杂情况下,可能会频繁调用sendMessage 往消息队列中,添加消息,导致消息积压,造成卡顿,
1,重复消息过滤
频繁发送同类型消息时,有可能队列中之前的消息还没有处理,又发了一条相同类型的消息,更新之前的数据,这时候,可以采用移除前一个消息的方法,优化消息队列。
private void sendTypeMsg(){
Message msg = Message.obtain();
msg.what = MSG_TYPE;
handler.removeMessages(MSG_TYPE);
handler.sendMessage(msg);
}
2,互斥消息取消
在发送消息时,优先将消息队列中还未处理的信息已经过时的消息 移除,优化队列
3,队列优化-复用消息
创建消息时,优先采用之前回收的消息,避免重复创建对象,引起GC
/**
* 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();
}
完~
(如果错误或不足,望指出, 大家共同进步)