技术记录---Handle收不到消息的问题分析

前言:在上一篇文章中《技术记录---Toast频繁弹出问题及其流程分析》我们对Toast的先cancelshow的不能弹出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的消息机制,保证了 mViewmNextView的最终值是null,导致了先cancelshow不会弹出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;
}

3、那么mQuitting 什么情况会赋值 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);
    }
}
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页