2

Я смущен тем, как работает система Android, особенно когда она обновляет иерархию представлений. Мы все знаем, что мы не должны обновлять какой-либо вид из любого потока, кроме потока пользовательского интерфейса (Main). И даже система Android выбрасывает исключение, когда мы пытаемся это сделать. На днях я пытался реализовать пользовательский прогресс, показывающий представление в моем приложении. Поэтому я начал с использования стандартных потоков Java и компилятора обработчика.Обновление просмотров из не-пользовательских потоков

Что меня удивило, так как я смог обновить TextView из фонового потока.

new Thread(new Runnable() { 

     @Override 
     public void run() { 
      mTextView.setText("I am " + Thread.currentThread().getName()); 
     } 
    }).start(); 

После этого я попробовал обновить другие виды, которые работали довольно хорошо. Поэтому я попытался поставить вызов сна в фоновом потоке.

new Thread(new Runnable() { 

     @Override 
     public void run() { 
      mTextView.setText("Thread : before sleep"); 
      try { 
       Thread.sleep(1000); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
      mTextView.setText("Thread : after sleep"); 
     } 
    }).start(); 

И он выходит из строя, как ожидается, говоря

android.view.ViewRootImpl $ CalledFromWrongThreadException: только оригинальный поток, который создал иерархию вида может коснуться его точки зрения.

Затем я попытался положить SetText() вызывает в петлю на 100, 1000 раз до и после вызова сна(). Конечно, приложение падает каждый раз, но я смог увидеть текст «Перед сном» в своем текстовом виде.

Так что мой вопрос когда же система обнаружит, что некоторые не-UI поток пытается обновить представление. И Почему он не работает, когда нет вызова sleep() в не-UI-потоке?

+0

Просто используйте android.os.Handler - он всегда будет запускать свою runnable на UI-thread. – Dogcat

+0

когда? см. 'android/view/ViewRootImpl.java' имя метода' checkThread' – pskink

+0

@Dogcat: Я знаю о обработчике. Но мой вопрос в том, почему в первом случае не происходит сбоев системы? – apersiankite

ответ

-1

Резьба представляет собой параллельный процесс для потока пользовательского интерфейса. когда вы пытаетесь включить функцию сна внутри потока, выполнение потока прекращается. Ответ на ваш вопрос находится внутри самого вопроса. он говорит - только исходный поток, создавший иерархию представлений, может коснуться его представлений. поэтому другие - это два потока, выполняющие один поток ui, а другой - тот, который вы создали. когда вы вызываете метод сна. ваш поток останавливает выполнение, для которого нет синхронизации с нитью ui. и когда ваш поток пытается изменить текст textview, оба потока не синхронизируются. перед сном поток был синхронизирован. и после сна они не синхронизированы.

+1

Прежде всего ** Нить не является процессом **. Забудьте о вызове sleep() в течение минуты и попробуйте код без сна. Код работает нормально. Вот в чем я сомневаюсь, почему система андроида позволяет любому потоку, отличному от UI, обновлять представление. ** Он не генерирует исключение **. И, кстати, нет синхронизации между потоком пользовательского интерфейса и любым другим потоком, внесли ли вы его в сон или нет. – apersiankite

+0

ok .. использование asynctask .. – TheAndroidFreak

+1

Вы шутите? Я уже сказал вам (на ваш последний ответ), что я знаю все проблемы многопоточности и связанные с ними шаблоны для использования при написании кода для Android. ** Пожалуйста, перечитайте мой вопрос.** – apersiankite

2

Я запустил фрагмент кода с sleep в Lollipop, и он сработает. Трассировка стека является:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 
     at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357) 
     at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:909) 
     at android.view.ViewGroup.invalidateChild(ViewGroup.java:4690) 
     at android.view.View.invalidateInternal(View.java:11801) 
     at android.view.View.invalidate(View.java:11765) 
     at android.view.View.invalidate(View.java:11749) 
     at android.widget.TextView.checkForRelayout(TextView.java:6850) 
     at android.widget.TextView.setText(TextView.java:4057) 
     at android.widget.TextView.setText(TextView.java:3915) 
     at android.widget.TextView.setText(TextView.java:3890) 
     at com.test.MainActivity$16.run(MainActivity.java:1126) 
     at java.lang.Thread.run(Thread.java:818) 

Так ключевые шкура вокруг линии 4057 из TextView.setText, которая является:

if (mLayout != null) { 
    checkForRelayout(); 
} 

Мы можем увидеть, если mLayout из TextView является null, checkForRelayout() не будет называться и таким образом приложение не будет разбиваться. И mLayout будет инициализирован в onDraw от TextView. Таким образом, приложение не разбивается при первом вызове setText, потому что mLayout имеет значение NULL. После рисования инициализируется mLayout и вызывает прибой приложения во второй раз, когда вызывается setText.

Я думаю, вы начинаете Thread до того, как нарисован TextView (например, в onCreate или onResume). Правильно?

Независимо от того, произойдет ли сбой приложения или нет, вы можете позвонить по телефону TIME, которое вы вызываете setText. Если вы вызываете setText, прежде чем TextView будет нарисован, все будет в порядке. В противном случае приложение выйдет из строя.

+0

Итак, вы хотите сказать, что Thread.sleep() каким-то образом инициализирует mlayout в TextView. Потому что даже если вы вызываете первый setText() в цикле 1000 раз, он ведет себя одинаково. – apersiankite

+0

Вы можете попробовать добавить 'sleep' перед первым вызовом' setText'. Приложение также потерпит крах. – shhp

+0

Во время 'Thread.sleep()' 'TextView' уже нарисован, что также означает инициализацию' mLayout'. – shhp

Смежные вопросы