Android Handler removeMessage(what,obj)失效问题

@[TOC](Android Handler removeMessage(what,obj)失效)

前言

最近开发过程中,突然发现一个不解的问题;项目中使用的Handler.removeMessages(what,obj)失效了!明明每次事件触发时都有移除掉之前的message,但仍然会多次响应,每次触发抛出去的message无法取消掉,被触发多次,出现问题(问题代码demo如下)

public class ObjectTestActivity extends AppCompatActivity {

    private static String TAG = "ObjectTestActivity";

    @BindView(R.id.btn_object_test)
    Button btnObjectTest;
    @BindView(R.id.btn_test2)
    Button btnTest2;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_object_test);
        ButterKnife.bind(this);
    }

    private Handler mHandler = new H(this) ;
    private static class H extends Handler {
        private WeakReference<ObjectTestActivity> mWeakReference ;
        public H(ObjectTestActivity activity) {
            mWeakReference = new WeakReference<>(activity);
        }
        @Override
        public void dispatchMessage(Message msg) {
            super.dispatchMessage(msg);
            ObjectTestActivity activity = mWeakReference.get();
            if(activity != null) {
                switch (msg.what) {
                    case MSG_WHAT :
                        Log.d(TAG, "dispatchMessage: got msg :" + msg.what + " > " + msg.obj );
                        break;
                    default:
                        break;
                }
            }
        }

    }
    public static final int TYPE_VALUE_A = 0x10016 ;
    public static final int MSG_WHAT = 2 ;
    @OnClick({R.id.btn_test2})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn_test2:
                int what = MSG_WHAT ;
                Object object = TYPE_VALUE_A ;
                mHandler.removeMessages(what , object);
                Log.d(TAG, "onViewClicked: btn_test2 clicked ...");
                Message message = mHandler.obtainMessage(what , object) ;
                mHandler.sendMessageDelayed(message , 1000);
                break;
            default:
                break;
        }
    }
}

多次点击后的打印,每次点击message都没有被remove掉

2020-05-24 22:30:36.126 938-938/com.boom.boomapplication D/ObjectTestActivity: onViewClicked: btn_test2 clicked ...
2020-05-24 22:30:36.653 938-938/com.boom.boomapplication D/ObjectTestActivity: onViewClicked: btn_test2 clicked ...
2020-05-24 22:30:37.106 938-938/com.boom.boomapplication D/ObjectTestActivity: onViewClicked: btn_test2 clicked ...
2020-05-24 22:30:37.131 938-938/com.boom.boomapplication D/ObjectTestActivity: dispatchMessage: got msg :2 > 65558
2020-05-24 22:30:37.615 938-938/com.boom.boomapplication D/ObjectTestActivity: onViewClicked: btn_test2 clicked ...
2020-05-24 22:30:37.659 938-938/com.boom.boomapplication D/ObjectTestActivity: dispatchMessage: got msg :2 > 65558
2020-05-24 22:30:38.111 938-938/com.boom.boomapplication D/ObjectTestActivity: onViewClicked: btn_test2 clicked ...
2020-05-24 22:30:38.142 938-938/com.boom.boomapplication D/ObjectTestActivity: dispatchMessage: got msg :2 > 65558
2020-05-24 22:30:38.526 938-938/com.boom.boomapplication D/ObjectTestActivity: onViewClicked: btn_test2 clicked ...
2020-05-24 22:30:38.655 938-938/com.boom.boomapplication D/ObjectTestActivity: dispatchMessage: got msg :2 > 65558
2020-05-24 22:30:39.112 938-938/com.boom.boomapplication D/ObjectTestActivity: dispatchMessage: got msg :2 > 65558
2020-05-24 22:30:39.527 938-938/com.boom.boomapplication D/ObjectTestActivity: dispatchMessage: got msg :2 > 65558

当然,问题还是要解决的,来吧,上源码

Handler.removeMessages()

removeMessages()根据参数分为如下两个方法;api写的很明白,分别为移除what标识的所有message和移除what标识并且指定obj的message

/**
     * Remove any pending posts of messages with code 'what' that are in the
     * message queue.
     */
    public final void removeMessages(int what) {
        mQueue.removeMessages(this, what, null);
    }
/**
     * Remove any pending posts of messages with code 'what' and whose obj is
     * 'object' that are in the message queue.  If <var>object</var> is null,
     * all messages will be removed.
     */
    public final void removeMessages(int what, Object object) {
        mQueue.removeMessages(this, what, object);
    }

两个方法相同的调用了MessageQueue中的removeMessages()

