前言:在上一篇文章中《技术记录---Toast频繁弹出问题及其流程分析》我们对Toast的先cancel后show的不能弹出Toast的原因就行了分析;可不知道你有没有发现,我们的代码入下:
mToast.cancel();//会使得 mTN.mView和mTN.mNextView 都为null
mToast.setText("new message");
mToast.show();// 会把mTN.mNextView重新赋值(不为null)
既然我们是先赋值 mTN.mView =null 和 mTN.mNextView = null;然后在show的时候,我们又把 mTN.mNextView =view;这样 if(mView != mNextView)判断时候,不应该满足条件么?!这样toast应该显示才对呀?可事实上并非如此。
我们调用 toast.cancel()的时候调用的是mTN.hide()方法,mTN.hide()方法如下:
/**
* schedule handleHide into the right thread
*/
@Override
public void hide() {
//发送消息
mHandler.post(mHide);
}
mHide是一个 runnable对象
final Runnable mHide = new Runnable() {
@Override
public void run() {
//处理隐藏操作
handleHide();
// Don't do this in handleHide() because it is also invoked by handleShow()
mNextView = null;
}
};
由此可见,当执行完 toast.cancel()的时候,里面的操作其实是发送了一个消息(定义A消息)出去,然后继续走下面的代码,toast.show(); 当show动作走完的时候,程序会回过头来去执行A消息;这样尽管 show已经对mNextView进行赋值,可是当执行A消息的时候,会对 mNextView再次赋值成 null。消息图如下所示:
由图中可以看出,正是由于Handler的消息机制,保证了 mView和mNextView的最终值是null,导致了先cancel后show不会弹出toast。
Handler问题:
说到Handler,今天遇到一个问题:就是handler 发送了消息,却在handleMessage的地方没有收到消息,甚是奇怪。代码如下:
//创建一个Thread,使得 handler 在子线程中执行耗时操作,而不是在主线程中执行。
HandlerThread handlerThread = new HandlerThread("handler_thread");
handlerThread.start();
// 创建一个Handler 用前面创建的 handlerThread.getLooper()
mHandler = new Handler(handlerThread.getLooper());
以上代码,是多么正常的实例化mHandler;然后通过 mHandler.sendEmptyMessage(0); 发送一个消息出去,这是又多么简单的一次操作。可结果却是多么的出人意料。因为同样的代码,同样的操作,可程序在某些情况下,却收不到了消息?!无奈,只能去分析下源码,看看为什么没有收到消息?!
问题根因跟踪分析:
1、当我们调用 sendEmptyMessage()方法的时候,最终会调用到 Handler.enqueueMessage 方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
2、然后跟踪到 MessageQueue.enqueueMessage方法。
final boolean enqueueMessage(Message msg, long when) {
........
boolean needWake;
synchronized (this) {
// 此处会判断 是否消息队列已经退出,如果退出返回false。
if (mQuiting) {
RuntimeException e = new RuntimeException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
}
................
}
}
if (needWake) {
nativeWake(mPtr);
}
return true;
}
final void quit() {
if (!mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuiting) {
return;
}
mQuiting = true;
}
nativeWake(mPtr);
}
由此可见,当调用 quit的时候会对mQuiting进行赋值true,这样在以后的发送消息的时候,就不会添加到消息队列当中去了。
而官方的sendEmptyMessage定义也证明了这一点。
/**
* Sends a Message containing only the what value.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
(通常情况下不会返回false,除非消息队列被强制退出了)
*/
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
总结:
通过上面分析,问题的原因就有可能是不合法的调用了 quit方法,搜索项目,发现有些地方,在某些情况下会不和业务逻辑的去执行 quit方法,导致无法处理的消息。再就是可能的一个原因,就是 HandlerThread可能会系统强制回收了。
所以,为了安全,我们在调用 发送消息的时候最好做一个判断,如果消息发送不成功则重新创建handler。代码如下:
private void createHandler(){
HandlerThread handlerThread = new HandlerThread("handler_thread");
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper());
}
private void sendMessage(int what){
boolean isOk = mHandler.sendEmptyMessage(what);
if (!isOk){
//创建创建handler
createHandler();
isOk = mHandler.sendEmptyMessage(what);
}
}