You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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更简洁且易于管理:

  1. 确保项目依赖了kotlinx-coroutines-android(在app的build.gradle中添加):
dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 20:01:14