The code in MessageQueue
(which is handling the message deletion) is doing this:
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
// clearing code
}
where p
is a message in the queue, p.obj
is the token associated with it, and object
is the optional token you've passed in to clear messages for. So if you have passed in a token, and it matches the token of the current message, the message gets cleared.
The problem is it uses referential equality to compare tokens - if they're not exactly the same object, if you're not passing in the same token instance you posted the message with, it doesn't match and nothing happens.
When you declare token2
as an Int
, which is Kotlin's own "kind of a primitive", and then pass it into a method that requires an actual object, it gets boxed into an Integer
. And you do that twice - once to post a message with a token, once to clear messages with a token. It creates a different (non-referentially equal) object each time.
You can test this by storing the token objects and comparing them:
val handler = Handler()
//val token1: Long = 1001L
//val token2: Int = 121
val token1: Long = 1001L
val token2: Int = 1002
var postedToken: Any? = null
var cancelledToken: Any? = null
fun postIt(r: ()->Unit, token: Any, time: Long): Any {
handler.postAtTime(r, token, time)
return token
}
fun cancelIt(token: Any): Any {
handler.removeCallbacksAndMessages(token)
return token
}
postIt(
{
Log.e("postAtTime 1", " printed 1 ")
cancelledToken = cancelIt(token2)
// referential equality, triple-equals!
Log.e("Comparing", "Posted === cancelled: ${postedToken === cancelledToken}")
},
token1,
SystemClock.uptimeMillis() + 2000
)
postedToken = postIt(
{
Log.e("postAtTime 2", " printed 2 ")
},
token2,
SystemClock.uptimeMillis() + 4000
)
E/Comparing: Posted === cancelled: false
As for why it works with an Int
of 121, I'm assuming it's down to Java's integer cache. Under the hood, the Kotlin code (if you do Show Bytecode
and then decompile it) is calling Integer.valueOf(token2)
. Here's what the docs say about it:
Returns an Integer instance representing the specified int value. If a new Integer instance is not required, this method should generally be used in preference to the constructor 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.
So calling Integer(number)
will always create a new object, valueOf(number)
might create one, or it might return an Integer
object it created earlier. A value of 121 will always return the same object as before, which is why you're getting referential equality with that one, so the tokens match. For the larger number, you're getting different objects (you can check their IDs in the debugger)
But why does it work in Java and not Kotlin? I didn't test with Java, but it's possible the cache is working differently, maybe the compiler is able to be smarter about reusing the same object for an int
variable outside of the "definitely cached" range. Or if you're defining your token in your Java code as an Integer
instead of an int
then you're creating one object and passing it both times, so that will always match.
Anyhow uh that's a lot of background to try and help you work out why it's breaking! The short version is don't do that, don't let it autobox things, create a token object and keep a reference to it so you can pass the same instance again later ;)
(This goes for String
s too - Java has a String pool where it reuses the same object if you declare a string literal twice, but it might not, so it's safer to assign a String
to a variable, and then you know it's always the same object)
char
, in java its primitive right but here behaves differently fromint
? – Misestimate