Android中线程内初始化lateinit变量失败问题求助
Android中使用Google Sheets API时lateinit变量未初始化问题
我在Android应用中使用Google Sheets API,尝试在独立线程中获取凭证。创建了GoogleSheets类用于处理电子表格的凭证获取和单元格值操作,在Fragment类开头声明了实例变量private lateinit var sheets: GoogleSheets,并在按钮点击事件中尝试初始化:
load.setOnClickListener(View.OnClickListener { Thread { sheets = GoogleSheets(requireContext(), "1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA") }.start() println(sheets) println(sheets.getValues("A1")) })
运行程序时抛出未初始化异常:
kotlin.UninitializedPropertyAccessException: lateinit property sheets has not been initialized
完整Fragment代码如下:
import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment import android.provider.Settings import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.EditText import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import com.example.frcscout22.GoogleSheets import com.example.frcscout22.R // TODO: AUTOMATICALLY SWITCH TO DATA TAB AFTER LOAD OR CREATE NEW class Home: Fragment(R.layout.fragment_home) { private lateinit var sheets: GoogleSheets private val STORAGE_PERMISSION_CODE = 100 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) } @RequiresApi(Build.VERSION_CODES.P) override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view: View = inflater.inflate(R.layout.fragment_home, container, false) val load = view.findViewById<Button>(R.id.button3) val new = view.findViewById<Button>(R.id.button4) val editText = view.findViewById<EditText>(R.id.editTextTextPersonName) if (!checkPermission()) { println("requested") requestPermission() } new.setOnClickListener(View.OnClickListener { val sheets = GoogleSheets(requireContext(),"1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA") sheets.setValues("A1", "this is a test", "USER_ENTERED") println(sheets.getValues("A1").values) }) load.setOnClickListener(View.OnClickListener { Thread { sheets = GoogleSheets(requireContext(), "1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA") }.start() println(sheets) println(sheets.getValues("A1")) }) return view } private fun requestPermission(){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){ //Android is 11(R) or above try { val intent = Intent() intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION val uri = Uri.fromParts("package", requireActivity().packageName, "Home") intent.data = uri storageActivityResultLauncher.launch(intent) } catch (e: Exception){ val intent = Intent() intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION storageActivityResultLauncher.launch(intent) } } else{ //Android is below 11(R) ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_CODE ) } } private val storageActivityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ //here we will handle the result of our intent if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){ //Android is 11(R) or above if (Environment.isExternalStorageManager()){ //Manage External Storage Permission is granted } } else{ //Android is below 11(R) } } private fun checkPermission(): Boolean{ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){ //Android is 11(R) or above Environment.isExternalStorageManager() } else{ //Android is below 11(R) val write = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) val read = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) write == PackageManager.PERMISSION_GRANTED && read == PackageManager.PERMISSION_GRANTED } } }
我不确定变量未初始化的原因是否和线程有关,该如何解决这个问题?
问题原因
完全和线程有关。线程是异步执行的:你调用Thread.start()后,主线程会立刻继续执行后续的println(sheets)和sheets.getValues("A1"),但此时子线程还没来得及完成sheets的初始化,所以触发了lateinit变量未初始化的异常。
解决方案
方案1:在子线程内完成初始化后再执行操作
将对sheets的调用放在子线程内部,确保初始化完成后再使用。如果需要更新UI,要切换回主线程:
load.setOnClickListener(View.OnClickListener { Thread { sheets = GoogleSheets(requireContext(), "1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA") // 初始化完成后再执行操作 val cellValue = sheets.getValues("A1") // 切换回主线程处理结果(如果需要更新UI) requireActivity().runOnUiThread { println(sheets) println(cellValue) // 这里可以添加UI更新逻辑,比如把数据显示到TextView } }.start() })
方案2:使用Kotlin协程(推荐)
协程是Android官方推荐的异步处理方案,比Thread更简洁且易于管理:
- 确保项目依赖了
kotlinx-coroutines-android(在app的build.gradle中添加):
dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" }
- 修改Fragment代码:
class Home: Fragment(R.layout.fragment_home) { private lateinit var sheets: GoogleSheets private val STORAGE_PERMISSION_CODE = 100 // 声明协程作用域,绑定Fragment生命周期 private val coroutineScope = CoroutineScope(Dispatchers.IO + Job()) // ... 其他代码不变 ... load.setOnClickListener { coroutineScope.launch { // 在IO线程初始化GoogleSheets sheets = GoogleSheets(requireContext(), "1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA") val cellValue = sheets.getValues("A1") // 切换到主线程处理结果 withContext(Dispatchers.Main) { println(sheets) println(cellValue) } } } // Fragment销毁时取消协程,避免内存泄漏 override fun onDestroy() { super.onDestroy() coroutineScope.cancel() } }
方案3:使用ViewModel管理实例(进阶)
如果GoogleSheets实例需要在配置变更(比如屏幕旋转)后保留,建议将其放在ViewModel中,在ViewModel的后台线程初始化:
class SheetsViewModel: ViewModel() { var sheets: GoogleSheets? = null fun initSheets(context: Context, sheetId: String) { viewModelScope.launch(Dispatchers.IO) { sheets = GoogleSheets(context, sheetId) } } }
在Fragment中使用ViewModel:
private val viewModel: SheetsViewModel by viewModels() // 按钮点击事件 load.setOnClickListener { viewModel.initSheets(requireContext(), "1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA") // 可以观察viewModel中的sheets变化,或者在需要时判断是否已初始化 viewModel.viewModelScope.launch { // 等待初始化完成(或者用LiveData观察) while (viewModel.sheets == null) { delay(100) } val cellValue = viewModel.sheets?.getValues("A1") withContext(Dispatchers.Main) { println(cellValue) } } }
注意事项
lateinit变量必须在首次使用前完成初始化,异步操作无法保证主线程使用时变量已就绪。- 所有涉及网络、IO的耗时操作必须放在后台线程,UI操作必须回到主线程执行。
- 协程方案中,一定要绑定生命周期,避免内存泄漏。
内容的提问来源于stack exchange,提问作者user17792511
相关产品推荐
相关产品推荐