void removeMessages(Handler h, int what, Object object) {
        if (h == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;
			//My Note:对比message的what与obj是否一致,并进行message的回收(Message.recycleUnchecked()具体逻辑此处不讨论,有兴趣可自行研究🤗);Em..这似乎也没错 U•ェ•*U
            // Remove all messages at front.
            while (p != null && p.target == h && p.what == what
                   && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();
                p = n;
            }
            
			// My Note:不太理解这里进行重复移除的逻辑,看上去似乎与上面无差,有知道的大佬欢迎评论区盖楼,thx
            // Remove all messages after front.
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && n.what == what
                        && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }

通过对比Message的what与obj进行消息的移除,(p.what == what) && (p.obj == obj),what是int值,那就是obj判断为false?

回到代码中,Object传入的是一个int值,难道是这个?

{    
public static final int TYPE_VALUE_A = 0x10016 ;

    {
    ...
        Object object = TYPE_VALUE_A ;
        ...
        Message message = mHandler.obtainMessage(what , object) ;
    ...
    }
}

在原有的代码上修改如下

...
			case R.id.btn_test2:
                int what = MSG_WHAT ;
                Object object = TYPE_VALUE_A ;
                //新建一个相同值的obj比较是否相同
                Object object2 = TYPE_VALUE_A ;
                mHandler.removeMessages(what , object);
                Log.d(TAG, "onViewClicked: btn_test2 clicked ...object == object2 ?" + (object == object2) );
                Message message = mHandler.obtainMessage(what , object) ;
                mHandler.sendMessageDelayed(message , 1000);
                break;
...

false!看来确实是每次传入的obj不一致导致的,继续看

2020-05-24 23:29:37.224 2020-2020/com.boom.boomapplication D/ObjectTestActivity: onViewClicked: btn_test2 clicked ...object = object2 ?false
2020-05-24 23:29:38.235 2020-2020/com.boom.boomapplication D/ObjectTestActivity: dispatchMessage: got msg :2 > 65558
2020-05-24 23:29:38.407 2020-2020/com.boom.boomapplication D/ObjectTestActivity: onViewClicked: btn_test2 clicked ...object = object2 ?false
2020-05-24 23:29:39.369 2020-2020/com.boom.boomapplication D/ObjectTestActivity: onViewClicked: btn_test2 clicked ...object = object2 ?false
2020-05-24 23:29:39.424 2020-2020/com.boom.boomapplication D/ObjectTestActivity: dispatchMessage: got msg :2 > 65558
2020-05-24 23:29:40.371 2020-2020/com.boom.boomapplication D/ObjectTestActivity: dispatchMessage: got msg :2 > 65558

Integer的自动装箱/拆箱及缓存机制

Java在JDK1.5开始对基本数据类型提供了自动装箱和自动拆箱机制;可将基本数据类型直接赋值给包装类对象
eg:
Integer intObj = 5 ;
int it = intObj ;

我们代码中也是将一个int的基本类型直接复制给Object对象,首先将int会自动装箱为Integer对象,然后再将Integer对象的引用传给Object对象,完成传递

大家都知道对象的 == 判断是根据引用地址进行判别的,如果两个对象地址不一致那么对比的结果就会是false;引出Integer的一个特性 - - - 缓存机制

Integer类内部存在一个IntegerCache的静态内部类,IntegerCache里又存在一处静态代码块;第一次使用Integer时即会初始化该代码块,完成缓存逻辑;默认缓存[-128,127]

Integer.java

/**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */

    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

Integer的自动装箱调用的是内部的静态方法valueOf(),到这里已经大致清晰了;valueOf()获取Integer对象时首先会通过low与high判断该值是否包含于缓存中,如包含,则返回缓存中已有的Integer对象,反之创建新的Integer对象

    /**
     * Returns an {@code Integer} instance representing the specified
     * {@code int} value.  If a new {@code Integer} instance is not
     * required, this method should generally be used in preference to
     * the constructor {@link #Integer(int)}, as this method is likely
     * to yield significantly better space and time performance by
     * caching frequently requested values.
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @since  1.5
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

回头看看我们之前Handler中使用的int值;

 // 值为16进制,转为十进制 = 65558 ,大于默认缓存的127,因此重新创建了对象
public static final int TYPE_VALUE_A = 0x10016 ;
//找到原因后问题也很好解决,增加一个静态的Integer对象,每次Handler中使用该值即可
public static final Integer TYPE_INTEGER_VALUE = TYPE_VALUE_A;

为了验证,试了下将值改为126,一切正常;改为使用静态Integer对象,一切正常;OK至此完美解决此问题!

总结:
这次的问题其实并不是什么高难度问题,但要解决并彻底理解还是花费了一点时间,因此日常开发中要养成阅读源码的习惯;因为:阅读源码是离优秀的程序员距离最近的时候!

ps:第一次写博客,如内容有误欢迎指正评论,谢谢🤪

参考:
[1]详解Java的自动装箱与拆箱(Autoboxing and unboxing)

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值