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.
Мой вопрос: как это могло быть?
ИЗМЕНИТЬ:
Все это происходит в фрагменте
Ответы
Ответ 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