Android 未删除令牌类型 Int 或 Long 的处理程序回调 (*Kotlin)

Android Handler callback not removed for token type Int or Long (*Kotlin)

我在 Kotlin android 项目中执行了这段代码,它会记录这两条消息。如果我将 token 更改为 CharString,它将只打印一条消息,这是想要的行为。 android 中的 java 项目中的相同用例可以正常工作。

    val handler = Handler()
    //val token1: Long = 1001L
    //val token2: Int = 121
    val token1: Long = 1001L
    val token2: Int = 1002

    handler.postAtTime(
        {
            Log.e("postAtTime 1", " printed 1 ")
            handler.removeCallbacksAndMessages(token2)
        },
        token1,
        SystemClock.uptimeMillis() + 2000
    )

    handler.postAtTime(
        {
            Log.e("postAtTime 2", " printed 2 ")
        },
        token2,
        SystemClock.uptimeMillis() + 4000
    )

我的问题是为什么在 Kotlin 中对于 Int 类型的标记,Long 处理程序不删除回调?

编辑 如果我尝试使用注释值,它会起作用

MessageQueue 中的代码(处理消息删除)是 doing this:

while (p != null && p.target == h
                    && (object == null || p.obj == object)) {

// clearing code
}

其中 p 是队列中的一条消息,p.obj 是与之关联的标记,object 是您传入的用于清除消息的可选标记。因此,如果您 已经 传递了一个令牌,并且它与当前消息的令牌相匹配,则消息将被清除。

问题是它使用引用相等性来比较令牌 - 如果它们不是完全相同的对象,如果您没有传入与 post 编辑消息时使用的相同令牌实例,它不会'匹配但没有任何反应。


当您将 token2 声明为 Int,这是 Kotlin 自己的“原始类型”,然后将其传递给需要实际对象的方法时,它会被装箱到一个Integer。你这样做了两次 - 一次是 post 带有令牌的消息,一次是清除带有令牌的消息。它每次都会创建一个不同的(non-referentially 相等的)对象。

您可以通过存储令牌对象并比较它们来对此进行测试:

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

至于为什么它与 121 的 Int 一起工作,我假设它下降到 Java 的整数缓存。在幕后,Kotlin 代码(如果您执行 Show Bytecode 然后反编译它)正在调用 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.

所以调用Integer(number)总是创建一个新对象,valueOf(number)可能创建一个,或者它可能 return 它之前创建的 Integer 对象。值 121 将 always return 与以前相同的对象,这就是为什么你得到与那个对象的引用相等,所以标记匹配。对于更大的数字,你会得到不同的对象(你可以在调试器中检查它们的 ID)


但为什么它在 Java 而不是 Kotlin 中工作?我没有使用 Java 进行测试,但缓存的工作方式可能有所不同,也许编译器能够更聪明地为“绝对缓存”范围之外的 int 变量重用同一对象.或者,如果您在 Java 代码中将令牌定义为 Integer 而不是 int,那么您将创建一个对象并将其传递两次,因此它始终匹配。

无论如何,呃,有很多背景知识可以帮助您弄清楚它为什么会坏!简短的版本是不要那样做,不要让它自动装箱,创建一个令牌对象并保留对它的引用,以便您稍后可以再次传递相同的实例;)

(这也适用于 Strings - Java 有一个字符串池,如果你声明一个字符串文字两次,它会重用同一个对象,但它 可能不会,所以把一个String赋值给一个变量会更安全,然后你就知道它总是同一个对象)