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... слабый соус, слабый соус
надеюсь, это поможет вам
Была такая же проблема. Решено в моем случае
Краткое объяснение:
- Первый запуск AsynckTask в потоке, не являющемся пользовательским интерфейсом, с Looper приводит к загрузке AsyncTask.class и инициализации sHandler для обработчика, созданного на этом не UI LOOPER.
- Теперь sHandler подключен к этому потоку, не являющемуся пользовательским интерфейсом, для ЛЮБОГО экземпляра подклассов AsyncTask и методов onPreExecute, onProgressUpdate и onPostExecute, которые будут вызываться в этом потоке без пользовательского интерфейса (если только AsyncTask.class не будет выгружен).
- Любая попытка разобраться с пользовательским интерфейсом внутри любого из вышеперечисленных методов приведет к сбою с android.view.ViewRootImpl$CalledFromWrongThreadException
- Чтобы избежать такой ситуации, всегда следует запускать (по крайней мере, в первый раз) 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