为什么这段代码没有错误运行?我正在从AsyncTask的doInBackground更新UI

时间:2019-02-20 06:41:01

标签: android android-asynctask

我正在尝试AsyncTask,发现了一个不寻常的情况,这是我的代码。

class MainActivity : AppCompatActivity() {
    lateinit var textView:TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textView = findViewById(R.id.textView)
        val task = MyAsyncTask()
        task.execute()
    }

    inner class MyAsyncTask:AsyncTask<Void,Void,String>() {
        override fun doInBackground(vararg params: Void?): String {
            //Thread.sleep(3000)
            textView.text ="From AsyncTask"
            return "hello"
        }
    }
}

它有效!!,请参阅Thread.sleep的注释,但如果我取消注释,那么我们知道的常见错误

Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

为什么会这样,如果有人知道,请分享您的反馈。谢谢

4 个答案:

答案 0 :(得分:0)

是时候了。当您注释Thread.sleep(3000)时,您的视图尚未准备就绪并与您的活动相关联,因此此版本的代码仅更新了UI中尚不存在的视图。

但是,经过一段时间后,UI将启动,并且当您在doInBackground()中更新视图时,您将收到一个异常,指出只有创建视图层次结构的原始线程才能触摸其视图。这也证明了取消注释Thread.sleep(3000)

时遇到的异常是合理的

答案 1 :(得分:0)

因为您在以下代码中调用Thread.sleep

inner class MyAsyncTask:AsyncTask<Void,Void,String>() {
    override fun doInBackground(vararg params: Void?): String {

        Thread.sleep(3000)
        textView.text ="From AsyncTask"
        ...
    }
}

它将在当前线程上运行,当前线程是AsyncTask的后台线程。您可以在Thread.sleep文档中看到这一点:

  

公共静态无效睡眠(长毫秒,                       int nanos)

     

使当前正在执行的线程进入睡眠状态(暂时停止   执行)指定的毫秒数加上指定的   纳秒数,取决于系统的精度和准确性   计时器和调度程序。该线程不会失去任何所有权   显示器。

因此,下一个代码也将切换到后台线程:

textView.text ="From AsyncTask"

这将引发异常。

答案 2 :(得分:0)

当您注释掉Thread.sleep(3000)时,您的应用仍然可以正常运行而不会崩溃,因为当时textView在屏幕上尚不可见。要确保textView在屏幕上可见,可以使用addOnGlobalLayoutListener。现在,无论您是否注释掉Thread.sleep(3000),您的应用程序总是崩溃。

class MainActivity : AppCompatActivity() {
    lateinit var textView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textView = findViewById(R.id.textView)

        textView.viewTreeObserver.addOnGlobalLayoutListener {
            // This view is visible on screen
            val task = MyAsyncTask()
            task.execute()
        }
    }

    inner class MyAsyncTask : AsyncTask<Void, Void, String>() {
        override fun doInBackground(vararg params: Void?): String {
            // Thread.sleep(3000)
            textView.text = "From AsyncTask From AsyncTask From AsyncTask"
            return "hello"
        }
    }
}

另一方面,当您不注释掉Thread.sleep(3000)时,您的应用会在3秒后立即崩溃,因为那时textView已经在屏幕上可见,这就是为什么您的应用崩溃

那么这个异常是从哪里来的。看看ViewRootImpl source code

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

来自this link

  

requestLayout()

     

如果关于您视图的某些更改会影响大小,则   您应该调用requestLayout()。这将触发onMeasure和   onLayout不仅适用于此视图,而且一直沿   父视图。

当您更新textView的文本时,其大小将改变,这就是为什么调用requestLayout的原因,因此,它将调用checkThread并抛出CalledFromWrongThreadException

答案 3 :(得分:-1)

作为例外明确指出:

  

只有创建视图层次结构的原始线程才能触摸其   视图。

AsyncTask doInBackground在独立的后台线程中运行,而该后台线程与Main Thread无关,现在所有与UI相关的工作都应在Main线程上完成,而不是settext doInBackgroundonpostexecute中完成。