Почему этот код работает без ошибок?Я обновляю пользовательский интерфейс от doInBackground AsyncTask - PullRequest
0 голосов
/ 20 февраля 2019

Я экспериментировал с 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"
        }
    }
}

Это сработало!что мы знаем

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

Почему так, если кто-нибудь знает, пожалуйста, поделитесь своим мнением.Спасибо

Ответы [ 4 ]

0 голосов
/ 20 февраля 2019

Когда вы комментируете 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

@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.");
    }
}

с по этой ссылке

requestLayout ()

Если что-то в вашем представлении изменится, что повлияет на размер, вам следует вызвать requestLayout ().Это вызовет onMeasure и onLayout не только для этого представления, но и до линии родительских представлений.

Когда вы обновляете текст для textView, его размер будет изменен, поэтому requestLayout вызывается, в результате он вызывает checkThread и выбрасывает CalledFromWrongThreadException.

0 голосов
/ 20 февраля 2019

Речь идет о времени.Когда вы комментируете Thread.sleep (3000), ваше представление не готово и не связано с вашей деятельностью, поэтому эта версия кода просто обновляет представление, которое еще не существует в пользовательском интерфейсе.

Однако через некоторое время пользовательский интерфейс будет запущен, и когда вы обновите представление в doInBackground (), вы получите исключение, в котором указано Только исходный поток, создавший иерархию представления, может коснуться его.просмотры .Это также оправдывает исключение, которое вы получили, когда раскомментировали Thread.sleep (3000)

0 голосов
/ 20 февраля 2019

потому что когда вы вызываете 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 :

public static void sleep (long millis, int nanos)

Заставляет текущий выполняющийся поток спать(временно прекратить выполнение) на указанное количество миллисекунд плюс указанное количество наносекунд, в зависимости от точности и точности системных таймеров и планировщиков.Поток не теряет права собственности ни на какие мониторы.

Таким образом, следующий код также переключится на фоновый поток:

textView.text ="From AsyncTask"

, что вызовет исключение.

0 голосов
/ 20 февраля 2019

Как ясно говорит исключение:

Только исходный поток, создавший иерархию представлений, может касаться его представлений.

AsyncTask doInBackground работает в отдельномфоновый поток, который не имеет никакого отношения к основному потоку, теперь вся работа, связанная с пользовательским интерфейсом, должна выполняться в основном потоке, так что вместо settext на doInBackground выполняйте это в onpostexecute.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...