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

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

У меня есть:

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

Исходя из этого метода в трассировке стека (восстановлено):

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() вызывается из отправленного сообщения обработчика (thead the UI).

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

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

РЕДАКТИРОВАТЬ: Это все происходит фрагментом

6 ответов

Решение

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

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

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

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

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

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

пример:

    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

это огромный провал для 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), что означает, что он всегда будет выполняться в "основном потоке" или "входном потоке", который они неправильно называют "потоком пользовательского интерфейса" (который считается любым потоком, в котором Взаимодействие с пользовательским интерфейсом происходит, например, в нескольких случаях, в данном случае) это и некачественное мастерство, и дрянная документация от команды Android... слабый соус, слабый соус

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

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

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

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

История:

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

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

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

для метода, запущенного из AsynckTask.onPostExecute()

После некоторого расследования оказалось, что служебное приложение 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 (раздел " Правила потоков "):

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

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

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

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

Размещение следующей строки кода в Application 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

Надеюсь, это прояснило ситуацию немного больше.

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

Где

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());
                   }
                }

            };

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

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

Другие вопросы по тегам