#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
в этом случае вы могли бы просто установить флаг!