【问题标题】:Android Handler callback not removed for token type Int or Long (*Kotlin)未删除令牌类型 Int 或 Long (*Kotlin) 的 Android 处理程序回调
【发布时间】:2020-06-16 09:58:19
【问题描述】:

我在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 中对于 IntLong 类型的令牌,处理程序不删除回调?

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

【问题讨论】:

  • 我试试这段代码,它只打印一个
  • 可能与我的机器有关,但我与其他同事尝试过,他们也遇到了同样的问题
  • 作为我的客人,Long 和 Int 在 Kotlin 中是 JVM 代码的原始类型,这意味着当您使用 Object 调用 removeCallbacksAndMessages 时,它将自动装箱并在 Int 和 Long 的情况下创建新对象。在 Kotlin 中,我建议您使用 String 或 Long?诠释?它是对象类型。
  • char 怎么样,在 java 中它的原始权利,但这里的行为与 int 不同?
  • 它也为我打印一次。

标签: android kotlin android-handler


【解决方案1】:

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

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

// clearing code
}

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

问题在于它使用引用相等来比较标记 - 如果它们不是完全相同的对象,如果您没有传递与您发布消息的相同标记实例,它不匹配并且没有任何反应。


当您将 token2 声明为 Int(这是 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
)
E/Comparing: Posted === cancelled: false

至于为什么它与 121 的 Int 一起工作,我假设它取决于 Java 的整数缓存。在底层,Kotlin 代码(如果你执行Show Bytecode 然后反编译它)调用Integer.valueOf(token2)Here's what the docs say about it:

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

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


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

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

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

【讨论】:

  • 谢谢你的回答,我试过Int因为这个答案stackoverflow.com/a/30905295/2736039,但是我错了
  • 是的,这就是整个自动装箱的事情,它可能会或可能不会起作用,具体取决于 JVM 是否喜欢你;)当你需要某种令牌时,最好的办法是始终自己创建一个对象(无论你喜欢什么类型,只要它是 Object 就像 Integer 而不是原始类型)然后坚持下去,这样你以后可以再次传递完全相同的对象。关键是对象本身,而不是内容,所以你需要掌握它!
猜你喜欢
  • 2021-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-18
  • 1970-01-01
  • 2021-12-15
  • 1970-01-01
相关资源
最近更新 更多