如何在 Kotlin 中使用 Thread.sleep()

6
这段内容来自于这里的代码实验室的最后部分:

调试入门 - 调试示例:访问不存在的值

这都在 MainActivity.kt 文件中。
下面是我的 onCreate。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val helloTextView: TextView = findViewById(R.id.division_textview)
    helloTextView.text = "Hello, debugging!"

    division()
}
//...

以下是谷歌提供的除法函数,但稍作修改,接下来我会做出解释...
fun division() {
  val numerator = 60
  var denominator = 4
    repeat(4) {
     Thread.sleep(3000)
     findViewById<TextView>(R.id.division_textview).setText("${numerator / denominator}")
     Log.v(TAG, "${numerator / denominator}")
     denominator--
    }
  }

这些指令似乎期望sleep()接受秒数,但AS表明它期望毫秒,所以我将他们的3改为3000。我添加了Log.v(TAG, "${numerator / denominator}")来查看发生了什么,因为它并不像预期那样运行。
这个目的是让模拟器创建一个更新商的gif。这在调试时应该很有帮助。
屏幕上什么也没有显示,甚至连应用程序的名称都没有,直到repeat()完成。 日志按照3秒间隔发生,如预期。
为什么布局要等待,如何使其在每次循环迭代时更新?
3个回答

5

根据提供的代码,我真的不知道那个 Codelab 在干什么。在 onCreate 完成之前(既不是布局,也不是任何对布局的更改),应用程序不会呈现任何内容,而且 onCreate 不会完成,直到运行了它所有的代码,包括调用 division 函数中 repeat 块的代码。

division 没有启动任何工作线程,因此所有 Thread.sleep 所做的就是阻塞主线程 - 这会挂起应用程序。而且你是正确的,sleep 确实需要毫秒值,而不是秒 - 我觉得他们并没有实际运行这段代码,它充满了其他错误和不一致之处,这实际上让人难以理解应该做什么。改变哪个 Log.d 调用?在 onCreate 中的那些吗?(他们实际上指的是 division 中的 Log.v 调用,我认为)


以下是如何在 Kotlin 中使用线程 - 您需要创建一个新线程(因此您不在主线程上,这样它才能完成创建活动并运行 UI):

fun division() {
    // create a new thread (and start it immediately)
    thread(start=true) {
        repeat(4) { i ->
            Thread.sleep(3000L)
            // assuming you've done the ``findViewById`` and assigned it to a variable
            runOnUiThread { divisionTextView.text = "$i" }
        }
    }
}

这里只是为了简洁而使用当前重复次数(i)进行更新,但重要的事情是:
  • 你正在创建一个新线程来完成工作(并且睡眠)
  • 你正在使用runOnUiThread(这是Activity的一个方法)在主线程/ UI线程上更新文本(同样的东西)。如果您尝试从另一个线程触摸UI它将崩溃

另一种方法是通过视图向UI线程post可运行线程,可以使用那个TextView

divisionTextView.post { divisionTextView.text = "$i" }

在这里使用协程会是一个更好的选择(您可以在主线程上运行它们而不会阻塞,因此您不必担心切换线程以更新UI或任何线程安全性问题),但那就是执行线程的基础知识。我真的不知道这个代码实验中发生了什么。


1
大多数的代码实验室都相当不错,但你说得对。这个有点乱!有趣的是,它是一个调试实验室,并且我想它提到了堆栈溢出。 :-) - TecBrat
当我回到我的开发机器时,我会尝试你的代码并看看是否能使其工作。更重要的是,我会看看自己是否完全理解它。谢谢。 - TecBrat
1
@TecBrat 是的,很多问题都可以在这里找到答案,这很好,但我觉得它应该有自己的部分!如果您不熟悉线程,请阅读https://docs.oracle.com/javase/tutorial/essential/concurrency/ - 至少要了解“同步”,但“活性”也很重要(此代码实验室存在活性问题!)。Kotlin版本就像一个方便的构建器,一旦您知道正常的方式,它就会很有意义。一旦您掌握了基础知识,这就是重要的Android内容:https://developer.android.com/guide/components/processes-and-threads - cactustictacs
这个答案中的代码可行。谢谢。 - TecBrat

2
屏幕上什么都没有显示,甚至连应用程序的名称也没有,直到repeat()完成。日志按照3秒间隔发生,正如预期的那样。为什么布局要等待?
这是由于Activity生命周期所致。
- 当活动处于Created状态时,您无法看到UI。 - 当活动处于Started状态时,UI变得可见,但您无法与其交互。 - 当活动处于Resumed状态时,UI可见,您可以与其交互。
由于您在onCreate()回调中调用了division()函数,因此您将无法看到活动UI。
如何使它在循环的每次迭代中更新?
一种方法是在新线程中运行division()函数。通过这样做,主线程显示UI(您将能够看到它)。然后使用runOnUiThread来更新division_textview文本。
    fun division() {
    val numerator = 60
    var denominator = 4
    thread(start=true) {
        /* The loop only repeat 4 times to avoid a crash*/
        repeat(4) {
            Log.v(TAG, "$denominator")
            // Set division_textview text to the quotient.
            runOnUiThread { findViewById<TextView>(R.id.division_textview).setText("${numerator / denominator}") }
            // Wait for 1 second
            Thread.sleep(1000L)
            denominator--
        }
    }
}

0
你可以使用协程。delay函数非常容易使用。
lifecycleScope.launch {
    myTextView.text = "Starting"
    delay(1000L)
    myTextView.text = "Processing"
    delay(2000L)
    myTextView.text = "Done"
}

尽管协程是当今Android上的首选方法,但这与OP试图做的事情(遵循codelab)有些超出范围。您还没有提供一个单一的链接来说明协程是什么,它的作用或lifecycleScope来自哪里。 - Martin Marconcini

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接