android 线程中未初始化lateinit变量

jaxagkaj  于 2022-11-20  发布在  Android
关注(0)|答案(2)|浏览(282)

我在一个android应用程序中使用google sheets api,并试图在一个单独的线程中获取凭据。
GoogleSheets是我创建的一个类,用于获取电子表格的凭据和单元格值
private lateinit var sheets: GoogleSheets是一个示例变量,我在类的开头声明了这个变量。

load.setOnClickListener(View.OnClickListener {
            Thread {
                sheets = GoogleSheets(requireContext(), "1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA")
            }.start()
            println(sheets)
            println(sheets.getValues("A1"))
        })

但是它告诉我sheets变量还没有初始化:

kotlin.UninitializedPropertyAccessException: lateinit property sheets has not been initialized

下面是完整的类:

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
        }
    }
}

我不知道为什么变量没有被初始化。它是否与它在线程中有关?我如何解决这个问题?谢谢!!

63lcw9qa

63lcw9qa1#

你正在启动另一个线程来初始化它,所以如果你立即检查它,另一个线程还没有时间初始化属性。这是lateinit的误用,你也没有利用线程同步,所以它很容易受到其他bug的影响。
我建议加载带有协程的工作表,当需要在任何地方使用它时,使用suspend函数来检索示例。当只使用协程来访问属性时,不需要担心线程同步。
实际上,这应该放在一个比Fragment更长寿的类中,这样你就不必在每次重新创建Fragment时都重新加载它,但是为了简单起见,我在这个例子中只将它放在Fragment中。

class Home: Fragment(R.layout.fragment_home) {
    private val loadSheetsDeferred = viewLifecycle.lifecycleScope.async(Dispatchers.IO) {
        GoogleSheets(requireContext(), "1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA")
    }

    private suspend fun getSheets(): GoogleSheets = loadSheetsDeferred.await()

    private val STORAGE_PERMISSION_CODE = 100

    @RequiresApi(Build.VERSION_CODES.P)
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //...

        new.setOnClickListener { viewLifecycle.lifecycleScope.launch {
            getSheets().setValues("A1", "this is a test", "USER_ENTERED")
            println(getSheets().getValues("A1").values)
        } }

        load.setOnClickListener { viewLifecycle.lifecycleScope.launch {
            println(getSheets())
            println(getSheets().getValues("A1"))
        } }

        return view
    }

    //...
}
zxlwwiss

zxlwwiss2#

我猜错误是在告诉你,当你这样做的时候,错误就发生了:

Thread {
    sheets = GoogleSheets(requireContext(), "1fs1U9-LMmkmQbQ2Kn-rNVHIQwh6_frAbwaTp7MSyDIA")
}.start()
println(sheets)

lateinit是您向编译器承诺,在任何东西试图读取sheets之前,您已经为它赋值了,您正在对println(sheets)执行此操作。您在刚刚启动的线程中对它赋值--但在当前线程上运行println语句之前,该操作不太可能完成!
如果你涉及到这样的线程,你也必须担心同步--仅仅因为你的工作线程设置了sheets的值,并不意味着 current 线程会看到更新的值。如果你不熟悉整个问题和你必须采取的步骤来确保事情保持一致,你可以看一下here
你最好的办法就是在sheets赋值后,在线程内做println的事情。如果你做的事情比这更复杂,并且需要回到主线程上,你可以在视图上做postRunnable。老实说,如果你可以用协程来代替,从长远来看,这可能会让你的生活更容易

相关问题