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

Ult*_*o_m 2 android kotlin android-handler

我在 android 项目中执行了这段代码Kotlin,它将记录两条消息。如果我将其更改tokenChar,否则String它将仅打印一条消息,这是所需的行为。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
    )
Run Code Online (Sandbox Code Playgroud)

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

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

cac*_*acs 5

中的代码MessageQueue(处理消息删除)正在执行以下操作

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

// clearing code
}
Run Code Online (Sandbox Code Playgroud)

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

问题是它使用引用相等来比较令牌 - 如果它们不是完全相同的对象,如果您没有传入发布消息的同一令牌实例,则它不匹配并且什么也不会发生。


当您声明token2Int,这是 Kotlin 自己的“一种原语”,然后将其传递给需要实际对象的方法时,它会被装箱到Integer. 您会执行两次 - 一次是使用令牌发布消息,一次是使用令牌清除消息。它每次都会创建一个不同的(非引用相等)对象。

您可以通过存储令牌对象并比较它们来测试这一点:

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
)
Run Code Online (Sandbox Code Playgroud)
E/Comparing: Posted === cancelled: false
Run Code Online (Sandbox Code Playgroud)

至于为什么它能与Int121 一起工作,我假设它取决于 Java 的整数缓存。在幕后,Kotlin 代码(如果您这样做Show Bytecode然后反编译它)正在调用Integer.valueOf(token2). 以下是文档对此的说法

返回表示指定 int 值的 Integer 实例。如果不需要新的 Integer 实例,通常应优先使用此方法而不是构造函数 Integer(int),因为此方法可能通过缓存频繁请求的值来产生显着更好的空间和时间性能。此方法将始终缓存 -128 到 127(含)范围内的值,并且可能缓存此范围之外的其他值

因此,调用Integer(number)始终创建一个新对象,valueOf(number) 也可能创建一个新对象,也可能Integer返回之前创建的一个对象。值 121 将始终返回与之前相同的对象,这就是为什么您会获得与该对象的引用相等性,因此标记匹配。对于较大的数字,您将获得不同的对象(您可以在调试器中检查它们的 ID)


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

无论如何,呃,这是很多背景知识,可以帮助您找出它崩溃的原因!简而言之,不要这样做,不要让它自动装箱,创建一个令牌对象并保留对它的引用,以便稍后可以再次传递相同的实例;)

(这也适用于Strings - Java 有一个字符串池,如果您两次声明字符串文字,它会重用同一个对象,但也可能不会,因此将 a 分配String给变量更安全,然后您就知道它始终是同一个对象)