Подтвердить что ты не робот

Android: получил CalledFromWrongThreadException в onPostExecute() - Как это могло быть?

У меня есть приложение в производстве в течение нескольких недель, используя ACRA, и у меня были ошибки с ошибкой, пока не появилась одна странная ошибка.

У меня есть:

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

исходящий из этого метода в трассировке стека (retraced):

at my.app.CountdownFragment$1.void onPostExecute(java.lang.Object)(SourceFile:1)

И это соответствующий фрагмент исходного кода:

    private void addInstructionsIfNeeded() {
    if (S.sDisplayAssist) {
        new AsyncTask<String, Void, String>() {

            @Override
            protected String doInBackground(String... params) {
                return null;
            }

            /*
             * runs on the ui thread
             */
            protected void onPostExecute(String result) {

                Activity a = getActivity();

                if (S.sHelpEnabled && a != null) {

                    in = new InstructionsView(a.getApplicationContext());

                    RelativeLayout mv = (RelativeLayout) a
                            .findViewById(R.id.main_place);

                    mv.addView(in.prepareView());
                }

            };

        }.execute("");
    }
}

Где addInstructionsIfNeeded() вызывается из отправленного обработчиком сообщения (UAD thead).

  • onPostExecute() работает в потоке пользовательского интерфейса, поэтому почему у меня есть "неправильный поток"?
  • Этот код работает уже на более чем 150 устройствах и более 100000 раз (согласно Flurry) и никогда не имел этой ошибки.
  • Исходным устройством является Samsung SGH-I997, работающий с SDK 4.0.4.

Мой вопрос: как это могло быть?

ИЗМЕНИТЬ: Все это происходит в фрагменте

4b9b3361

Ответ 1

Я страдал от одной и той же проблемы, это еще одна ошибка системы Android...

что происходит:

при определенных обстоятельствах приложение может иметь более одного "петлителя" и, следовательно, более одного "потока пользовательского интерфейса"

