@[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至此完美解决此问题!