Android Обратный вызов обработчика не удален для типа токена Int или Long (*Kotlin) - PullRequest
2 голосов
/ 16 июня 2020

Я выполняю этот код в проекте Kotlin android, и он будет записывать оба сообщения. Если я изменю 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
    )

Мой вопрос: почему в Kotlin для токена типа Int, Long обработчик не удаляет обратный вызов?

EDIT Если я попробую с закомментированными значениями, он работает

1 Ответ

2 голосов
/ 25 августа 2020

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

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

// clearing code
}

где p - сообщение в очереди, p.obj - это связанный с ним токен, а object - дополнительный токен, который вы передали для очистки сообщений. Таким образом, если вы передали токен , и он соответствует токену текущего сообщения, сообщение будет очищено.

Проблема в том, что для сравнения токенов используется ссылочное равенство - если они ' re не совсем тот же объект, если вы не передаете тот же экземпляр токена, с которым отправили сообщение, он не совпадает и ничего не происходит.

Когда вы объявляете 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 а не Kotlin? Я не тестировал с Java, но, возможно, кеш работает по-другому, возможно, компилятор сможет более разумно использовать один и тот же объект для переменной int за пределами диапазона «определенно кэшированного». Или, если вы определяете свой токен в коде Java как Integer вместо int, тогда вы создаете один объект и передаете его оба раза, так что он всегда будет совпадать.

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

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

...