свойству lateinit недостаточно скоро присвоено значение?

#kotlin #kotlin-lateinit

#kotlin #kotlin-lateinit

Вопрос:

Мой код работает 9 999 из 10 000 раз. Но примерно раз в 10 000 раз приложение завершает UninitializedPropertyAccessException работу с ошибкой, это проблематично, поскольку в производстве находится около 20 000 ~ 30 000 устройств Android с моим кодом.

Похоже, что иногда фактическое присвоение lateinit переменной происходит недостаточно быстро (и, следовательно, вызывает исключение выше).

У кого-нибудь еще была подобная проблема? Каково было ваше решение?

TcpService.kt

класс TcpService: Service() {

     private lateinit var mTcpClient: TcpClient

    override fun onCreate() {
        registerReceiver(object: BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                when(intent.action) {
                    // This will cause an Uninitialized Property Exception 1 in 10,000 times
                    ACTION_SEND_MESSAGE -> mTcpClient.sendMessageAsync(intent.getStringExtra(EXTRA_NEW_MESSAGE)
                }
            }
        }, IntentFilter(ACTION_SEND_MESSAGE)
        })
    }

    override fun onStart() {
        mTcpClient = mTcpClient()
    }

    // ...

}
  

TcpClient.kt

 class TcpClient() {

    init {
        // ...
        sendBroadcast(Intent(ACTION_SEND_MESSAGE).putExtra(EXTRA_NEW_MESSAGE, "Message Contents")
    }

    // ...

}
  

Комментарии:

1. Параллельная трансляция выполняется до инициализации TcpClient объекта.

2. Почему бы вам не переместить создание экземпляра перед созданием получателя onCreate() ?

3. @Tenfour04 Это может показаться очевидным решением, но этот пост представляет собой чрезвычайно упрощенную версию реального кода, где я не могу создать экземпляр TcpClient перед получателем

4. @Alirezaa Спасибо, что указали на это — вероятно, это то, что я делаю неправильно. Я переработал свой код, чтобы НЕ было трансляции в блоке инициализации. Только время покажет, правильное ли это решение, но я настроен оптимистично 🙂

5. Было бы плохо иметь start() метод на TcpClient , и поместить туда этот sendBroadcast бит? То, что вы здесь делаете, — это вызов метода в экземпляре до завершения работы конструктора (включая init блок) — обычно в широковещательной системе возникает асинхронная задержка, но я думаю, иногда она просто запускается напрямую и немедленно? Сделав это отдельным шагом, который вы вызываете после mTcpClient = mTcpClient() того, как поле обязательно будет назначено

Ответ №1:

ах, это случилось со мной однажды, и я потратил целый день, а потом решил это (до сих пор не знаю, как), но почему бы вам не сделать mTcpClient null, а затем проверить null:

  `when(intent.action) {
 // This will cause an Uninitialized Property Exception 1 in 10,000 times
                ACTION_SEND_MESSAGE ->{
if(mTcpClient != null){
mTcpClient.sendMessageAsync(intent.getStringExtra(EXTRA_NEW_MESSAGE)  
}else{
mTcpClient=mTcpClient()
mTcpClient.sendMessageAsync(intent.getStringExtra(EXTRA_NEW_MESSAGE)
}
  }
    }`
  

что-то в этом роде, я не профессионал, извините

Ответ №2:

Вместо того, чтобы присваивать свойству значение null, а затем проверять значение null, что может быть опасно в некоторых сценариях, вы можете использовать isInitialized метод для своих lateInit свойств :

     when (intent.action) {
        if (::mTcpClient.isInitialized) { ACTION_SEND_MESSAGE ->
          mTcpClient.sendMessageAsync(intent.getStringExtra(EXTRA_NEW_MESSAGE)
        }
    }
  

Комментарии:

1. Я бы сказал, что использовать lateinit переменную и спрашивать, инициализирована ли она, — это плохая практика или запах кода. Предпочтительнее использовать необязательный тип и нулевую проверку.

2. лично я считаю isInitialized , что это чище, чем делать ненулевое поле обнуляемым, чтобы вы могли отложить его инициализацию, особенно в чем-то вроде Kotlin, где вам придется писать весь свой код вокруг этой обнуляемости (или перчить его, !! что определенно является запахом кода)

3. initialised = true в этом случае вы могли бы просто установить флаг!