协程中一个lateinit变量初始化成功,另一个失败的原因排查
为什么todoAdapter能正常操作而dropdownAdapter未初始化抛出异常?
在Android项目的MainActivity中,通过lifecycleScope发起两个Retrofit异步请求,使用async与await从API获取数据时,todoAdapter可成功初始化并赋值,但dropdownAdapter未初始化,抛出kotlin.UninitializedPropertyAccessException异常。
问题代码
package com.example.retrofittodo import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log import android.widget.ArrayAdapter import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.example.retrofittodo.databinding.ActivityMainBinding import kotlinx.coroutines.async import retrofit2.HttpException import retrofit2.Response import java.io.IOException const val TAG = "MainActivity" class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var todoAdapter: TodoAdapter private lateinit var dropdownAdapter: ArrayAdapter<User> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // Get data lifecycleScope.launchWhenCreated { binding.progressBar.isVisible = true val todosDeferred = async { RetrofitInstance.api.getTodos(null) } val usersDeferred = async { RetrofitInstance.api.getUsers() } val todoResponse = try { todosDeferred.await() } catch (e: IOException) { // Most likely no internet connection or maybe output stream closed Log.e(TAG, "IOException, you might not have internet connection") binding.progressBar.isVisible = false return@launchWhenCreated } catch (e: HttpException) { // If response code does not start with digit 2 then something is unusual Log.e(TAG, "HttpException, unexpected response") binding.progressBar.isVisible = false return@launchWhenCreated } val userResponse = try { usersDeferred.await() } catch (e: IOException) { // Most likely no internet connection or maybe output stream closed Log.e(TAG, "IOException, you might not have internet connection") binding.progressBar.isVisible = false return@launchWhenCreated } catch (e: HttpException) { // If response code does not start with digit 2 then something is unusual Log.e(TAG, "HttpException, unexpected response") binding.progressBar.isVisible = false return@launchWhenCreated } if (checkResponse(todoResponse)) { todoAdapter.todos = todoResponse.body()!! //succeeds } else { Log.e(TAG, "Todo response not successful") } if (checkResponse(userResponse)) { dropdownAdapter = ArrayAdapter(this@MainActivity, R.layout.item_user, userResponse.body()!!) // fails } else { Log.e(TAG, "User response not successful") } binding.progressBar.isVisible = false } setupRecyclerView() binding.dropdownUser.setAdapter(dropdownAdapter) } private fun <T : Any> checkResponse(response: Response<List<T>>) = response.isSuccessful && response.body() != null private fun setupRecyclerView() = binding.rvTodos.apply { todoAdapter = TodoAdapter() adapter = todoAdapter layoutManager = LinearLayoutManager(this@MainActivity) } }
报错日志
E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.retrofittodo, PID: 8917 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.retrofittodo/com.example.retrofittodo.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property dropdownAdapter has not been initialized at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3635) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3792) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7839) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003) Caused by: kotlin.UninitializedPropertyAccessException: lateinit property dropdownAdapter has not been initialized at com.example.retrofittodo.MainActivity.onCreate(MainActivity.kt:119) at android.app.Activity.performCreate(Activity.java:8051) at android.app.Activity.performCreate(Activity.java:8031) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1329) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3608) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3792) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7839) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003) W/System: A resource failed to call close.
原因分析
核心问题是代码执行顺序和初始化时机的差异:
- todoAdapter的初始化是同步执行的:在
onCreate中调用setupRecyclerView()时,该方法内部直接实例化了todoAdapter = TodoAdapter(),所以当协程后续给todoAdapter.todos赋值时,todoAdapter已经完成初始化,不会触发异常。 - dropdownAdapter的初始化是异步延迟的:
dropdownAdapter的实例化被放在了协程内部的网络请求回调里,而binding.dropdownUser.setAdapter(dropdownAdapter)是在协程启动后立刻同步执行的。由于协程是异步挂起执行的,网络请求需要时间,所以setAdapter执行时,dropdownAdapter还没有被赋值,此时访问lateinit修饰的变量就会抛出UninitializedPropertyAccessException异常。
修复方案
方案一:将setAdapter移到协程内部初始化之后
把设置适配器的代码放在dropdownAdapter实例化之后,确保初始化完成再使用:
if (checkResponse(userResponse)) { dropdownAdapter = ArrayAdapter(this@MainActivity, R.layout.item_user, userResponse.body()!!) // 移到这里执行 binding.dropdownUser.setAdapter(dropdownAdapter) }
方案二:提前初始化dropdownAdapter
给dropdownAdapter一个默认的空适配器初始化,避免lateinit变量未赋值就被访问,后续再更新数据:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) setupRecyclerView() // 提前初始化空适配器 dropdownAdapter = ArrayAdapter(this, R.layout.item_user, emptyList()) binding.dropdownUser.setAdapter(dropdownAdapter) // Get data lifecycleScope.launchWhenCreated { // ...其他代码 if (checkResponse(userResponse)) { // 更新适配器数据 dropdownAdapter.clear() dropdownAdapter.addAll(userResponse.body()!!) } } }
内容的提问来源于stack exchange,提问作者Siddharth Manthan
相关产品推荐
相关产品推荐

