Почему ViewRootImpl$CalledFromWrongThreadException не является точным?

Фрагмент с построчным обновлением TextView (через setText() старую строку на новую) из потока, не связанного с пользовательским интерфейсом:

TextView mLog;
final static String ONE_LINER;

// Condition 1: update from loopless thread
new Thread(new Runnable() {
    @Override
    public void run() {
        mLog.append(ONE_LINER);
    }
}).start();

// Condition 2: update from thread with Looper
HandlerThread handlerThread = new HandlerThread();
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        mLog.append(ONE_LINER);
    }
};
handler.sendEmptyMessage();

Выполнение кода вызывает CalledFromWrongThreadException с разными трассировками стека (пожалуйста, проверьте "Stacktrace #1" и "Stacktrace #2" ниже). В обоих вариантах (обновление View из простого Thread или из HandlerThread, внутри которого есть Looper) генерация исключений не является точной — событие не происходит точно после каждого обновления пользовательского интерфейса. через рабочий поток.

У кого-нибудь есть мысли, почему поведение системы при следующих процедурах вызова (в тех же входных условиях) не является постоянным?

Stacktrace #1 (частота: высокая; в большинстве случаев)

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
            at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7900)
            at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:1207)
            at android.view.ViewGroup.invalidateChild(ViewGroup.java:5424)
            at android.view.View.invalidateInternal(View.java:13884)
            at android.view.View.invalidate(View.java:13848)
            at android.view.View.invalidate(View.java:13832)
            at android.widget.TextView.updateAfterEdit(TextView.java:8918)
            at android.widget.TextView.handleTextChanged(TextView.java:8943)
            at android.widget.TextView$ChangeWatcher.onTextChanged(TextView.java:11623)
            at android.text.SpannableStringBuilder.sendTextChanged(SpannableStringBuilder.java:1037)
            at android.text.SpannableStringBuilder.replace(SpannableStringBuilder.java:563)
            at android.text.SpannableStringBuilder.append(SpannableStringBuilder.java:286)
            at android.text.SpannableStringBuilder.append(SpannableStringBuilder.java:34)
            at android.widget.TextView.append(TextView.java:4454)
            at android.widget.TextView.append(TextView.java:4441)

Stacktrace #2 (частота: редко; поймано один раз)

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
            at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7900)
            at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1170)
            at android.view.View.requestLayout(View.java:20043)
            at android.view.View.requestLayout(View.java:20043)
            at android.view.View.requestLayout(View.java:20043)
            at android.view.View.requestLayout(View.java:20043)
            at android.view.View.requestLayout(View.java:20043)
            at android.view.View.requestLayout(View.java:20043)
            at android.view.View.requestLayout(View.java:20043)
            at android.widget.TextView.checkForRelayout(TextView.java:8024)
            at android.widget.TextView.setText(TextView.java:4959)
            at android.widget.TextView.setText(TextView.java:4786)
            at android.widget.TextView.append(TextView.java:4451)
            at android.widget.TextView.append(TextView.java:4441)

Примечание.

  1. Нет ошибки "Только исходный поток, создавший иерархию представлений, может касаться своих представлений", когда представление обновляется без задержки объяснение не полностью охватывает проблему (только "Stacktrace #2").
  2. ОС: Андроид 6.0.

comment
Вы спрашиваете, почему TextView будет иметь разные пути кода, вызываемые в результате добавления? Почему это важно?   -  person alanv    schedule 12.11.2015
comment
@alanv основной интерес представляет Почему CalledFromWrongThreadException не срабатывает каждый раз, когда происходят неправильные обновления пользовательского интерфейса? Обнаружение источника поведения полезно в случае дальнейшего построения теста по принципу отказоустойчивости потому что, наоборот, это приведет к непредсказуемым и трудно воспроизводимым результатам тестирования.   -  person Yehor Nemov    schedule 13.11.2015