- side note - Я использую термин "поток пользовательского интерфейса" в самых слабых чувствах в этом ответе, поскольку, когда люди говорят "поток пользовательского интерфейса", они обычно означают главную или входную нить, Android, как и многие другие ОС перед ним, допускает использование нескольких сообщений (например, Looper в Android, см. http://en.wikipedia.org/wiki/Event_loop) для разных деревьев пользовательского интерфейса. такой андроид для всех целей и целей способен запускать более чем один "поток пользовательского интерфейса" в определенных обстоятельствах, и использование этого термина приводит к безудержной двусмысленности... - примечание к концу -

это означает:

поскольку приложение может иметь более одного "потока пользовательского интерфейса" и AsyncTask всегда "работает в потоке пользовательского интерфейса" [ref], кто-то решил [плохо ], что вместо AsyncTask всегда работает на своем потоке создания (который в 99,999999% случаев будет правильным "потоком пользовательского интерфейса" ), они решили использовать hocus pocus (или плохо обработанный ярлык, который вы решили) выполнить на "основной петлителя"..

Пример:

    Log.i("AsyncTask / Handler created ON: " + Thread.currentThread().getId());
    Log.i("Main Looper: " + Looper.getMainLooper().getThread().getId() + "      myLooper: "+ Looper.myLooper().getThread().getId());

    new AsyncTask<Void, Void, Void>() {

        @Override
        protected Void doInBackground(Void... params) {
            Log.i("doInBackground ran ON: " + Thread.currentThread().getId());
            // I'm in the background, all is normal

            handler.post(new Runnable() {

                @Override
                public void run() {
                    Log.i("Handler posted runnable ON: " + Thread.currentThread().getId());
                    // this is the correct thread, that onPostExecute should be on
                }
            });

            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            Log.i("onPostExecute ran ON: " + Thread.currentThread().getId());
            // this CAN be the wrong thread in certain situations
        }

    }.execute();

, если вызывается из плохой ситуации, описанной выше, результат будет выглядеть примерно так:

    AsyncTask / Handler created ON: 16
    Main Looper: 1      myLooper: 16
    doInBackground ran ON: 12
    onPostExecute ran ON: 1
    Handler posted runnable ON: 16

, что огромный FAIL для AsyncTask

как показано, это может быть уменьшено с помощью Handler.post(Runnable) в моем конкретном случае, двойственность моей ситуации "потока пользовательского интерфейса" была вызвана тем, что я создавал диалоговое окно в ответ на метод интерфейса JavaScript, называемый с WebView, в основном: WebView имел свой собственный "поток пользовательского интерфейса", и это был тот, который я в настоящее время выполнял.

из того, что я могу сказать (без особого внимания или чтения в нем слишком много), кажется, что методы обратного вызова класса AsyncTask в целом сбегают от одного статически созданного экземпляра обработчика (см. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.3_r1/android/os/AsyncTask.java#AsyncTask.0sHandler), что означает, что он всегда будет выполняться в "основном потоке" или "потоке ввода", который они неправильно называют "потоком пользовательского интерфейса" (который предполагается как любой поток где взаимодействуют пользовательские интерфейсы, например, несколько потоков в этом случае), это и дрянное мастерство, и дрянная документация от команды андроида... слабый соус, соус слаб.

надеюсь, что это поможет вам -ck

Ответ 2

Была та же проблема. Решено в моем случае

Кратко объяснение:

  • Запуск AsynckTask для в первый раз в не UI поток с помощью петлителя приводит к загрузке AsyncTask.class и инициализации sHandler обработчику, построенному на это не UI.
  • Теперь sHandler подключен к этому потоку не UI для ЛЮБОГО экземпляра подклассов AsyncTask и onPreExecute, onProgressUpdate и onPostExecute будут задействованы в этом потоке non UI (если AsyncTask.class не будет выгружен)
  • Любая попытка обработать пользовательский интерфейс внутри любого из вышеперечисленных методов приведет к сбою с android.view.ViewRootImpl $CalledFromWrongThreadException
  • Чтобы избежать такой ситуации, нужно выполнить всегда (по крайней мере для в первый раз) AsyncTask в потоке пользовательского интерфейса, чтобы позволить AsyncTask sHandler - поле должно быть инициализировано с помощью UI looper

История:

Было два приложения для производства: A - основное приложение для Android и B - некоторое приложение-приложение.

После приложения интеграции B ito app A мы получили много сбоев:

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

для метода, выполняющегося от AsynckTask.onPostExecute()

После некоторого исследования оказалось, что утилита app B использует AsyncTask в своем HandlerThread

Следы были найдены в исходном коде AsyncTask:

private static final InternalHandler sHandler = new InternalHandler();

Это обработчик, который используется для отправки onPostExecute() в поток пользовательского интерфейса.

Этот обработчик статический, и он будет инициализирован во время загрузки класса, то есть сначала новый внешний вид AsyncTask()

Это означает, что onPostExecute всегда будет отправляться в этот поток, где впервые был вызван новый AsyncTask() (если AsyncTask.class не будет выгружен и не загружен снова )

В моем случае поток был примерно таким:

1 - starting app A
2 - initializing B form A
3 - B creates its own HandlerThread and launches AsyncTask <- now onPostExecute wil be posted to this HandlerThread no matter where from an instance of AsyncTask will be launched in future
4 - create AsyncTask in the app A for a long operation and update UI in its onPostExecute
5 - when executing onPostExecute() the CalledFromWrongThreadException is thrown

Затем мой друг показал мне связанную документацию из android.developers (Правила Threading):

Класс AsyncTask должен быть загружен в поток пользовательского интерфейса. Готово автоматически по состоянию на JELLY_BEAN. Экземпляр задачи должен быть создан поток пользовательского интерфейса. execute (Params...) должен быть вызван в потоке пользовательского интерфейса.

Надеюсь, это поможет прояснить ситуацию)

Ответ 3

Может быть, причина в Flurry? У меня было это исключение, когда я использовал Flurry 3.2.1. Но когда я вернулся к Flurry 3.2.0, у меня не было этого исключения

Используйте Flurry 3.2.2 и выше.

Ответ 4

Размещение следующей строки кода в приложении onCreate должно решить проблему:

     /**
     * Fixing AsyncTask Issue not called on main thread
     */
    try {
        Class.forName("android.os.AsyncTask");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

Кажется, проблема возникает, когда класс AsyncTask сначала инициируется в другом главном потоке, который не является нашим главным потоком, я проверил его, добавив код внизу, в мое приложение onCreate

    new Thread(new Runnable() {


        @Override
        public void run() {

            Log.i("tag","1.3onPostExecute ran ON: " + Thread.currentThread().getId());
            Looper.prepare();
            new AsyncTask<Void,Void,Void>(){
                @Override
                protected Void doInBackground(Void... params) {
                    Log.i("tag","2onPostExecute ran ON: " + Thread.currentThread().getId());
                    return null;
                }

                @Override
                protected void onPostExecute(Void aVoid) {
                    Log.i("tag","1.2onPostExecute ran ON: " + Thread.currentThread().getId());
                    super.onPostExecute(aVoid);
                }
            }.execute();
            Looper.loop();
            Looper.myLooper().quit();
        }
    }).start();

Этот код запустит AsynTask в основном потоке, который не является основным приложением, и приведет к сбою приложения в любой другой AsyncTask, которая будет выполнять любой пользовательский интерфейс после выполнения. сбой при вызове CalledFromWrongThreadException

Надеюсь, что он очистил вещи немного больше.

Спасибо всем за большую помощь в этом.

Ответ 5

Где

runOnUiThread(new Runnable() {
    public void run() { /*code*/ } );

в вашем коде

/*
             * runs on the ui thread
             */
            protected void onPostExecute(String result) {

                Activity a = getActivity();

                if (S.sHelpEnabled && a != null) {

                    in = new InstructionsView(a.getApplicationContext());

                    runOnUiThread(new Runnable() {
                       public void run() {

                    RelativeLayout mv = (RelativeLayout) a
                            .findViewById(R.id.main_place);

                    mv.addView(in.prepareView());
                   }
                }

            };

Попробуйте этот код. Я думаю, что это устранит проблему.

Ответ 6

Я думаю, что проблема кроется в строке Activity a = getActivity(); Я думаю, вы должны это сделать, прежде чем входить в AsyncTask