Обратный вызов обработчика Android не удален для типа токена Int или Long (*Kotlin)

У меня есть этот код, выполненный в проекте Android Kotlin, и он будет регистрировать оба сообщения. Если я изменю token на Char или String, он напечатает только одно сообщение, которое является желаемым поведением. Тот же вариант использования в java-проекте в Android работает как надо.

    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
    )

У меня вопрос, почему в Котлине для токена типа Int, Long обработчик не удаляет callback?

ИЗМЕНИТЬ Если я попытаюсь использовать закомментированные значения, это сработает


person Ultimo_m    schedule 16.06.2020    source источник
comment
Я пробую этот код, и он печатает только один   -  person Công Hải    schedule 16.06.2020
comment
возможно, это связано с моей машиной, но я пробовал с другими коллегами, и у них была такая же проблема.   -  person Ultimo_m    schedule 16.06.2020
comment
Как я гость, Long и Int являются примитивными типами в Kotlin при коде JVM, это означает, что когда вы вызываете removeCallbacksAndMessages с объектом, он будет автоматически упаковываться и создавать новый объект в случае Int и Long. В Kotlin я рекомендую вам использовать String или Long? Внутр.? это тип объекта.   -  person Công Hải    schedule 16.06.2020
comment
как насчет char, в java это правильно, но здесь ведет себя иначе, чем int?   -  person Ultimo_m    schedule 16.06.2020
comment
У меня тоже один раз печатает.   -  person aminography    schedule 16.06.2020
comment
хорошо, извините за путаницу со значениями, которые вы тестировали, это также сработало для меня, но попробуйте сейчас с новыми значениями и посмотрите, я пробовал разные варианты использования   -  person Ultimo_m    schedule 16.06.2020
comment
кто-нибудь из вас пробовал новые значения, если вы можете воспроизвести мою проблему? @аминография   -  person Ultimo_m    schedule 16.06.2020


Ответы (1)


Код в MessageQueue (обрабатывающий удаление сообщения): делаем это:

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

Что касается того, почему он работает с Int из 121, я предполагаю, что это связано с целочисленным кешем Java. Под капотом код Kotlin (если вы сделаете Show Bytecode, а затем декомпилируете его) вызывает Integer.valueOf(token2). Вот что говорится об этом в документации:

Возвращает экземпляр Integer, представляющий указанное значение int. Если новый экземпляр Integer не требуется, этот метод обычно следует использовать вместо конструктора Integer(int), так как этот метод, скорее всего, даст значительно лучшую производительность пространства и времени за счет кэширования часто запрашиваемых значений. . Этот метод всегда будет кэшировать значения в диапазоне от -128 до 127 включительно и может кэшировать другие значения за пределами этого диапазона.

Таким образом, вызов Integer(number) будет всегда создавать новый объект, valueOf(number) может создать его или может возвращать объект Integer, созданный ранее. Значение 121 всегда возвращает тот же объект, что и раньше, поэтому вы получаете ссылочное равенство с этим, поэтому токены совпадают. Для большего числа вы получаете разные объекты (вы можете проверить их идентификаторы в отладчике)


Но почему это работает на Java, а не на Котлине? Я не тестировал Java, но, возможно, кеш работает по-другому, возможно, компилятор может быть умнее при повторном использовании одного и того же объекта для переменной int за пределами определенно кэшированного диапазона. Или, если вы определяете свой токен в своем Java-коде как Integer вместо int, вы создаете один объект и передаете его оба раза, так что это всегда будет совпадать.

Во всяком случае, это много предыстории, чтобы попытаться помочь вам понять, почему он ломается! Короткая версия: не делайте этого, не позволяйте ему автоматически упаковывать вещи, создайте объект токена и сохраните ссылку на него, чтобы вы могли снова передать тот же экземпляр позже;)

(Это относится и к Strings — в Java есть пул строк, где он повторно использует один и тот же объект, если вы дважды объявляете строковый литерал, но это может быть и нет, поэтому безопаснее присвоить String переменной, и тогда вы знаете, что это всегда один и тот же объект)

person cactustictacs    schedule 25.08.2020
comment
Спасибо за ответ, я попробовал Int из-за этого ответа stackoverflow.com/a/30905295/2736039, но ошибся - person Ultimo_m; 26.08.2020
comment
Да, это вся эта штука с автоупаковкой, которая может работать или не работать в зависимости от того, нравитесь ли вы JVM;) Лучшее, что можно сделать, когда вам нужен какой-то токен, - это всегда создавать объект самостоятельно (любой тип, который вам нравится, просто так пока это Object как Integer, а не примитив), а затем удерживайте его, чтобы вы могли снова передать тот же самый объект позже. Ключом является сам объект, а не его содержимое, поэтому вам нужно держать его в руках! - person cactustictacs; 27.08.2020